Part of #38.
Background (live-tested — 2026-05-10)
claude-opus-4.7 and newer models use a different thinking API than 4.5/4.6:
Old format (4.5 / 4.6):
{"thinking": {"type": "enabled", "budget_tokens": 2000}}
New format (4.7+):
{
"thinking": {"type": "adaptive"},
"output_config": {"effort": "low" | "medium" | "high"}
}
Sending "type": "enabled" to claude-opus-4.7 returns:
HTTP 400: "thinking.type.enabled" is not supported for this model. Use "thinking.type.adaptive" and "output_config.effort" to control thinking
Current state
AnthropicMessagesPayload.thinking in anthropic-types.ts only models {type: "enabled", budget_tokens?: number}. The adaptive type and output_config field are not declared.
On the native pass-through path (#39): the client's request is forwarded as-is. If Claude Code sends the old enabled format to a 4.7 model, the upstream will reject it — the proxy itself isn't to blame, but we should either:
- Forward as-is and let the client handle the 400 (simplest), OR
- Auto-translate: if client sends
{type: "enabled", budget_tokens: N} to a 4.7+ model, convert to {type: "adaptive"} + appropriate output_config.effort tier
Effort tier mapping (proposed)
budget_tokens >= 8000 → effort: "high"
budget_tokens >= 3000 → effort: "medium"
else → effort: "low"
Tasks
Acceptance criteria
- Client sends
{thinking: {type:"enabled", budget_tokens:2000}, model:"claude-opus-4.7"} → proxy transparently converts → response includes thinking content
- Client sends
{thinking: {type:"adaptive"}, output_config:{effort:"high"}, model:"claude-opus-4.7"} → forwarded unchanged
- Client sends
{thinking: {type:"enabled", budget_tokens:2000}, model:"claude-sonnet-4.6"} → forwarded unchanged (native 4.6 format)
- Tests cover each conversion branch
File pointers
- Touch:
src/routes/messages/anthropic-types.ts
- Touch:
src/services/copilot/create-messages-native.ts
Part of #38.
Background (live-tested — 2026-05-10)
claude-opus-4.7and newer models use a different thinking API than 4.5/4.6:Old format (4.5 / 4.6):
{"thinking": {"type": "enabled", "budget_tokens": 2000}}New format (4.7+):
{ "thinking": {"type": "adaptive"}, "output_config": {"effort": "low" | "medium" | "high"} }Sending
"type": "enabled"toclaude-opus-4.7returns:Current state
AnthropicMessagesPayload.thinkinginanthropic-types.tsonly models{type: "enabled", budget_tokens?: number}. Theadaptivetype andoutput_configfield are not declared.On the native pass-through path (#39): the client's request is forwarded as-is. If Claude Code sends the old
enabledformat to a 4.7 model, the upstream will reject it — the proxy itself isn't to blame, but we should either:{type: "enabled", budget_tokens: N}to a 4.7+ model, convert to{type: "adaptive"}+ appropriateoutput_config.efforttierEffort tier mapping (proposed)
Tasks
anthropic-types.ts:create-messages-native.ts: add anormalizeThinkingForModel(payload, model)step:modelis inADAPTIVE_THINKING_MODELS(4.7+) ANDthinking.type === "enabled"→ convert to adaptive formatmodelis inLEGACY_THINKING_MODELS(4.5/4.6) ANDthinking.type === "adaptive"→ convert back (with budget heuristic from effort tier)ADAPTIVE_THINKING_MODELSfrom/modelsresponse: models wheresupports.min_thinking_budgetis absent butsupports.max_thinking_budgetis present (or detect via athinking_formatcapability field if Copilot adds one)thinkingthrough as-is; let upstream reject if incompatibleAcceptance criteria
{thinking: {type:"enabled", budget_tokens:2000}, model:"claude-opus-4.7"}→ proxy transparently converts → response includes thinking content{thinking: {type:"adaptive"}, output_config:{effort:"high"}, model:"claude-opus-4.7"}→ forwarded unchanged{thinking: {type:"enabled", budget_tokens:2000}, model:"claude-sonnet-4.6"}→ forwarded unchanged (native 4.6 format)File pointers
src/routes/messages/anthropic-types.tssrc/services/copilot/create-messages-native.ts