Roo-Code/src/shared/__tests__/api.spec.ts
Roo Code 67b5d3448b feat: ROO-434 - Remove Claude Code provider
- Remove Claude Code integration files (oauth.ts, streaming-client.ts, tests)
- Remove Claude Code API provider handler and tests
- Remove Claude Code UI components
- Remove Claude Code types and models
- Update provider lists and configuration
- Remove Claude Code message handlers from webview
- Remove Claude Code initialization from extension
- Update UI hooks and settings to remove Claude Code support
- Remove Claude Code authentication state tracking
- Remove legacy migration code for Claude Code settings
- Update test files to use qwen-code instead of claude-code
2026-01-09 17:34:11 +00:00

481 lines
14 KiB
TypeScript

import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
import { getModelMaxOutputTokens, shouldUseReasoningBudget, shouldUseReasoningEffort } from "../api"
describe("getModelMaxOutputTokens", () => {
const mockModel: ModelInfo = {
maxTokens: 8192,
contextWindow: 200000,
supportsPromptCache: true,
}
test("should return model maxTokens when maxTokens is within 20% of context window", () => {
const settings: ProviderSettings = {
apiProvider: "anthropic",
}
// mockModel has maxTokens: 8192 and contextWindow: 200000
// 8192 is 4.096% of 200000, which is <= 20%, so it should use model.maxTokens
const result = getModelMaxOutputTokens({
modelId: "claude-3-5-sonnet-20241022",
model: mockModel,
settings,
})
expect(result).toBe(8192)
})
test("should handle reasoning budget models correctly", () => {
const reasoningModel: ModelInfo = {
...mockModel,
supportsReasoningBudget: true,
requiredReasoningBudget: true,
}
const settings: ProviderSettings = {
apiProvider: "anthropic",
enableReasoningEffort: true,
modelMaxTokens: 32000,
}
const result = getModelMaxOutputTokens({
modelId: "claude-3-7-sonnet-20250219",
model: reasoningModel,
settings,
})
expect(result).toBe(32000)
})
test("should return default of 8192 when maxTokens is undefined", () => {
const modelWithoutMaxTokens: ModelInfo = {
contextWindow: 100000,
supportsPromptCache: true,
}
const result = getModelMaxOutputTokens({
modelId: "some-model",
model: modelWithoutMaxTokens,
settings: {},
})
expect(result).toBe(8192)
})
test("should return ANTHROPIC_DEFAULT_MAX_TOKENS for Anthropic models that support reasoning budget but aren't using it", () => {
const anthropicModelId = "claude-sonnet-4-20250514"
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningBudget: true,
maxTokens: 64_000, // This should be ignored
}
const settings: ProviderSettings = {
apiProvider: "anthropic",
enableReasoningEffort: false, // Not using reasoning
}
const result = getModelMaxOutputTokens({ modelId: anthropicModelId, model, settings })
expect(result).toBe(ANTHROPIC_DEFAULT_MAX_TOKENS) // Should be 8192, not 64_000
})
test("should return model.maxTokens for non-Anthropic models that support reasoning budget but aren't using it", () => {
const geminiModelId = "gemini-2.5-flash-preview-04-17"
const model: ModelInfo = {
contextWindow: 1_048_576,
supportsPromptCache: false,
supportsReasoningBudget: true,
maxTokens: 65_535, // 65_535 is ~6.25% of 1_048_576, which is <= 20%
}
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 since it's within 20% threshold
})
test("should clamp maxTokens to 20% of context window when maxTokens exceeds threshold", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: false,
maxTokens: 50_000, // 50% of context window, exceeds 20% threshold
}
const settings: ProviderSettings = {
apiProvider: "openai",
}
const result = getModelMaxOutputTokens({
modelId: "gpt-4",
model,
settings,
format: "openai",
})
// Should clamp to 20% of context window: 100_000 * 0.2 = 20_000
expect(result).toBe(20_000)
})
test("should clamp maxTokens to 20% of context window for Anthropic models when maxTokens exceeds threshold", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: true,
maxTokens: 50_000, // 50% of context window, exceeds 20% threshold
}
const settings: ProviderSettings = {
apiProvider: "anthropic",
}
const result = getModelMaxOutputTokens({
modelId: "claude-3-5-sonnet-20241022",
model,
settings,
})
// Should clamp to 20% of context window: 100_000 * 0.2 = 20_000
expect(result).toBe(20_000)
})
test("should use model.maxTokens when exactly at 20% threshold", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: false,
maxTokens: 20_000, // Exactly 20% of context window
}
const settings: ProviderSettings = {
apiProvider: "openai",
}
const result = getModelMaxOutputTokens({
modelId: "gpt-4",
model,
settings,
format: "openai",
})
expect(result).toBe(20_000) // Should use model.maxTokens since it's exactly at 20%
})
test("should bypass 20% cap for GPT-5 models and use exact configured max tokens", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: false,
maxTokens: 128_000, // 64% of context window, normally would be capped
}
const settings: ProviderSettings = {
apiProvider: "openai",
}
// Test various GPT-5 model IDs
const gpt5ModelIds = ["gpt-5", "gpt-5-turbo", "GPT-5", "openai/gpt-5-preview", "gpt-5-32k", "GPT-5-TURBO"]
gpt5ModelIds.forEach((modelId) => {
const result = getModelMaxOutputTokens({
modelId,
model,
settings,
format: "openai",
})
// Should use full 128k tokens, not capped to 20% (40k)
expect(result).toBe(128_000)
})
})
test("should still apply 20% cap to non-GPT-5 models", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: false,
maxTokens: 128_000, // 64% of context window, should be capped
}
const settings: ProviderSettings = {
apiProvider: "openai",
}
// Test non-GPT-5 model IDs
const nonGpt5ModelIds = ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo", "claude-3-5-sonnet", "gemini-pro"]
nonGpt5ModelIds.forEach((modelId) => {
const result = getModelMaxOutputTokens({
modelId,
model,
settings,
format: "openai",
})
// Should be capped to 20% of context window: 200_000 * 0.2 = 40_000
expect(result).toBe(40_000)
})
})
test("should handle GPT-5 models with various max token configurations", () => {
const testCases = [
{
maxTokens: 128_000,
contextWindow: 200_000,
expected: 128_000, // Uses full 128k
},
{
maxTokens: 64_000,
contextWindow: 200_000,
expected: 64_000, // Uses configured 64k
},
{
maxTokens: 256_000,
contextWindow: 400_000,
expected: 256_000, // Uses full 256k even though it's 64% of context
},
]
testCases.forEach(({ maxTokens, contextWindow, expected }) => {
const model: ModelInfo = {
contextWindow,
supportsPromptCache: false,
maxTokens,
}
const result = getModelMaxOutputTokens({
modelId: "gpt-5-turbo",
model,
settings: { apiProvider: "openai" },
format: "openai",
})
expect(result).toBe(expected)
})
})
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", () => {
test("should return true when model has requiredReasoningBudget", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
requiredReasoningBudget: true,
}
// Should return true regardless of settings
expect(shouldUseReasoningBudget({ model })).toBe(true)
expect(shouldUseReasoningBudget({ model, settings: {} })).toBe(true)
expect(shouldUseReasoningBudget({ model, settings: { enableReasoningEffort: false } })).toBe(true)
})
test("should return true when model supports reasoning budget and settings enable reasoning effort", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningBudget: true,
}
const settings: ProviderSettings = {
enableReasoningEffort: true,
}
expect(shouldUseReasoningBudget({ model, settings })).toBe(true)
})
test("should return false when model supports reasoning budget but settings don't enable reasoning effort", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningBudget: true,
}
const settings: ProviderSettings = {
enableReasoningEffort: false,
}
expect(shouldUseReasoningBudget({ model, settings })).toBe(false)
expect(shouldUseReasoningBudget({ model, settings: {} })).toBe(false)
expect(shouldUseReasoningBudget({ model })).toBe(false)
})
test("should return false when model doesn't support reasoning budget", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
}
const settings: ProviderSettings = {
enableReasoningEffort: true,
}
expect(shouldUseReasoningBudget({ model, settings })).toBe(false)
expect(shouldUseReasoningBudget({ model })).toBe(false)
})
})
describe("shouldUseReasoningEffort", () => {
test("should return true when model has reasoningEffort property", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
reasoningEffort: "medium",
}
expect(shouldUseReasoningEffort({ model })).toBe(true)
expect(shouldUseReasoningEffort({ model, settings: {} })).toBe(true)
expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: undefined } })).toBe(true)
})
test("should return false when enableReasoningEffort is false, even if reasoningEffort is set", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningEffort: true,
}
const settings: ProviderSettings = {
enableReasoningEffort: false,
reasoningEffort: "medium",
}
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
})
test("should return false when enableReasoningEffort is false, even if model has reasoningEffort property", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
reasoningEffort: "medium",
}
const settings: ProviderSettings = {
enableReasoningEffort: false,
}
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
})
test("should return true when model supports reasoning effort and settings provide reasoning effort", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningEffort: true,
}
const settings: ProviderSettings = {
reasoningEffort: "high",
}
expect(shouldUseReasoningEffort({ model, settings })).toBe(true)
})
test("should return false when model supports reasoning effort but settings don't provide reasoning effort", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningEffort: true,
}
const settings: ProviderSettings = {
reasoningEffort: undefined,
}
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
expect(shouldUseReasoningEffort({ model, settings: {} })).toBe(false)
expect(shouldUseReasoningEffort({ model })).toBe(false)
})
test("should return false when model doesn't support reasoning effort and has no default", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
}
const settings: ProviderSettings = {
reasoningEffort: "high",
}
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
expect(shouldUseReasoningEffort({ model })).toBe(false)
})
test("should handle different reasoning effort values", () => {
const model: ModelInfo = {
contextWindow: 200_000,
supportsPromptCache: true,
supportsReasoningEffort: true,
}
const settingsLow: ProviderSettings = { reasoningEffort: "low" }
const settingsMedium: ProviderSettings = { reasoningEffort: "medium" }
const settingsHigh: ProviderSettings = { reasoningEffort: "high" }
expect(shouldUseReasoningEffort({ model, settings: settingsLow })).toBe(true)
expect(shouldUseReasoningEffort({ model, settings: settingsMedium })).toBe(true)
expect(shouldUseReasoningEffort({ model, settings: settingsHigh })).toBe(true)
})
// New cases for extended capability surface
test("array capability includes 'disable' with selection 'disable' -> false", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "medium", "high"] as unknown as any,
}
const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "disable" as any }
expect(shouldUseReasoningEffort({ model, settings })).toBe(false)
})
test("array capability includes 'none' with settings.reasoningEffort='none' -> true", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: true,
supportsReasoningEffort: ["none", "minimal", "low", "medium", "high"] as unknown as any,
}
const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "none" as any }
expect(shouldUseReasoningEffort({ model, settings })).toBe(true)
})
test("array capability includes 'minimal' with settings.reasoningEffort='minimal' -> true", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: true,
supportsReasoningEffort: ["none", "minimal", "low", "medium", "high"] as unknown as any,
}
const settings: ProviderSettings = { enableReasoningEffort: true, reasoningEffort: "minimal" as any }
expect(shouldUseReasoningEffort({ model, settings })).toBe(true)
})
test("boolean true with 'none' and 'minimal' -> true", () => {
const model: ModelInfo = {
contextWindow: 100_000,
supportsPromptCache: true,
supportsReasoningEffort: true,
}
expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: "none" as any } })).toBe(true)
expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: "minimal" as any } })).toBe(true)
})
})