From 145e0e0b5dbd75fa1c5a72ec8e19c0ed88755d1e Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:46:00 +0800 Subject: [PATCH] fix(claude): add default max_tokens for models --- internal/runtime/executor/claude_executor.go | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index 0ec35199..bc5d2065 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -22,6 +22,7 @@ import ( claudeauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" @@ -44,10 +45,33 @@ type ClaudeExecutor struct { // Previously "proxy_" was used but this is a detectable fingerprint difference. const claudeToolPrefix = "" +// Anthropic-compatible upstreams may reject or even crash when dynamically +// registered Claude models omit max_tokens. Use a conservative default. +const defaultModelMaxTokens = 1024 + func NewClaudeExecutor(cfg *config.Config) *ClaudeExecutor { return &ClaudeExecutor{cfg: cfg} } func (e *ClaudeExecutor) Identifier() string { return "claude" } +func ensureModelMaxTokens(body []byte, modelID string) []byte { + if len(body) == 0 || !gjson.ValidBytes(body) { + return body + } + + if maxTokens := gjson.GetBytes(body, "max_tokens"); maxTokens.Exists() { + return body + } + + for _, provider := range registry.GetGlobalRegistry().GetModelProviders(strings.TrimSpace(modelID)) { + if strings.EqualFold(provider, "claude") { + body, _ = sjson.SetBytes(body, "max_tokens", defaultModelMaxTokens) + return body + } + } + + return body +} + // PrepareRequest injects Claude credentials into the outgoing HTTP request. func (e *ClaudeExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth.Auth) error { if req == nil { @@ -127,6 +151,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r requestedModel := payloadRequestedModel(opts, req.Model) body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) + body = ensureModelMaxTokens(body, baseModel) // Disable thinking if tool_choice forces tool use (Anthropic API constraint) body = disableThinkingIfToolChoiceForced(body) @@ -293,6 +318,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A requestedModel := payloadRequestedModel(opts, req.Model) body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) + body = ensureModelMaxTokens(body, baseModel) // Disable thinking if tool_choice forces tool use (Anthropic API constraint) body = disableThinkingIfToolChoiceForced(body)