diff --git a/internal/translate/default_max_tokens_test.go b/internal/translate/default_max_tokens_test.go index 9505dcd56..b9d96c921 100644 --- a/internal/translate/default_max_tokens_test.go +++ b/internal/translate/default_max_tokens_test.go @@ -178,3 +178,22 @@ func TestOpenAISameFormat_ExplicitMaxTokensClampsToKimiK3Ceiling(t *testing.T) { out := parseAndEmit(t, body, "openai", opts) assert.Equal(t, float64(32000), out["max_tokens"]) } + +// Regression: qwen/qwen3.8-max was absent from modelMaxOutputTokens, so a +// Claude Code turn requesting max_tokens=64000 was clamped to the 8192 +// fallback. Qwen3-Max serves 32k output on Fireworks (qwen3p8-max); every +// agentic turn ended in finish_reason=length and an auto-resume loop until +// the client surfaced "response exceeded the 64000 output token maximum". +func TestOpenAISameFormat_ExplicitMaxTokensClampsToQwen38MaxCeiling(t *testing.T) { + body := []byte(`{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}],"max_tokens":64000}`) + opts := translate.EmitOptions{ + TargetModel: "qwen/qwen3.8-max", + Capabilities: router.Lookup("qwen/qwen3.8-max"), + } + out := parseAndEmit(t, body, "openai", opts) + assert.Equal(t, float64(32768), out["max_tokens"]) + + body = []byte(`{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}],"max_tokens":16384}`) + out = parseAndEmit(t, body, "openai", opts) + assert.Equal(t, float64(16384), out["max_tokens"]) +} diff --git a/internal/translate/envelope.go b/internal/translate/envelope.go index f994b5654..4aa2424c6 100644 --- a/internal/translate/envelope.go +++ b/internal/translate/envelope.go @@ -1201,6 +1201,10 @@ var modelMaxOutputTokens = map[string]int{ // Keyed by full catalog ID, since decision.Model keeps the vendor prefix. // Other OSS rows are still absent and so inherit the 8192 fallback. "moonshotai/kimi-k3": 131072, + // Qwen3-Max family serves 32k output on Fireworks (qwen3p8-max). Without + // this entry a Claude Code max_tokens=64000 turn was clamped to the 8192 + // fallback and truncated every agentic turn (finish_reason=length loop). + "qwen/qwen3.8-max": 32768, } const defaultMaxOutputTokenCap = 8192