|
|
@@ -1,164 +1,97 @@
|
|
|
-// npx vitest run src/shared/__tests__/api.spec.ts
|
|
|
-
|
|
|
-import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
|
|
|
-
|
|
|
+import { describe, test, expect } from "vitest"
|
|
|
import { getModelMaxOutputTokens, shouldUseReasoningBudget, shouldUseReasoningEffort } 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"
|
|
|
|
|
|
-describe("getMaxTokensForModel", () => {
|
|
|
- const modelId = "test"
|
|
|
-
|
|
|
- /**
|
|
|
- * Testing the specific fix in commit cc79178f:
|
|
|
- * For thinking models, use apiConfig.modelMaxTokens if available,
|
|
|
- * otherwise fall back to 8192 (not modelInfo.maxTokens)
|
|
|
- */
|
|
|
-
|
|
|
- it("should return apiConfig.modelMaxTokens for thinking models when provided", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- requiredReasoningBudget: true,
|
|
|
- maxTokens: 8000,
|
|
|
- }
|
|
|
+describe("getModelMaxOutputTokens", () => {
|
|
|
+ const mockModel: ModelInfo = {
|
|
|
+ maxTokens: 8192,
|
|
|
+ contextWindow: 200000,
|
|
|
+ supportsPromptCache: true,
|
|
|
+ }
|
|
|
|
|
|
+ test("should return claudeCodeMaxOutputTokens when using claude-code provider", () => {
|
|
|
const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4000,
|
|
|
- }
|
|
|
-
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(4000)
|
|
|
- })
|
|
|
-
|
|
|
- it("should return 16_384 for thinking models when modelMaxTokens not provided", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- requiredReasoningBudget: true,
|
|
|
- maxTokens: 8000,
|
|
|
+ apiProvider: "claude-code",
|
|
|
+ claudeCodeMaxOutputTokens: 16384,
|
|
|
}
|
|
|
|
|
|
- const settings = {}
|
|
|
-
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(16_384)
|
|
|
- })
|
|
|
-
|
|
|
- it("should return 16_384 for thinking models when apiConfig is undefined", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- requiredReasoningBudget: true,
|
|
|
- maxTokens: 8000,
|
|
|
- }
|
|
|
+ const result = getModelMaxOutputTokens({
|
|
|
+ modelId: "claude-3-5-sonnet-20241022",
|
|
|
+ model: mockModel,
|
|
|
+ settings,
|
|
|
+ })
|
|
|
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model, settings: undefined })).toBe(16_384)
|
|
|
+ expect(result).toBe(16384)
|
|
|
})
|
|
|
|
|
|
- it("should return modelInfo.maxTokens for non-thinking models", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 8000,
|
|
|
- }
|
|
|
-
|
|
|
+ test("should return model maxTokens when not using claude-code provider", () => {
|
|
|
const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4000,
|
|
|
+ apiProvider: "anthropic",
|
|
|
}
|
|
|
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(8000)
|
|
|
- })
|
|
|
-
|
|
|
- it("should return 20% of context window for non-thinking models with undefined maxTokens", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- }
|
|
|
-
|
|
|
- const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4000,
|
|
|
- }
|
|
|
+ const result = getModelMaxOutputTokens({
|
|
|
+ modelId: "claude-3-5-sonnet-20241022",
|
|
|
+ model: mockModel,
|
|
|
+ settings,
|
|
|
+ })
|
|
|
|
|
|
- // Should return 20% of context window when maxTokens is undefined
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(40000)
|
|
|
+ expect(result).toBe(8192)
|
|
|
})
|
|
|
|
|
|
- test("should return maxTokens from modelInfo when thinking is false", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 2048,
|
|
|
- }
|
|
|
-
|
|
|
+ test("should return default CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS when claude-code provider has no custom max tokens", () => {
|
|
|
const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4096,
|
|
|
+ apiProvider: "claude-code",
|
|
|
+ // No claudeCodeMaxOutputTokens set
|
|
|
}
|
|
|
|
|
|
- const result = getModelMaxOutputTokens({ modelId, model, settings })
|
|
|
- expect(result).toBe(2048)
|
|
|
+ const result = getModelMaxOutputTokens({
|
|
|
+ modelId: "claude-3-5-sonnet-20241022",
|
|
|
+ model: mockModel,
|
|
|
+ settings,
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(result).toBe(CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS)
|
|
|
})
|
|
|
|
|
|
- test("should return modelMaxTokens from apiConfig when thinking is true", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 2048,
|
|
|
+ test("should handle reasoning budget models correctly", () => {
|
|
|
+ const reasoningModel: ModelInfo = {
|
|
|
+ ...mockModel,
|
|
|
+ supportsReasoningBudget: true,
|
|
|
requiredReasoningBudget: true,
|
|
|
}
|
|
|
|
|
|
const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4096,
|
|
|
- }
|
|
|
-
|
|
|
- const result = getModelMaxOutputTokens({ modelId, model, settings })
|
|
|
- expect(result).toBe(4096)
|
|
|
- })
|
|
|
-
|
|
|
- test("should fallback to DEFAULT_THINKING_MODEL_MAX_TOKENS when thinking is true but apiConfig.modelMaxTokens is not defined", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 2048,
|
|
|
- requiredReasoningBudget: true,
|
|
|
+ apiProvider: "anthropic",
|
|
|
+ enableReasoningEffort: true,
|
|
|
+ modelMaxTokens: 32000,
|
|
|
}
|
|
|
|
|
|
- const settings: ProviderSettings = {}
|
|
|
-
|
|
|
- const result = getModelMaxOutputTokens({ modelId, model, settings: undefined })
|
|
|
- expect(result).toBe(16_384)
|
|
|
- })
|
|
|
-
|
|
|
- test("should handle undefined inputs gracefully", () => {
|
|
|
- const modelInfoOnly: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 2048,
|
|
|
- }
|
|
|
+ const result = getModelMaxOutputTokens({
|
|
|
+ modelId: "claude-3-7-sonnet-20250219",
|
|
|
+ model: reasoningModel,
|
|
|
+ settings,
|
|
|
+ })
|
|
|
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model: modelInfoOnly, settings: undefined })).toBe(2048)
|
|
|
+ expect(result).toBe(32000)
|
|
|
})
|
|
|
|
|
|
- test("should handle missing properties gracefully", () => {
|
|
|
- const modelInfoWithoutMaxTokens: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
+ test("should return 20% of context window when maxTokens is undefined", () => {
|
|
|
+ const modelWithoutMaxTokens: ModelInfo = {
|
|
|
+ contextWindow: 100000,
|
|
|
supportsPromptCache: true,
|
|
|
- requiredReasoningBudget: true,
|
|
|
}
|
|
|
|
|
|
- const settings: ProviderSettings = {
|
|
|
- modelMaxTokens: 4096,
|
|
|
- }
|
|
|
-
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model: modelInfoWithoutMaxTokens, settings })).toBe(4096)
|
|
|
-
|
|
|
- const modelInfoWithoutThinking: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- maxTokens: 2048,
|
|
|
- }
|
|
|
+ const result = getModelMaxOutputTokens({
|
|
|
+ modelId: "some-model",
|
|
|
+ model: modelWithoutMaxTokens,
|
|
|
+ settings: {},
|
|
|
+ })
|
|
|
|
|
|
- expect(getModelMaxOutputTokens({ modelId, model: modelInfoWithoutThinking, settings: undefined })).toBe(2048)
|
|
|
+ expect(result).toBe(20000) // 20% of 100000
|
|
|
})
|
|
|
|
|
|
test("should return ANTHROPIC_DEFAULT_MAX_TOKENS for Anthropic models that support reasoning budget but aren't using it", () => {
|
|
|
- // Test case for models that support reasoning budget but enableReasoningEffort is false
|
|
|
const anthropicModelId = "claude-sonnet-4-20250514"
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
@@ -168,6 +101,7 @@ describe("getMaxTokensForModel", () => {
|
|
|
}
|
|
|
|
|
|
const settings: ProviderSettings = {
|
|
|
+ apiProvider: "anthropic",
|
|
|
enableReasoningEffort: false, // Not using reasoning
|
|
|
}
|
|
|
|
|
|
@@ -176,7 +110,6 @@ describe("getMaxTokensForModel", () => {
|
|
|
})
|
|
|
|
|
|
test("should return model.maxTokens for non-Anthropic models that support reasoning budget but aren't using it", () => {
|
|
|
- // Test case for non-Anthropic models - should still use model.maxTokens
|
|
|
const geminiModelId = "gemini-2.5-flash-preview-04-17"
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 1_048_576,
|
|
|
@@ -186,16 +119,45 @@ describe("getMaxTokensForModel", () => {
|
|
|
}
|
|
|
|
|
|
const settings: ProviderSettings = {
|
|
|
+ apiProvider: "gemini",
|
|
|
enableReasoningEffort: false, // Not using reasoning
|
|
|
}
|
|
|
|
|
|
const result = getModelMaxOutputTokens({ modelId: geminiModelId, model, settings })
|
|
|
expect(result).toBe(65_535) // Should use model.maxTokens, not ANTHROPIC_DEFAULT_MAX_TOKENS
|
|
|
})
|
|
|
+
|
|
|
+ test("should return modelMaxTokens from settings when reasoning budget is required", () => {
|
|
|
+ const model: ModelInfo = {
|
|
|
+ contextWindow: 200_000,
|
|
|
+ supportsPromptCache: true,
|
|
|
+ requiredReasoningBudget: true,
|
|
|
+ maxTokens: 8000,
|
|
|
+ }
|
|
|
+
|
|
|
+ const settings: ProviderSettings = {
|
|
|
+ modelMaxTokens: 4000,
|
|
|
+ }
|
|
|
+
|
|
|
+ expect(getModelMaxOutputTokens({ modelId: "test", model, settings })).toBe(4000)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("should return default 16_384 for reasoning budget models when modelMaxTokens not provided", () => {
|
|
|
+ const model: ModelInfo = {
|
|
|
+ contextWindow: 200_000,
|
|
|
+ supportsPromptCache: true,
|
|
|
+ requiredReasoningBudget: true,
|
|
|
+ maxTokens: 8000,
|
|
|
+ }
|
|
|
+
|
|
|
+ const settings = {}
|
|
|
+
|
|
|
+ expect(getModelMaxOutputTokens({ modelId: "test", model, settings })).toBe(16_384)
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe("shouldUseReasoningBudget", () => {
|
|
|
- it("should return true when model has requiredReasoningBudget", () => {
|
|
|
+ test("should return true when model has requiredReasoningBudget", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -208,7 +170,7 @@ describe("shouldUseReasoningBudget", () => {
|
|
|
expect(shouldUseReasoningBudget({ model, settings: { enableReasoningEffort: false } })).toBe(true)
|
|
|
})
|
|
|
|
|
|
- it("should return true when model supports reasoning budget and settings enable reasoning effort", () => {
|
|
|
+ test("should return true when model supports reasoning budget and settings enable reasoning effort", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -222,7 +184,7 @@ describe("shouldUseReasoningBudget", () => {
|
|
|
expect(shouldUseReasoningBudget({ model, settings })).toBe(true)
|
|
|
})
|
|
|
|
|
|
- it("should return false when model supports reasoning budget but settings don't enable reasoning effort", () => {
|
|
|
+ test("should return false when model supports reasoning budget but settings don't enable reasoning effort", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -238,7 +200,7 @@ describe("shouldUseReasoningBudget", () => {
|
|
|
expect(shouldUseReasoningBudget({ model })).toBe(false)
|
|
|
})
|
|
|
|
|
|
- it("should return false when model doesn't support reasoning budget", () => {
|
|
|
+ test("should return false when model doesn't support reasoning budget", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -251,27 +213,10 @@ describe("shouldUseReasoningBudget", () => {
|
|
|
expect(shouldUseReasoningBudget({ model, settings })).toBe(false)
|
|
|
expect(shouldUseReasoningBudget({ model })).toBe(false)
|
|
|
})
|
|
|
-
|
|
|
- it("should handle undefined settings gracefully", () => {
|
|
|
- const modelWithRequired: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- requiredReasoningBudget: true,
|
|
|
- }
|
|
|
-
|
|
|
- const modelWithSupported: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- supportsReasoningBudget: true,
|
|
|
- }
|
|
|
-
|
|
|
- expect(shouldUseReasoningBudget({ model: modelWithRequired, settings: undefined })).toBe(true)
|
|
|
- expect(shouldUseReasoningBudget({ model: modelWithSupported, settings: undefined })).toBe(false)
|
|
|
- })
|
|
|
})
|
|
|
|
|
|
describe("shouldUseReasoningEffort", () => {
|
|
|
- it("should return true when model has reasoningEffort property", () => {
|
|
|
+ test("should return true when model has reasoningEffort property", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -284,7 +229,7 @@ describe("shouldUseReasoningEffort", () => {
|
|
|
expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: undefined } })).toBe(true)
|
|
|
})
|
|
|
|
|
|
- it("should return true when model supports reasoning effort and settings provide reasoning effort", () => {
|
|
|
+ test("should return true when model supports reasoning effort and settings provide reasoning effort", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -298,7 +243,7 @@ describe("shouldUseReasoningEffort", () => {
|
|
|
expect(shouldUseReasoningEffort({ model, settings })).toBe(true)
|
|
|
})
|
|
|
|
|
|
- it("should return false when model supports reasoning effort but settings don't provide reasoning effort", () => {
|
|
|
+ test("should return false when model supports reasoning effort but settings don't provide reasoning effort", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -314,7 +259,7 @@ describe("shouldUseReasoningEffort", () => {
|
|
|
expect(shouldUseReasoningEffort({ model })).toBe(false)
|
|
|
})
|
|
|
|
|
|
- it("should return false when model doesn't support reasoning effort", () => {
|
|
|
+ test("should return false when model doesn't support reasoning effort", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -328,7 +273,7 @@ describe("shouldUseReasoningEffort", () => {
|
|
|
expect(shouldUseReasoningEffort({ model })).toBe(false)
|
|
|
})
|
|
|
|
|
|
- it("should handle different reasoning effort values", () => {
|
|
|
+ test("should handle different reasoning effort values", () => {
|
|
|
const model: ModelInfo = {
|
|
|
contextWindow: 200_000,
|
|
|
supportsPromptCache: true,
|
|
|
@@ -343,37 +288,4 @@ describe("shouldUseReasoningEffort", () => {
|
|
|
expect(shouldUseReasoningEffort({ model, settings: settingsMedium })).toBe(true)
|
|
|
expect(shouldUseReasoningEffort({ model, settings: settingsHigh })).toBe(true)
|
|
|
})
|
|
|
-
|
|
|
- it("should handle undefined settings gracefully", () => {
|
|
|
- const modelWithReasoning: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- reasoningEffort: "medium",
|
|
|
- }
|
|
|
-
|
|
|
- const modelWithSupported: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- supportsReasoningEffort: true,
|
|
|
- }
|
|
|
-
|
|
|
- expect(shouldUseReasoningEffort({ model: modelWithReasoning, settings: undefined })).toBe(true)
|
|
|
- expect(shouldUseReasoningEffort({ model: modelWithSupported, settings: undefined })).toBe(false)
|
|
|
- })
|
|
|
-
|
|
|
- it("should prioritize model reasoningEffort over settings", () => {
|
|
|
- const model: ModelInfo = {
|
|
|
- contextWindow: 200_000,
|
|
|
- supportsPromptCache: true,
|
|
|
- supportsReasoningEffort: true,
|
|
|
- reasoningEffort: "low",
|
|
|
- }
|
|
|
-
|
|
|
- const settings: ProviderSettings = {
|
|
|
- reasoningEffort: "high",
|
|
|
- }
|
|
|
-
|
|
|
- // Should return true because model.reasoningEffort exists, regardless of settings
|
|
|
- expect(shouldUseReasoningEffort({ model, settings })).toBe(true)
|
|
|
- })
|
|
|
})
|