adaptive thinking support for passthrough mode - #5934
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughAnthropic typed and raw request paths now normalize unsupported thinking configurations. The logic uses canonical model identifiers, model families, and effort conditions. Tests cover adaptive-only models, disabled-thinking rejection, Mythos Preview behavior, raw requests, typed requests, and supported-field preservation. ChangesAnthropic thinking normalization
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant RequestBuilder
participant stripUnsupportedAnthropicFields
participant ThinkingCapabilityRules
RequestBuilder->>stripUnsupportedAnthropicFields: pass request and canonical model
stripUnsupportedAnthropicFields->>ThinkingCapabilityRules: evaluate model and effort
ThinkingCapabilityRules-->>stripUnsupportedAnthropicFields: return normalization decision
stripUnsupportedAnthropicFields-->>RequestBuilder: return normalized request or error
Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/providers/anthropic/utils.go`:
- Around line 323-325: The adaptive-thinking normalization in
core/providers/anthropic/utils.go:323-325 and the raw-path handling at
core/providers/anthropic/utils.go:662-674 must clear BudgetTokens whenever the
final thinking type is "adaptive", including requests already marked adaptive,
while preserving budget_tokens for legacy models. Update both typed and raw
paths accordingly, and add regression coverage in
core/providers/anthropic/adaptivethinkingstrip_test.go:45-124 for typed and raw
adaptive requests.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 717971b7-c85a-4cbb-9ea3-6dac70395d43
📒 Files selected for processing (3)
core/providers/anthropic/adaptivethinkingstrip_test.gocore/providers/anthropic/requestbuilder.gocore/providers/anthropic/utils.go
dca169b to
9c046db
Compare
8e0c1b1 to
bdec000
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
bdec000 to
bf3ad45
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
bf3ad45 to
4e8cdb2
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@core/providers/anthropic/adaptivethinkingstrip_test.go`:
- Around line 238-240: Strengthen the assertions in the adaptive-thinking
rewrite tests at each referenced site to require that the resulting req.Thinking
object is present and its Type equals "adaptive", rather than only checking that
"disabled" is absent. Preserve the separate budget_tokens removal assertion
while also verifying that an already-adaptive request retains thinking.Type as
"adaptive".
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4cfc3740-4aca-44a3-b352-2afeea866a80
📒 Files selected for processing (3)
core/providers/anthropic/adaptivethinkingstrip_test.gocore/providers/anthropic/requestbuilder.gocore/providers/anthropic/utils.go
🚧 Files skipped from review as they are similar to previous changes (2)
- core/providers/anthropic/utils.go
- core/providers/anthropic/requestbuilder.go
9c046db to
ddbb11d
Compare
4e8cdb2 to
926c7bd
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
ddbb11d to
6e6112d
Compare
926c7bd to
ec02f23
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
core/providers/anthropic/adaptivethinkingstrip_test.go (2)
264-282: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a mixed-case effort case.
RejectsDisabledThinkinglowercases the effort value before comparison (core/providers/anthropic/utils.goLines 1003-1014). The tests only send lowercase values, so the lowercasing branch is untested.Add
"XHigh"or"MAX"to the effort slice at Line 265 to lock that behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/anthropic/adaptivethinkingstrip_test.go` around lines 264 - 282, Extend the effort table in typed_request_rewrites_disabled_on_opus5_at_high_effort to include one mixed-case value such as "XHigh" or "MAX", ensuring RejectsDisabledThinking’s case-insensitive comparison is covered while preserving the existing assertions.
185-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert that
thinking.typeis untouched on legacy models.The comment at Lines 182-184 states the sanitizer must not rewrite legacy models. This subtest only checks
budget_tokens. A regression that rewritesthinking.typeonclaude-opus-4-5orclaude-sonnet-4-5still passes.Add a
thinking.typeassertion to cover the stated guarantee.✅ Proposed assertion
if got := providerUtils.GetJSONField(result, "thinking.budget_tokens").Int(); got != 10000 { t.Errorf("%s: budget_tokens = %d, want 10000 preserved; body: %s", model, got, string(result)) } + if got := providerUtils.GetJSONField(result, "thinking.type").String(); got != "adaptive" { + t.Errorf("%s: thinking.type = %q, want \"adaptive\" untouched; body: %s", + model, got, string(result)) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/providers/anthropic/adaptivethinkingstrip_test.go` around lines 185 - 199, Add an assertion in the raw_body_preserves_budget_tokens_on_legacy_models subtest, alongside the existing budget_tokens check, that verifies thinking.type remains "adaptive" for every legacy model. Preserve the current error handling and iteration over legacyOK.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@core/providers/anthropic/adaptivethinkingstrip_test.go`:
- Around line 264-282: Extend the effort table in
typed_request_rewrites_disabled_on_opus5_at_high_effort to include one
mixed-case value such as "XHigh" or "MAX", ensuring RejectsDisabledThinking’s
case-insensitive comparison is covered while preserving the existing assertions.
- Around line 185-199: Add an assertion in the
raw_body_preserves_budget_tokens_on_legacy_models subtest, alongside the
existing budget_tokens check, that verifies thinking.type remains "adaptive" for
every legacy model. Preserve the current error handling and iteration over
legacyOK.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c2594b8-d884-4b43-8b6f-3af2afe6923d
📒 Files selected for processing (3)
core/providers/anthropic/adaptivethinkingstrip_test.gocore/providers/anthropic/requestbuilder.gocore/providers/anthropic/utils.go
🚧 Files skipped from review as they are similar to previous changes (2)
- core/providers/anthropic/requestbuilder.go
- core/providers/anthropic/utils.go
Merge activity
|
The base branch was changed.
## Summary
Adaptive-only Anthropic models (Opus 4.7+, Opus 4.8, Opus 5, Sonnet 5, Fable 5, Mythos 5) no longer support the legacy `thinking.type: "enabled"` shape and reject it with a 400 upstream. When a caller sends a native Anthropic request body through Bifrost with `{"type":"enabled","budget_tokens":N}`, it was being forwarded verbatim, causing failures. This PR rewrites `thinking.type` from `"enabled"` to `"adaptive"` and strips `budget_tokens` for these models on both the typed and raw passthrough paths, matching the behavior already implemented in the converted chat/responses paths.
Additionally, `stripUnsupportedAnthropicFields` in both the responses and chat builders is now gated on `capModel` (the canonical Anthropic model name) rather than `request.Model`, so Bifrost aliases correctly resolve to a model the capability predicates can match.
## Changes
- In `stripUnsupportedAnthropicFields`, when `thinking.type == "enabled"` and the model is adaptive-only, the type is rewritten to `"adaptive"` and `budget_tokens` is set to `nil`. The field is rewritten rather than deleted because Opus 4.7/4.8 default thinking to Off, so dropping it would silently disable thinking for callers who explicitly requested it.
- In `StripUnsupportedFieldsFromRawBody`, the same rewrite is applied to the raw JSON body path using `SetJSONField` and `DeleteJSONField`.
- Both the responses and chat request builders now pass `capModel` instead of `request.Model` to `stripUnsupportedAnthropicFields` so that Bifrost aliases do not bypass model-level capability checks.
- Tests covering both the raw body and typed request paths verify that adaptive-only models get the rewrite and that older models (Opus 4.6, Sonnet 4.5, Haiku 4.5, etc.) have their legacy shape preserved unchanged.
## Type of change
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
go test ./core/providers/anthropic/... -run TestAdaptiveOnlyThinkingStrip -v
go test ./...
```
Expected: all four sub-tests pass — adaptive-only models have `thinking.type` rewritten to `"adaptive"` with `budget_tokens` removed, and legacy models retain `thinking.type: "enabled"` with `budget_tokens: 10000` intact.
## Screenshots/Recordings
N/A
## Breaking changes
- [ ] Yes
- [x] No
## Related issues
## Security considerations
No auth, secrets, or PII implications. The change only rewrites a field in outbound request bodies before they are forwarded to the Anthropic API.
## Checklist
- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
## Summary
Adaptive-only Anthropic models (Opus 4.7+, Opus 4.8, Opus 5, Sonnet 5, Fable 5, Mythos 5) no longer support the legacy `thinking.type: "enabled"` shape and reject it with a 400 upstream. When a caller sends a native Anthropic request body through Bifrost with `{"type":"enabled","budget_tokens":N}`, it was being forwarded verbatim, causing failures. This PR rewrites `thinking.type` from `"enabled"` to `"adaptive"` and strips `budget_tokens` for these models on both the typed and raw passthrough paths, matching the behavior already implemented in the converted chat/responses paths.
Additionally, `stripUnsupportedAnthropicFields` in both the responses and chat builders is now gated on `capModel` (the canonical Anthropic model name) rather than `request.Model`, so Bifrost aliases correctly resolve to a model the capability predicates can match.
## Changes
- In `stripUnsupportedAnthropicFields`, when `thinking.type == "enabled"` and the model is adaptive-only, the type is rewritten to `"adaptive"` and `budget_tokens` is set to `nil`. The field is rewritten rather than deleted because Opus 4.7/4.8 default thinking to Off, so dropping it would silently disable thinking for callers who explicitly requested it.
- In `StripUnsupportedFieldsFromRawBody`, the same rewrite is applied to the raw JSON body path using `SetJSONField` and `DeleteJSONField`.
- Both the responses and chat request builders now pass `capModel` instead of `request.Model` to `stripUnsupportedAnthropicFields` so that Bifrost aliases do not bypass model-level capability checks.
- Tests covering both the raw body and typed request paths verify that adaptive-only models get the rewrite and that older models (Opus 4.6, Sonnet 4.5, Haiku 4.5, etc.) have their legacy shape preserved unchanged.
## Type of change
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI
## Affected areas
- [x] Core (Go)
- [ ] Transports (HTTP)
- [x] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs
## How to test
```sh
go test ./core/providers/anthropic/... -run TestAdaptiveOnlyThinkingStrip -v
go test ./...
```
Expected: all four sub-tests pass — adaptive-only models have `thinking.type` rewritten to `"adaptive"` with `budget_tokens` removed, and legacy models retain `thinking.type: "enabled"` with `budget_tokens: 10000` intact.
## Screenshots/Recordings
N/A
## Breaking changes
- [ ] Yes
- [x] No
## Related issues
## Security considerations
No auth, secrets, or PII implications. The change only rewrites a field in outbound request bodies before they are forwarded to the Anthropic API.
## Checklist
- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable

