fix(claude): reconcile max_tokens vs thinking budget and lift per-model ceiling (#2381)

On the translated OpenAI->Claude path, adjustMaxTokens capped max_tokens
before applyThinking set thinking.budget_tokens, so max-effort budget
(128000) could exceed a 64k-clamped max_tokens -> Anthropic 400.
prepareClaudeRequest now reconciles after the budget is known: prefer
raising max_tokens, only shrink budget when it meets/exceeds the ceiling.

Also lift the global 64000 cap: the ceiling is now the model's real
maxOutput, so high-output models (fable/mythos, opus-4.8/sonnet-4.6) get
their full budget. adjustMaxTokens gains an optional ceiling arg (default
unchanged, callers untouched); openai-to-claude passes the model maxOutput.

Native Claude Code passthrough is unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
thienpv
2026-07-05 17:32:25 +07:00
committed by decolua
parent 5041494e1c
commit 46e6c01a01
4 changed files with 129 additions and 8 deletions

View File

@@ -192,10 +192,27 @@ export function prepareClaudeRequest(body, provider = null, apiKey = null, conne
delete body.output_config;
}
// Clamp max_tokens to the model output ceiling (never above DEFAULT_MAX_TOKENS)
// Clamp max_tokens to the model's real output ceiling. Models whose caps
// declare a higher maxOutput (e.g. Opus 4.8 / Sonnet 4.6 = 128000) are allowed
// up to it, so max-effort thinking gets full budget; others fall back to the
// conservative 64000 default.
if (body.max_tokens) {
const ceiling = Math.min(getCapabilitiesForModel(provider, body.model).maxOutput, DEFAULT_MAX_TOKENS);
const ceiling = getCapabilitiesForModel(provider, body.model).maxOutput || DEFAULT_MAX_TOKENS;
if (body.max_tokens > ceiling) body.max_tokens = ceiling;
// Reconcile against thinking budget. applyThinking (thinkingUnified.js) runs
// AFTER adjustMaxTokens capped max_tokens, and the claude-budget format maps
// max effort → budget_tokens 128000 — larger than the clamped max_tokens.
// Anthropic requires max_tokens strictly greater than budget_tokens (else 400).
// Prefer raising max_tokens to preserve the requested thinking depth; if the
// budget alone meets/exceeds the ceiling, cap output and shrink the budget so
// some tokens remain for the answer.
if (body.thinking?.type === "enabled" && body.thinking.budget_tokens && body.thinking.budget_tokens >= body.max_tokens) {
body.max_tokens = Math.min(body.thinking.budget_tokens + 1024, ceiling);
if (body.thinking.budget_tokens >= body.max_tokens) {
body.thinking.budget_tokens = Math.max(1024, body.max_tokens - 1024);
}
}
}
// 1. System: remove all cache_control, add only to last block with ttl 1h