mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat(claude-code): add configurable max output tokens setting (#5610)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com> Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
9aa2e6ea54
commit
f5dfbf1fc2
26 changed files with 305 additions and 232 deletions
|
|
@ -80,6 +80,7 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
|
|||
|
||||
const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
|
||||
claudeCodePath: z.string().optional(),
|
||||
claudeCodeMaxOutputTokens: z.number().int().min(1).max(200000).optional(),
|
||||
})
|
||||
|
||||
const glamaSchema = baseProviderSettingsSchema.extend({
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { anthropicModels } from "./anthropic.js"
|
|||
// Claude Code
|
||||
export type ClaudeCodeModelId = keyof typeof claudeCodeModels
|
||||
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-4-20250514"
|
||||
export const CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS = 8000
|
||||
export const claudeCodeModels = {
|
||||
"claude-sonnet-4-20250514": {
|
||||
...anthropicModels["claude-sonnet-4-20250514"],
|
||||
|
|
|
|||
|
|
@ -48,6 +48,32 @@ describe("ClaudeCodeHandler", () => {
|
|||
expect(model.id).toBe("claude-sonnet-4-20250514") // default model
|
||||
})
|
||||
|
||||
test("should override maxTokens when claudeCodeMaxOutputTokens is provided", () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
claudeCodePath: "claude",
|
||||
apiModelId: "claude-sonnet-4-20250514",
|
||||
claudeCodeMaxOutputTokens: 8000,
|
||||
}
|
||||
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
|
||||
const model = handlerWithMaxTokens.getModel()
|
||||
|
||||
expect(model.id).toBe("claude-sonnet-4-20250514")
|
||||
expect(model.info.maxTokens).toBe(8000) // Should use the configured value, not the default 64000
|
||||
})
|
||||
|
||||
test("should override maxTokens for default model when claudeCodeMaxOutputTokens is provided", () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
claudeCodePath: "claude",
|
||||
apiModelId: "invalid-model", // Will fall back to default
|
||||
claudeCodeMaxOutputTokens: 16384,
|
||||
}
|
||||
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
|
||||
const model = handlerWithMaxTokens.getModel()
|
||||
|
||||
expect(model.id).toBe("claude-sonnet-4-20250514") // default model
|
||||
expect(model.info.maxTokens).toBe(16384) // Should use the configured value
|
||||
})
|
||||
|
||||
test("should filter messages and call runClaudeCode", async () => {
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
|
@ -76,6 +102,43 @@ describe("ClaudeCodeHandler", () => {
|
|||
messages: filteredMessages,
|
||||
path: "claude",
|
||||
modelId: "claude-3-5-sonnet-20241022",
|
||||
maxOutputTokens: undefined, // No maxOutputTokens configured in this test
|
||||
})
|
||||
})
|
||||
|
||||
test("should pass maxOutputTokens to runClaudeCode when configured", async () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
claudeCodePath: "claude",
|
||||
apiModelId: "claude-3-5-sonnet-20241022",
|
||||
claudeCodeMaxOutputTokens: 16384,
|
||||
}
|
||||
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
const filteredMessages = [{ role: "user" as const, content: "Hello (filtered)" }]
|
||||
|
||||
mockFilterMessages.mockReturnValue(filteredMessages)
|
||||
|
||||
// Mock empty async generator
|
||||
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
|
||||
// Empty generator for basic test
|
||||
}
|
||||
mockRunClaudeCode.mockReturnValue(mockGenerator())
|
||||
|
||||
const stream = handlerWithMaxTokens.createMessage(systemPrompt, messages)
|
||||
|
||||
// Need to start iterating to trigger the call
|
||||
const iterator = stream[Symbol.asyncIterator]()
|
||||
await iterator.next()
|
||||
|
||||
// Verify runClaudeCode was called with maxOutputTokens
|
||||
expect(mockRunClaudeCode).toHaveBeenCalledWith({
|
||||
systemPrompt,
|
||||
messages: filteredMessages,
|
||||
path: "claude",
|
||||
modelId: "claude-3-5-sonnet-20241022",
|
||||
maxOutputTokens: 16384,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { claudeCodeDefaultModelId, type ClaudeCodeModelId, claudeCodeModels } from "@roo-code/types"
|
||||
import { claudeCodeDefaultModelId, type ClaudeCodeModelId, claudeCodeModels, type ModelInfo } from "@roo-code/types"
|
||||
import { type ApiHandler } from ".."
|
||||
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
|
||||
import { runClaudeCode } from "../../integrations/claude-code/run"
|
||||
|
|
@ -25,6 +25,7 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
|
|||
messages: filteredMessages,
|
||||
path: this.options.claudeCodePath,
|
||||
modelId: this.getModel().id,
|
||||
maxOutputTokens: this.options.claudeCodeMaxOutputTokens,
|
||||
})
|
||||
|
||||
// Usage is included with assistant messages,
|
||||
|
|
@ -129,12 +130,26 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
|
|||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in claudeCodeModels) {
|
||||
const id = modelId as ClaudeCodeModelId
|
||||
return { id, info: claudeCodeModels[id] }
|
||||
const modelInfo: ModelInfo = { ...claudeCodeModels[id] }
|
||||
|
||||
// Override maxTokens with the configured value if provided
|
||||
if (this.options.claudeCodeMaxOutputTokens !== undefined) {
|
||||
modelInfo.maxTokens = this.options.claudeCodeMaxOutputTokens
|
||||
}
|
||||
|
||||
return { id, info: modelInfo }
|
||||
}
|
||||
|
||||
const defaultModelInfo: ModelInfo = { ...claudeCodeModels[claudeCodeDefaultModelId] }
|
||||
|
||||
// Override maxTokens with the configured value if provided
|
||||
if (this.options.claudeCodeMaxOutputTokens !== undefined) {
|
||||
defaultModelInfo.maxTokens = this.options.claudeCodeMaxOutputTokens
|
||||
}
|
||||
|
||||
return {
|
||||
id: claudeCodeDefaultModelId,
|
||||
info: claudeCodeModels[claudeCodeDefaultModelId],
|
||||
info: defaultModelInfo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type Anthropic from "@anthropic-ai/sdk"
|
|||
import { execa } from "execa"
|
||||
import { ClaudeCodeMessage } from "./types"
|
||||
import readline from "readline"
|
||||
import { CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS } from "@roo-code/types"
|
||||
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
|
||||
|
|
@ -20,7 +21,9 @@ type ProcessState = {
|
|||
exitCode: number | null
|
||||
}
|
||||
|
||||
export async function* runClaudeCode(options: ClaudeCodeOptions): AsyncGenerator<ClaudeCodeMessage | string> {
|
||||
export async function* runClaudeCode(
|
||||
options: ClaudeCodeOptions & { maxOutputTokens?: number },
|
||||
): AsyncGenerator<ClaudeCodeMessage | string> {
|
||||
const process = runProcess(options)
|
||||
|
||||
const rl = readline.createInterface({
|
||||
|
|
@ -107,7 +110,13 @@ const claudeCodeTools = [
|
|||
|
||||
const CLAUDE_CODE_TIMEOUT = 600000 // 10 minutes
|
||||
|
||||
function runProcess({ systemPrompt, messages, path, modelId }: ClaudeCodeOptions) {
|
||||
function runProcess({
|
||||
systemPrompt,
|
||||
messages,
|
||||
path,
|
||||
modelId,
|
||||
maxOutputTokens,
|
||||
}: ClaudeCodeOptions & { maxOutputTokens?: number }) {
|
||||
const claudePath = path || "claude"
|
||||
|
||||
const args = [
|
||||
|
|
@ -134,8 +143,11 @@ function runProcess({ systemPrompt, messages, path, modelId }: ClaudeCodeOptions
|
|||
stderr: "pipe",
|
||||
env: {
|
||||
...process.env,
|
||||
// The default is 32000. However, I've gotten larger responses, so we increase it unless the user specified it.
|
||||
CLAUDE_CODE_MAX_OUTPUT_TOKENS: process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS || "64000",
|
||||
// Use the configured value, or the environment variable, or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
|
||||
CLAUDE_CODE_MAX_OUTPUT_TOKENS:
|
||||
maxOutputTokens?.toString() ||
|
||||
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS ||
|
||||
CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS.toString(),
|
||||
},
|
||||
cwd,
|
||||
maxBuffer: 1024 * 1024 * 1000,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
apiProvider: "claude-code",
|
||||
claudeCodeMaxOutputTokens: 16384,
|
||||
}
|
||||
|
||||
expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(4000)
|
||||
const result = getModelMaxOutputTokens({
|
||||
modelId: "claude-3-5-sonnet-20241022",
|
||||
model: mockModel,
|
||||
settings,
|
||||
})
|
||||
|
||||
expect(result).toBe(16384)
|
||||
})
|
||||
|
||||
it("should return 16_384 for thinking models when modelMaxTokens not provided", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
supportsPromptCache: true,
|
||||
requiredReasoningBudget: true,
|
||||
maxTokens: 8000,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
expect(getModelMaxOutputTokens({ modelId, model, settings: undefined })).toBe(16_384)
|
||||
})
|
||||
|
||||
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)
|
||||
const result = getModelMaxOutputTokens({
|
||||
modelId: "claude-3-5-sonnet-20241022",
|
||||
model: mockModel,
|
||||
settings,
|
||||
})
|
||||
|
||||
expect(result).toBe(8192)
|
||||
})
|
||||
|
||||
it("should return 20% of context window for non-thinking models with undefined maxTokens", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
supportsPromptCache: true,
|
||||
}
|
||||
|
||||
test("should return default CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS when claude-code provider has no custom max tokens", () => {
|
||||
const settings: ProviderSettings = {
|
||||
modelMaxTokens: 4000,
|
||||
apiProvider: "claude-code",
|
||||
// No claudeCodeMaxOutputTokens set
|
||||
}
|
||||
|
||||
// Should return 20% of context window when maxTokens is undefined
|
||||
expect(getModelMaxOutputTokens({ modelId, model, settings })).toBe(40000)
|
||||
const result = getModelMaxOutputTokens({
|
||||
modelId: "claude-3-5-sonnet-20241022",
|
||||
model: mockModel,
|
||||
settings,
|
||||
})
|
||||
|
||||
expect(result).toBe(CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS)
|
||||
})
|
||||
|
||||
test("should return maxTokens from modelInfo when thinking is false", () => {
|
||||
const model: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
supportsPromptCache: true,
|
||||
maxTokens: 2048,
|
||||
}
|
||||
|
||||
const settings: ProviderSettings = {
|
||||
modelMaxTokens: 4096,
|
||||
}
|
||||
|
||||
const result = getModelMaxOutputTokens({ modelId, model, settings })
|
||||
expect(result).toBe(2048)
|
||||
})
|
||||
|
||||
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,
|
||||
apiProvider: "anthropic",
|
||||
enableReasoningEffort: true,
|
||||
modelMaxTokens: 32000,
|
||||
}
|
||||
|
||||
const result = getModelMaxOutputTokens({ modelId, model, settings })
|
||||
expect(result).toBe(4096)
|
||||
const result = getModelMaxOutputTokens({
|
||||
modelId: "claude-3-7-sonnet-20250219",
|
||||
model: reasoningModel,
|
||||
settings,
|
||||
})
|
||||
|
||||
expect(result).toBe(32000)
|
||||
})
|
||||
|
||||
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,
|
||||
test("should return 20% of context window when maxTokens is undefined", () => {
|
||||
const modelWithoutMaxTokens: ModelInfo = {
|
||||
contextWindow: 100000,
|
||||
supportsPromptCache: true,
|
||||
maxTokens: 2048,
|
||||
requiredReasoningBudget: true,
|
||||
}
|
||||
|
||||
const settings: ProviderSettings = {}
|
||||
const result = getModelMaxOutputTokens({
|
||||
modelId: "some-model",
|
||||
model: modelWithoutMaxTokens,
|
||||
settings: {},
|
||||
})
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
expect(getModelMaxOutputTokens({ modelId, model: modelInfoOnly, settings: undefined })).toBe(2048)
|
||||
})
|
||||
|
||||
test("should handle missing properties gracefully", () => {
|
||||
const modelInfoWithoutMaxTokens: ModelInfo = {
|
||||
contextWindow: 200_000,
|
||||
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,
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { type ModelInfo, type ProviderSettings, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
|
||||
import {
|
||||
type ModelInfo,
|
||||
type ProviderSettings,
|
||||
ANTHROPIC_DEFAULT_MAX_TOKENS,
|
||||
CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS,
|
||||
} from "@roo-code/types"
|
||||
|
||||
// ApiHandlerOptions
|
||||
|
||||
|
|
@ -58,6 +63,12 @@ export const getModelMaxOutputTokens = ({
|
|||
model: ModelInfo
|
||||
settings?: ProviderSettings
|
||||
}): number | undefined => {
|
||||
// Check for Claude Code specific max output tokens setting
|
||||
if (settings?.apiProvider === "claude-code") {
|
||||
// Return the configured value or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
|
||||
return settings.claudeCodeMaxOutputTokens || CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
|
||||
}
|
||||
|
||||
if (shouldUseReasoningBudget({ model, settings })) {
|
||||
return settings?.modelMaxTokens || DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react"
|
|||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { type ProviderSettings } from "@roo-code/types"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Slider } from "@src/components/ui"
|
||||
|
||||
interface ClaudeCodeProps {
|
||||
apiConfiguration: ProviderSettings
|
||||
|
|
@ -16,25 +17,46 @@ export const ClaudeCode: React.FC<ClaudeCodeProps> = ({ apiConfiguration, setApi
|
|||
setApiConfigurationField("claudeCodePath", element.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.claudeCodePath || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="text"
|
||||
onInput={handleInputChange}
|
||||
placeholder={t("settings:providers.claudeCode.placeholder")}>
|
||||
{t("settings:providers.claudeCode.pathLabel")}
|
||||
</VSCodeTextField>
|
||||
const maxOutputTokens = apiConfiguration?.claudeCodeMaxOutputTokens || 8000
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{t("settings:providers.claudeCode.description")}
|
||||
</p>
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.claudeCodePath || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
type="text"
|
||||
onInput={handleInputChange}
|
||||
placeholder={t("settings:providers.claudeCode.placeholder")}>
|
||||
{t("settings:providers.claudeCode.pathLabel")}
|
||||
</VSCodeTextField>
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: 3,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{t("settings:providers.claudeCode.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="font-medium">{t("settings:providers.claudeCode.maxTokensLabel")}</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Slider
|
||||
min={8000}
|
||||
max={64000}
|
||||
step={1024}
|
||||
value={[maxOutputTokens]}
|
||||
onValueChange={([value]) => setApiConfigurationField("claudeCodeMaxOutputTokens", value)}
|
||||
/>
|
||||
<div className="w-16 text-sm text-center">{maxOutputTokens}</div>
|
||||
</div>
|
||||
<p className="text-sm text-vscode-descriptionForeground mt-1">
|
||||
{t("settings:providers.claudeCode.maxTokensDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Ruta del Codi Claude",
|
||||
"description": "Ruta opcional al teu CLI de Claude Code. Per defecte, 'claude' si no s'estableix.",
|
||||
"placeholder": "Per defecte: claude"
|
||||
"placeholder": "Per defecte: claude",
|
||||
"maxTokensLabel": "Tokens màxims de sortida",
|
||||
"maxTokensDescription": "Nombre màxim de tokens de sortida per a les respostes de Claude Code. El valor per defecte és 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude-Code-Pfad",
|
||||
"description": "Optionaler Pfad zu Ihrer Claude Code CLI. Standard ist 'claude', wenn nicht festgelegt.",
|
||||
"placeholder": "Standard: claude"
|
||||
"placeholder": "Standard: claude",
|
||||
"maxTokensLabel": "Maximale Ausgabe-Tokens",
|
||||
"maxTokensDescription": "Maximale Anzahl an Ausgabe-Tokens für Claude Code-Antworten. Standard ist 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude Code Path",
|
||||
"description": "Optional path to your Claude Code CLI. Defaults to 'claude' if not set.",
|
||||
"placeholder": "Default: claude"
|
||||
"placeholder": "Default: claude",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximum number of output tokens for Claude Code responses. Default is 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Ruta de Claude Code",
|
||||
"description": "Ruta opcional a su CLI de Claude Code. Por defecto, es 'claude' si no se establece.",
|
||||
"placeholder": "Por defecto: claude"
|
||||
"placeholder": "Por defecto: claude",
|
||||
"maxTokensLabel": "Tokens máximos de salida",
|
||||
"maxTokensDescription": "Número máximo de tokens de salida para las respuestas de Claude Code. El valor predeterminado es 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Chemin du code Claude",
|
||||
"description": "Chemin facultatif vers votre CLI Claude Code. La valeur par défaut est 'claude' si non défini.",
|
||||
"placeholder": "Défaut : claude"
|
||||
"placeholder": "Défaut : claude",
|
||||
"maxTokensLabel": "Jetons de sortie max",
|
||||
"maxTokensDescription": "Nombre maximum de jetons de sortie pour les réponses de Claude Code. La valeur par défaut est 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "क्लाउड कोड पथ",
|
||||
"description": "आपके क्लाउड कोड सीएलआई का वैकल्पिक पथ। यदि सेट नहीं है तो डिफ़ॉल्ट 'claude' है।",
|
||||
"placeholder": "डिफ़ॉल्ट: claude"
|
||||
"placeholder": "डिफ़ॉल्ट: claude",
|
||||
"maxTokensLabel": "अधिकतम आउटपुट टोकन",
|
||||
"maxTokensDescription": "Claude Code प्रतिक्रियाओं के लिए आउटपुट टोकन की अधिकतम संख्या। डिफ़ॉल्ट 8000 है।"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -377,7 +377,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Jalur Kode Claude",
|
||||
"description": "Jalur opsional ke Claude Code CLI Anda. Defaultnya adalah 'claude' jika tidak diatur.",
|
||||
"placeholder": "Default: claude"
|
||||
"placeholder": "Default: claude",
|
||||
"maxTokensLabel": "Token Output Maks",
|
||||
"maxTokensDescription": "Jumlah maksimum token output untuk respons Claude Code. Default adalah 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Percorso Claude Code",
|
||||
"description": "Percorso facoltativo per la tua CLI Claude Code. Predefinito 'claude' se non impostato.",
|
||||
"placeholder": "Predefinito: claude"
|
||||
"placeholder": "Predefinito: claude",
|
||||
"maxTokensLabel": "Token di output massimi",
|
||||
"maxTokensDescription": "Numero massimo di token di output per le risposte di Claude Code. Il valore predefinito è 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "クロードコードパス",
|
||||
"description": "Claude Code CLIへのオプションパス。設定されていない場合、デフォルトは「claude」です。",
|
||||
"placeholder": "デフォルト:claude"
|
||||
"placeholder": "デフォルト:claude",
|
||||
"maxTokensLabel": "最大出力トークン",
|
||||
"maxTokensDescription": "Claude Codeレスポンスの最大出力トークン数。デフォルトは8000です。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "클로드 코드 경로",
|
||||
"description": "Claude Code CLI의 선택적 경로입니다. 설정하지 않으면 'claude'가 기본값입니다.",
|
||||
"placeholder": "기본값: claude"
|
||||
"placeholder": "기본값: claude",
|
||||
"maxTokensLabel": "최대 출력 토큰",
|
||||
"maxTokensDescription": "Claude Code 응답의 최대 출력 토큰 수. 기본값은 8000입니다."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude Code Pad",
|
||||
"description": "Optioneel pad naar uw Claude Code CLI. Standaard 'claude' als niet ingesteld.",
|
||||
"placeholder": "Standaard: claude"
|
||||
"placeholder": "Standaard: claude",
|
||||
"maxTokensLabel": "Max Output Tokens",
|
||||
"maxTokensDescription": "Maximaal aantal output-tokens voor Claude Code-reacties. Standaard is 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Ścieżka Claude Code",
|
||||
"description": "Opcjonalna ścieżka do Twojego CLI Claude Code. Domyślnie 'claude', jeśli nie ustawiono.",
|
||||
"placeholder": "Domyślnie: claude"
|
||||
"placeholder": "Domyślnie: claude",
|
||||
"maxTokensLabel": "Maksymalna liczba tokenów wyjściowych",
|
||||
"maxTokensDescription": "Maksymalna liczba tokenów wyjściowych dla odpowiedzi Claude Code. Domyślnie 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Caminho do Claude Code",
|
||||
"description": "Caminho opcional para o seu Claude Code CLI. O padrão é 'claude' se não for definido.",
|
||||
"placeholder": "Padrão: claude"
|
||||
"placeholder": "Padrão: claude",
|
||||
"maxTokensLabel": "Tokens de saída máximos",
|
||||
"maxTokensDescription": "Número máximo de tokens de saída para respostas do Claude Code. O padrão é 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Путь к Claude Code",
|
||||
"description": "Необязательный путь к вашему Claude Code CLI. По умолчанию используется 'claude', если не установлено.",
|
||||
"placeholder": "По умолчанию: claude"
|
||||
"placeholder": "По умолчанию: claude",
|
||||
"maxTokensLabel": "Макс. выходных токенов",
|
||||
"maxTokensDescription": "Максимальное количество выходных токенов для ответов Claude Code. По умолчанию 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude Code Yolu",
|
||||
"description": "Claude Code CLI'nize isteğe bağlı yol. Ayarlanmazsa varsayılan olarak 'claude' kullanılır.",
|
||||
"placeholder": "Varsayılan: claude"
|
||||
"placeholder": "Varsayılan: claude",
|
||||
"maxTokensLabel": "Maksimum Çıktı Token sayısı",
|
||||
"maxTokensDescription": "Claude Code yanıtları için maksimum çıktı token sayısı. Varsayılan 8000'dir."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Đường dẫn Claude Code",
|
||||
"description": "Đường dẫn tùy chọn đến Claude Code CLI của bạn. Mặc định là 'claude' nếu không được đặt.",
|
||||
"placeholder": "Mặc định: claude"
|
||||
"placeholder": "Mặc định: claude",
|
||||
"maxTokensLabel": "Số token đầu ra tối đa",
|
||||
"maxTokensDescription": "Số lượng token đầu ra tối đa cho các phản hồi của Claude Code. Mặc định là 8000."
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude Code 路径",
|
||||
"description": "您的 Claude Code CLI 的可选路径。如果未设置,则默认为 “claude”。",
|
||||
"placeholder": "默认:claude"
|
||||
"placeholder": "默认:claude",
|
||||
"maxTokensLabel": "最大输出 Token",
|
||||
"maxTokensDescription": "Claude Code 响应的最大输出 Token 数量。默认为 8000。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
|
|
@ -373,7 +373,9 @@
|
|||
"claudeCode": {
|
||||
"pathLabel": "Claude Code 路徑",
|
||||
"description": "可選的 Claude Code CLI 路徑。如果未設定,則預設為 'claude'。",
|
||||
"placeholder": "預設:claude"
|
||||
"placeholder": "預設:claude",
|
||||
"maxTokensLabel": "最大輸出 Token",
|
||||
"maxTokensDescription": "Claude Code 回應的最大輸出 Token 數量。預設為 8000。"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue