Skip to content

feat: Handle claude-opus-4.7+ new thinking API (adaptive + output_config.effort) #45

@HXYerror

Description

@HXYerror

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:

  1. Forward as-is and let the client handle the 400 (simplest), OR
  2. 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

  • Add to anthropic-types.ts:
    thinking?: 
      | { type: "enabled"; budget_tokens?: number }
      | { type: "adaptive" }
    output_config?: { effort: "low" | "medium" | "high" }
  • In create-messages-native.ts: add a normalizeThinkingForModel(payload, model) step:
    • If model is in ADAPTIVE_THINKING_MODELS (4.7+) AND thinking.type === "enabled" → convert to adaptive format
    • If model is in LEGACY_THINKING_MODELS (4.5/4.6) AND thinking.type === "adaptive" → convert back (with budget heuristic from effort tier)
    • Otherwise pass through unchanged
  • Seed ADAPTIVE_THINKING_MODELS from /models response: models where supports.min_thinking_budget is absent but supports.max_thinking_budget is present (or detect via a thinking_format capability field if Copilot adds one)
  • Fallback: if model not in either set, pass thinking through as-is; let upstream reject if incompatible

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    anthropicAnthropic Messages API compatibilityreasoningReasoning / thinking / encrypted_content

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions