Skip to content

Commit f5dfbf1

Browse files
hannesrudolphCopilotdaniel-lxs
authored
feat(claude-code): add configurable max output tokens setting (#5610)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Daniel <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>
1 parent 9aa2e6e commit f5dfbf1

File tree

26 files changed

+301
-228
lines changed

26 files changed

+301
-228
lines changed

packages/types/src/provider-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
8080

8181
const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
8282
claudeCodePath: z.string().optional(),
83+
claudeCodeMaxOutputTokens: z.number().int().min(1).max(200000).optional(),
8384
})
8485

8586
const glamaSchema = baseProviderSettingsSchema.extend({

packages/types/src/providers/claude-code.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { anthropicModels } from "./anthropic.js"
44
// Claude Code
55
export type ClaudeCodeModelId = keyof typeof claudeCodeModels
66
export const claudeCodeDefaultModelId: ClaudeCodeModelId = "claude-sonnet-4-20250514"
7+
export const CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS = 8000
78
export const claudeCodeModels = {
89
"claude-sonnet-4-20250514": {
910
...anthropicModels["claude-sonnet-4-20250514"],

src/api/providers/__tests__/claude-code.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,32 @@ describe("ClaudeCodeHandler", () => {
4848
expect(model.id).toBe("claude-sonnet-4-20250514") // default model
4949
})
5050

51+
test("should override maxTokens when claudeCodeMaxOutputTokens is provided", () => {
52+
const options: ApiHandlerOptions = {
53+
claudeCodePath: "claude",
54+
apiModelId: "claude-sonnet-4-20250514",
55+
claudeCodeMaxOutputTokens: 8000,
56+
}
57+
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
58+
const model = handlerWithMaxTokens.getModel()
59+
60+
expect(model.id).toBe("claude-sonnet-4-20250514")
61+
expect(model.info.maxTokens).toBe(8000) // Should use the configured value, not the default 64000
62+
})
63+
64+
test("should override maxTokens for default model when claudeCodeMaxOutputTokens is provided", () => {
65+
const options: ApiHandlerOptions = {
66+
claudeCodePath: "claude",
67+
apiModelId: "invalid-model", // Will fall back to default
68+
claudeCodeMaxOutputTokens: 16384,
69+
}
70+
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
71+
const model = handlerWithMaxTokens.getModel()
72+
73+
expect(model.id).toBe("claude-sonnet-4-20250514") // default model
74+
expect(model.info.maxTokens).toBe(16384) // Should use the configured value
75+
})
76+
5177
test("should filter messages and call runClaudeCode", async () => {
5278
const systemPrompt = "You are a helpful assistant"
5379
const messages = [{ role: "user" as const, content: "Hello" }]
@@ -76,6 +102,43 @@ describe("ClaudeCodeHandler", () => {
76102
messages: filteredMessages,
77103
path: "claude",
78104
modelId: "claude-3-5-sonnet-20241022",
105+
maxOutputTokens: undefined, // No maxOutputTokens configured in this test
106+
})
107+
})
108+
109+
test("should pass maxOutputTokens to runClaudeCode when configured", async () => {
110+
const options: ApiHandlerOptions = {
111+
claudeCodePath: "claude",
112+
apiModelId: "claude-3-5-sonnet-20241022",
113+
claudeCodeMaxOutputTokens: 16384,
114+
}
115+
const handlerWithMaxTokens = new ClaudeCodeHandler(options)
116+
117+
const systemPrompt = "You are a helpful assistant"
118+
const messages = [{ role: "user" as const, content: "Hello" }]
119+
const filteredMessages = [{ role: "user" as const, content: "Hello (filtered)" }]
120+
121+
mockFilterMessages.mockReturnValue(filteredMessages)
122+
123+
// Mock empty async generator
124+
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
125+
// Empty generator for basic test
126+
}
127+
mockRunClaudeCode.mockReturnValue(mockGenerator())
128+
129+
const stream = handlerWithMaxTokens.createMessage(systemPrompt, messages)
130+
131+
// Need to start iterating to trigger the call
132+
const iterator = stream[Symbol.asyncIterator]()
133+
await iterator.next()
134+
135+
// Verify runClaudeCode was called with maxOutputTokens
136+
expect(mockRunClaudeCode).toHaveBeenCalledWith({
137+
systemPrompt,
138+
messages: filteredMessages,
139+
path: "claude",
140+
modelId: "claude-3-5-sonnet-20241022",
141+
maxOutputTokens: 16384,
79142
})
80143
})
81144

src/api/providers/claude-code.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Anthropic } from "@anthropic-ai/sdk"
2-
import { claudeCodeDefaultModelId, type ClaudeCodeModelId, claudeCodeModels } from "@roo-code/types"
2+
import { claudeCodeDefaultModelId, type ClaudeCodeModelId, claudeCodeModels, type ModelInfo } from "@roo-code/types"
33
import { type ApiHandler } from ".."
44
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
55
import { runClaudeCode } from "../../integrations/claude-code/run"
@@ -25,6 +25,7 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
2525
messages: filteredMessages,
2626
path: this.options.claudeCodePath,
2727
modelId: this.getModel().id,
28+
maxOutputTokens: this.options.claudeCodeMaxOutputTokens,
2829
})
2930