Summary
Adaptive-only Anthropic models (Opus 4.7+, Opus 4.8, Opus 5, Sonnet 5, Fable 5, Mythos 5) no longer support the legacy
thinking.type: "enabled"shape and reject it with a 400 upstream. When a caller sends a native Anthropic request body through Bifrost with{"type":"enabled","budget_tokens":N}, it was being forwarded verbatim, causing failures. This PR rewritesthinking.typefrom"enabled"to"adaptive"and stripsbudget_tokensfor these models on both the typed and raw passthrough paths, matching the behavior already implemented in the converted chat/responses paths.Additionally,
stripUnsupportedAnthropicFieldsin both the responses and chat builders is now gated oncapModel(the canonical Anthropic model name) rather thanrequest.Model, so Bifrost aliases correctly resolve to a model the capability predicates can match.Changes
stripUnsupportedAnthropicFields, whenthinking.type == "enabled"and the model is adaptive-only, the type is rewritten to"adaptive"andbudget_tokensis set tonil. The field is rewritten rather than deleted because Opus 4.7/4.8 default thinking to Off, so dropping it would silently disable thinking for callers who explicitly requested it.StripUnsupportedFieldsFromRawBody, the same rewrite is applied to the raw JSON body path usingSetJSONFieldandDeleteJSONField.capModelinstead ofrequest.ModeltostripUnsupportedAnthropicFieldsso that Bifrost aliases do not bypass model-level capability checks.Type of change
Affected areas
How to test
Expected: all four sub-tests pass — adaptive-only models have
thinking.typerewritten to"adaptive"withbudget_tokensremoved, and legacy models retainthinking.type: "enabled"withbudget_tokens: 10000intact.Screenshots/Recordings
N/A
Breaking changes
Related issues
Security considerations
No auth, secrets, or PII implications. The change only rewrites a field in outbound request bodies before they are forwarded to the Anthropic API.
Checklist
docs/contributing/README.mdand followed the guidelines