Skip to content

fix(anthropic): per-model thinking shape restores Claude reasoning on Copilot - #8

Merged
Hunter-Thompson merged 1 commit into
mainfrom
chat-our-sdk-anthropic-copilot-does-not-show-thinkning-l-wkeab8
Jul 7, 2026
Merged

fix(anthropic): per-model thinking shape restores Claude reasoning on Copilot#8
Hunter-Thompson merged 1 commit into
mainfrom
chat-our-sdk-anthropic-copilot-does-not-show-thinkning-l-wkeab8

Conversation

@Hunter-Thompson

Copy link
Copy Markdown
Contributor

Problem

Claude models on the Copilot provider never show thinking, even though we already route them to Copilot's native Anthropic /v1/messages endpoint (the approach of BerriAI/litellm#28053 / litellm#31802).

The remaining gap was the thinking request shape, applied provider-wide instead of per-model. Copilot serves two Claude generations with mutually incompatible shapes:

Generation Accepted shape on Copilot /v1/messages What we sent
claude-haiku-4.5, claude-sonnet-4.5, claude-opus-4.5 thinking.type=enabled + budget_tokens only — adaptive returns no thinking blocks adaptive → reasoning silently disabled
claude-sonnet-4.6, claude-opus-4.6/4.7/4.8, claude-sonnet-5, claude-fable-5 adaptive + output_config.effort (400 on enabled) adaptive

Commit bdd304a switched everything to adaptive after the newer models' live 400; that fixed them but silently killed thinking for the 4.5 family. The LiteLLM issue independently verified enabled + budget_tokens emits thinking blocks end-to-end (incl. streaming content_block_*) on Copilot /v1/messages for claude-haiku-4.5.

The first-party API had the mirror bug: effort-only models (claude-fable-5, claude-opus-4-7+, generation 5) were sent enabled+budget, which they reject with a 400.

The shape split mirrors the models.dev reasoning_options catalogs for anthropic and github-copilot (and opencode's per-model adaptive gating).

Changes

  • internal/anthropic/thinking.go: ModelSupportsAdaptiveThinking / ModelRequiresAdaptiveThinking classify Claude generations; tolerant of dotted (claude-sonnet-4.6), dashed (claude-sonnet-4-6), dated (...-20250929), and legacy version-first (claude-3-7-sonnet) names.
  • providers/anthropic: buildRequest resolves the shape per model. AdaptiveThinking now means "effort-first gateway": adaptive wherever the generation supports it, enabled+budget for the 4.5-and-older family. The first-party path keeps enabled except on effort-only models. Effort-only settings derive a budget on the enabled path (same ladder as ModeReasoningSettings).
  • Self-healing: a 400 whose body mentions thinking.type flips the shape once, retries, and pins the working shape on the model handle — catalog drift degrades to one retry instead of a failed run (both directions).

Verification

  • New unit tests: generation classifier table; per-model shape matrix (Copilot + first-party); factory-level regression TestCopilotClaude45UsesEnabledThinking (Copilot claude-sonnet-4.5enabled + budget_tokens=8192, empty output_config); 400-flip retry + stickiness; unrelated-400 passthrough.
  • Existing TestCopilotClaudeUsesAdaptiveThinking (opus-4.8 → adaptive+max) still passes unchanged.
  • go build ./..., go vet, full go test ./... — only failure is the known pre-existing pkg/agentsdk/sandbox container-environment failure (os.Executable), unrelated.

Residual risk

The generation split is a name heuristic; a Copilot-side capability change would be absorbed by the one-shot 400 flip rather than a hard failure. claude-sonnet-4 (empty reasoning_options on Copilot) now gets enabled+budget — its native first-party shape — instead of adaptive; if the shim rejects thinking there entirely, the flip retries with adaptive and then surfaces the error.

… Copilot

Copilot's /v1/messages shim serves two Claude generations with mutually
incompatible extended-thinking request shapes: the 4.5-and-older family
only implements thinking.type=enabled + budget_tokens (adaptive requests
return no thinking blocks at all), while 4.6+/fable/5.x reject enabled
with a 400 and require thinking.type=adaptive + output_config.effort.
The provider-wide AdaptiveThinking switch sent adaptive to everything,
which silently disabled visible reasoning for claude-haiku-4.5,
claude-sonnet-4.5, and claude-opus-4.5 on Copilot. Upstream verification
of the enabled shape on Copilot /v1/messages: BerriAI/litellm#28053 /
litellm#31802. The first-party API had the mirror bug: effort-only
models (claude-fable-5, claude-opus-4.7+, generation 5) got
enabled+budget, which they reject.

- internal/anthropic: ModelSupportsAdaptiveThinking /
  ModelRequiresAdaptiveThinking classify Claude generations (dotted,
  dashed, dated, and legacy version-first names), mirroring the
  models.dev reasoning_options catalogs for anthropic/github-copilot.
- providers/anthropic: buildRequest resolves the thinking shape per
  model. AdaptiveThinking now means "effort-first gateway": adaptive
  wherever supported, enabled+budget for the 4.5 family; the first-party
  path keeps enabled except on effort-only models. Effort-only settings
  derive a budget on the enabled path (same ladder as
  ModeReasoningSettings).
- Self-healing: a 400 mentioning thinking.type flips the shape once,
  retries, and pins the working shape on the model handle, so catalog
  drift degrades to one retry instead of a failed run.

Co-authored-by: captaintrips-gratefulagents <captaintrips@gratefulagents.dev>
@Hunter-Thompson
Hunter-Thompson merged commit 75e7b6f into main Jul 7, 2026
1 check passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6c232762a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +390 to +394
if sent.Thinking.Type == "adaptive" {
m.thinkingShape.Store(thinkingShapeEnabled)
} else {
m.thinkingShape.Store(thinkingShapeAdaptive)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pin the shape only after the retry succeeds

When a shape-related 400 is followed by a failed retry (for example a transient 429/5xx/network error, or the opposite shape is also invalid), this stores the flipped shape before any successful response proves it works. Because the same AnthropicModel can be reused for later attempts or turns, subsequent calls bypass the per-model heuristic and are forced to the unverified shape, causing avoidable repeated failures or silently disabling thinking for models that needed the original shape.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant