From 9b3baaaa8ea589cce70a08d42e98339333cc5546 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 12 Aug 2025 01:09:59 +0000 Subject: [PATCH] refactor: improve GPT-5 token limit implementation - Extract GPT5_MAX_OUTPUT_TOKENS as a named constant (10,000) - Improve model detection with more specific regex pattern - Add comprehensive documentation explaining the context window overflow issue - Add tests for date-suffixed models and invalid model name patterns - Update all test assertions to use the named constant --- src/shared/__tests__/api.spec.ts | 69 ++++++++++++++++++++++++++++---- src/shared/api.ts | 20 ++++++--- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index 20a3b8746e..522030bb6e 100644 --- a/src/shared/__tests__/api.spec.ts +++ b/src/shared/__tests__/api.spec.ts @@ -1,5 +1,10 @@ import { describe, test, expect } from "vitest" -import { getModelMaxOutputTokens, shouldUseReasoningBudget, shouldUseReasoningEffort } from "../api" +import { + getModelMaxOutputTokens, + shouldUseReasoningBudget, + shouldUseReasoningEffort, + GPT5_MAX_OUTPUT_TOKENS, +} from "../api" import type { ModelInfo, ProviderSettings } from "@roo-code/types" import { CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" @@ -233,7 +238,7 @@ describe("getModelMaxOutputTokens", () => { format: "openai", }) - expect(result).toBe(10_000) + expect(result).toBe(GPT5_MAX_OUTPUT_TOKENS) }) test("should limit GPT-5-mini models to 10k max output tokens", () => { @@ -250,7 +255,7 @@ describe("getModelMaxOutputTokens", () => { format: "openai", }) - expect(result).toBe(10_000) + expect(result).toBe(GPT5_MAX_OUTPUT_TOKENS) }) test("should limit GPT-5-nano models to 10k max output tokens", () => { @@ -267,17 +272,17 @@ describe("getModelMaxOutputTokens", () => { format: "openai", }) - expect(result).toBe(10_000) + expect(result).toBe(GPT5_MAX_OUTPUT_TOKENS) }) - test("should respect user override for GPT-5 models but cap at 10k", () => { + test("should respect user override for GPT-5 models but cap at GPT5_MAX_OUTPUT_TOKENS", () => { const gpt5Model: ModelInfo = { contextWindow: 400_000, maxTokens: 128_000, supportsPromptCache: true, } - // User tries to set 15k, should be capped at 10k + // User tries to set 15k, should be capped at GPT5_MAX_OUTPUT_TOKENS const settings: ProviderSettings = { modelMaxTokens: 15_000, } @@ -289,10 +294,10 @@ describe("getModelMaxOutputTokens", () => { format: "openai", }) - expect(result).toBe(10_000) + expect(result).toBe(GPT5_MAX_OUTPUT_TOKENS) }) - test("should allow user to set lower than 10k for GPT-5 models", () => { + test("should allow user to set lower than GPT5_MAX_OUTPUT_TOKENS for GPT-5 models", () => { const gpt5Model: ModelInfo = { contextWindow: 400_000, maxTokens: 128_000, @@ -331,6 +336,54 @@ describe("getModelMaxOutputTokens", () => { // Should use model's maxTokens since it's within 20% of context window expect(result).toBe(16_384) }) + + test("should handle GPT-5 models with date suffixes", () => { + const gpt5Model: ModelInfo = { + contextWindow: 400_000, + maxTokens: 128_000, + supportsPromptCache: true, + } + + // Test various date-suffixed GPT-5 models + const modelIds = ["gpt-5-2025-08-07", "gpt-5-mini-2025-08-07", "gpt-5-nano-2025-08-07"] + + modelIds.forEach((modelId) => { + const result = getModelMaxOutputTokens({ + modelId, + model: gpt5Model, + settings: {}, + format: "openai", + }) + expect(result).toBe(GPT5_MAX_OUTPUT_TOKENS) + }) + }) + + test("should not match invalid GPT-5 model names", () => { + const model: ModelInfo = { + contextWindow: 128_000, + maxTokens: 16_384, + supportsPromptCache: true, + } + + // These should NOT be treated as GPT-5 models + const invalidModelIds = [ + "gpt-5-turbo", // Invalid variant + "gpt-50", // Different number + "gpt-5-", // Incomplete + "gpt-5-mini-turbo", // Invalid variant combination + ] + + invalidModelIds.forEach((modelId) => { + const result = getModelMaxOutputTokens({ + modelId, + model, + settings: {}, + format: "openai", + }) + // Should use model's maxTokens since it's within 20% of context window + expect(result).toBe(16_384) + }) + }) }) }) diff --git a/src/shared/api.ts b/src/shared/api.ts index 03f2901fdd..124ae27299 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -69,6 +69,15 @@ export const DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS = 16_384 export const DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS = 8_192 export const GEMINI_25_PRO_MIN_THINKING_TOKENS = 128 +// GPT-5 specific constants +/** + * Maximum output tokens for GPT-5 models to prevent context window overflow. + * When input approaches the 272k limit, the model's 128k max output can exceed + * the total 400k context window, causing API errors. + * @see https://github.com/cline/cline/issues/5474#issuecomment-3172109387 + */ +export const GPT5_MAX_OUTPUT_TOKENS = 10_000 + // Max Tokens export const getModelMaxOutputTokens = ({ @@ -88,14 +97,15 @@ export const getModelMaxOutputTokens = ({ } // Special handling for GPT-5 models to prevent context window overflow - // Limit max output to 10k tokens as per https://github.com/cline/cline/issues/5474#issuecomment-3172109387 - if (modelId.startsWith("gpt-5")) { - // Allow user override via settings, but cap at 10k + // GPT-5 models include: gpt-5, gpt-5-mini, gpt-5-nano, and dated variants + const isGpt5Model = /^gpt-5(-mini|-nano)?(-\d{4}-\d{2}-\d{2})?$/i.test(modelId) + if (isGpt5Model) { + // Allow user override via settings, but cap at GPT5_MAX_OUTPUT_TOKENS const userMaxTokens = settings?.modelMaxTokens if (userMaxTokens) { - return Math.min(userMaxTokens, 10000) + return Math.min(userMaxTokens, GPT5_MAX_OUTPUT_TOKENS) } - return 10000 + return GPT5_MAX_OUTPUT_TOKENS } if (shouldUseReasoningBudget({ model, settings })) {