3031
// Usage is included with assistant messages,
@@ -129,12 +130,26 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
129130
const modelId = this.options.apiModelId
130131
if (modelId && modelId in claudeCodeModels) {
131132
const id = modelId as ClaudeCodeModelId
132-
return { id, info: claudeCodeModels[id] }
133+
const modelInfo: ModelInfo = { ...claudeCodeModels[id] }
134+
135+
// Override maxTokens with the configured value if provided
136+
if (this.options.claudeCodeMaxOutputTokens !== undefined) {
137+
modelInfo.maxTokens = this.options.claudeCodeMaxOutputTokens
138+
}
139+
140+
return { id, info: modelInfo }
141+
}
142+
143+
const defaultModelInfo: ModelInfo = { ...claudeCodeModels[claudeCodeDefaultModelId] }
144+
145+
// Override maxTokens with the configured value if provided
146+
if (this.options.claudeCodeMaxOutputTokens !== undefined) {
147+
defaultModelInfo.maxTokens = this.options.claudeCodeMaxOutputTokens
133148
}
134149

135150
return {
136151
id: claudeCodeDefaultModelId,
137-
info: claudeCodeModels[claudeCodeDefaultModelId],
152+
info: defaultModelInfo,
138153
}
139154
}
140155

src/integrations/claude-code/run.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type Anthropic from "@anthropic-ai/sdk"
33
import { execa } from "execa"
44
import { ClaudeCodeMessage } from "./types"
55
import readline from "readline"
6+
import { CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS } from "@roo-code/types"
67

78
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
89

@@ -20,7 +21,9 @@ type ProcessState = {
2021
exitCode: number | null
2122
}
2223

23-
export async function* runClaudeCode(options: ClaudeCodeOptions): AsyncGenerator<ClaudeCodeMessage | string> {
24+
export async function* runClaudeCode(
25+
options: ClaudeCodeOptions & { maxOutputTokens?: number },
26+
): AsyncGenerator<ClaudeCodeMessage | string> {
2427
const process = runProcess(options)
2528

2629
const rl = readline.createInterface({
@@ -107,7 +110,13 @@ const claudeCodeTools = [
107110

108111
const CLAUDE_CODE_TIMEOUT = 600000 // 10 minutes
109112

110-
function runProcess({ systemPrompt, messages, path, modelId }: ClaudeCodeOptions) {
113+
function runProcess({
114+
systemPrompt,
115+
messages,
116+
path,
117+
modelId,
118+
maxOutputTokens,
119+
}: ClaudeCodeOptions & { maxOutputTokens?: number }) {
111120
const claudePath = path || "claude"
112121

113122
const args = [
@@ -134,8 +143,11 @@ function runProcess({ systemPrompt, messages, path, modelId }: ClaudeCodeOptions
134143
stderr: "pipe",
135144
env: {
136145
...process.env,
137-
// The default is 32000. However, I've gotten larger responses, so we increase it unless the user specified it.
138-
CLAUDE_CODE_MAX_OUTPUT_TOKENS: process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS || "64000",
146+
// Use the configured value, or the environment variable, or default to CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS
147+
CLAUDE_CODE_MAX_OUTPUT_TOKENS:
148+
maxOutputTokens?.toString() ||
149+
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS ||
150+
CLAUDE_CODE_DEFAULT_MAX_OUTPUT_TOKENS.toString(),
139151
},
140152
cwd,
141153
maxBuffer: 1024 * 1024 * 1000,

0 commit comments

Comments
 (0)