Make Claude Sonnet 5 adaptive thinking visible#100
Merged
Conversation
Sonnet 5 enables adaptive thinking by default but defaults thinking.display
to "omitted", so the thinking field returns empty and users see no thinking.
Inject {type:"adaptive",display:"summarized"} for claude-sonnet-5 unless the
caller explicitly disabled thinking.
Unlike Fable/Mythos (which reject a disable), Sonnet 5 accepts
{type:"disabled"}, so an intentional opt-out is preserved (canonicalized to a
bare {type:"disabled"} since display is invalid with disabled). A manual
{type:"enabled",budget_tokens} would 400 on Sonnet 5, so it is overwritten
with the adaptive form.
There was a problem hiding this comment.
1 issue found across 6 files
Confidence score: 4/5
- In
packages/pi/src/convert.ts, the shared use ofCLAUDE_SONNET_5_ADAPTIVE_THINKINGacrossisFableOrMythos5andisSonnet5can route Fable/Mythos behavior through Sonnet-specific config, which risks subtle model-selection mismatches in production; split these branches so each model family references its own constant before merging.
Architecture diagram
sequenceDiagram
participant Client as API Client
participant Gateway as Request Gateway
participant Rewriter as rewriteRequestBody()
participant Normalizer as Model Normalizer
participant Pi as Pi Request Builder
participant API as Anthropic API
Note over Client,API: Claude Sonnet 5 Adaptive Thinking Flow
Client->>Gateway: POST /v1/messages (model: claude-sonnet-5)
Gateway->>Rewriter: rewriteRequestBody()
Rewriter->>Normalizer: normalizeSonnet5Request()
alt No thinking field in request
Normalizer->>Normalizer: NEW: Inject {type:"adaptive", display:"summarized"}
Normalizer-->>Rewriter: {display: "summarized"}
else thinking.type === "disabled"
Normalizer->>Normalizer: NEW: Canonicalize to bare {type:"disabled"}
Normalizer-->>Rewriter: {display: "disabled"}
else thinking.type === "enabled" (with budget_tokens)
Normalizer->>Normalizer: NEW: Overwrite with adaptive + summarized
Normalizer-->>Rewriter: {display: "summarized"}
end
alt Non-Sonnet-5 model
Normalizer-->>Rewriter: return null (no change)
end
Rewriter->>Rewriter: Log perf metrics (sonnet5ThinkingDisplay)
Rewriter-->>Gateway: Modified request body
Gateway->>API: Forward request
Note over Pi,API: Alternative Pi SDK Flow
Client->>Pi: buildAnthropicRequest(model: claude-sonnet-5)
Pi->>Pi: Check isClaudeSonnet5Model()
Pi->>Pi: NEW: Inject {type:"adaptive", display:"summarized"}
alt reasoning option provided
Pi->>Pi: Map reasoning to output_config.effort
end
Pi-->>Client: Modified request body
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Split the Pi convert branch so Fable/Mythos uses CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING and Sonnet 5 uses CLAUDE_SONNET_5_ADAPTIVE_THINKING, instead of sharing the Sonnet 5 constant for both. The constants are aliased today, but using each family's own keeps their thinking configs independently evolvable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Claude Sonnet 5 (
claude-sonnet-5) enables adaptive thinking by default, but itsthinking.displaydefaults to"omitted"— so thethinkingfield returns empty and thinking is invisible to the user. This is a silent change from earlier models wheredisplaydefaulted to"summarized".Fix
Normalize the request body for
claude-sonnet-5so adaptive thinking is visible:thinking: {type: "adaptive", display: "summarized"}.{type: "disabled"}→ preserve the disable, canonicalized to a bare{type: "disabled"}(thedisplayfield is invalid alongsidetype: "disabled"and can 400).{type: "enabled", budget_tokens}would 400 on Sonnet 5, so it is overwritten with the adaptive form.This mirrors the existing Fable/Mythos 5 handling, with the Sonnet-5-specific difference that
{type: "disabled"}is accepted (Fable/Mythos reject it).claude-sonnet-5is in the native model catalog, so no model registration is added.Changes
packages/core/src/models.ts—isClaudeSonnet5Model,CLAUDE_SONNET_5_MODEL_ID,CLAUDE_SONNET_5_ADAPTIVE_THINKING.packages/opencode/src/transform.ts—normalizeSonnet5Request, called alongside the Fable/Mythos normalizer.packages/pi/src/convert.ts— Sonnet 5 folded into the adaptive-thinking branch (Pi's typed options cannot express disable, so it always emits adaptive + summarized and maps reasoning to effort).Tests
isClaudeSonnet5Modelprecision: matchesclaude-sonnet-5and dated snapshots; rejectsclaude-sonnet-4-6, Fable/Mythos ids, andclaude-sonnet-5x.{type:"disabled"}; non-Sonnet-5 untouched.display: "summarized"forclaude-sonnet-5; reasoning→effort mapping intact.All suites green (opencode 744, pi 47, e2e 2), typecheck and lint clean.
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Make adaptive thinking visible for
claude-sonnet-5. Requests now defaultthinkingto{type: "adaptive", display: "summarized"}unless explicitly disabled, preventing emptythinkingresponses.packages/opencode/src/transform.ts: preserve{type:"disabled"}(stripdisplay), override{type:"enabled", budget_tokens}to adaptive summarized, and set adaptive summarized when missing; non-Sonnet-5 untouched.packages/core; updatepackages/pito emit adaptive summarized and mapreasoning→output_config.effort(Pi cannot disable).packages/pito use each model family's constant (CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKINGvsCLAUDE_SONNET_5_ADAPTIVE_THINKING) so configs stay independent.Written for commit 893c7c9. Summary will update on new commits.
Greptile Summary
This PR makes Claude Sonnet 5's adaptive thinking visible by injecting
{type:"adaptive",display:"summarized"}into requests that would otherwise silently return an emptythinkingfield (Sonnet 5 defaultsdisplayto"omitted"). The change mirrors existing Fable/Mythos 5 handling in both the raw-body transform path and the Pi typed-options path.packages/core/src/models.ts: AddsCLAUDE_SONNET_5_MODEL_ID, theCLAUDE_SONNET_5_ADAPTIVE_THINKINGconstant (currently aliased toCLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING), andisClaudeSonnet5Model()with dash-prefix matching for dated snapshots.packages/opencode/src/transform.ts: IntroducesnormalizeSonnet5Request()which—unlike the Fable/Mythos normalizer—preserves an explicit{type:"disabled"}(stripping invaliddisplay), overwrites{type:"enabled",budget_tokens}(which would 400 on Sonnet 5), and defaults everything else to adaptive+summarized.packages/pi/src/convert.ts: Folds Sonnet 5 into the adaptive-summarized branch and mapsreasoningtooutput_config.effort; Pi's typed surface cannot express disable, so no disable case is needed here.Confidence Score: 5/5
Safe to merge — the change is narrowly scoped to Sonnet 5 model IDs and all non-Sonnet-5 requests are left completely untouched.
The normalizer correctly identifies the three distinct thinking states (absent/enabled → adaptive+summarized, explicit disabled → canonicalized bare disabled) with no overlap with the Fable/Mythos path. Tests cover all branches including the dash-prefix false-positive guard. Both code paths (raw transform and Pi typed interface) are updated consistently.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Incoming request body] --> B{isClaudeSonnet5Model?} B -- No --> C[normalizeSonnet5Request returns null] B -- Yes --> D{thinking present AND type is disabled?} D -- Yes --> E[Canonicalize to bare disabled object, strip display field] D -- No --> F[Set thinking to adaptive plus summarized, overwrite any enabled plus budget value] E --> G[Log sonnet5ThinkingDisplay as disabled] F --> H[Log sonnet5ThinkingDisplay as summarized] G --> I[Continue pipeline] H --> I subgraph Pi path P1[buildAnthropicRequest] --> P2{isClaudeSonnet5Model?} P2 -- Yes --> P3[body.thinking = adaptive plus summarized] P3 --> P4{options.reasoning set?} P4 -- Yes --> P5[body.output_config = effort] P4 -- No --> P6[No output_config] P2 -- No --> P7[Fable/Mythos or budget-based path] end%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A[Incoming request body] --> B{isClaudeSonnet5Model?} B -- No --> C[normalizeSonnet5Request returns null] B -- Yes --> D{thinking present AND type is disabled?} D -- Yes --> E[Canonicalize to bare disabled object, strip display field] D -- No --> F[Set thinking to adaptive plus summarized, overwrite any enabled plus budget value] E --> G[Log sonnet5ThinkingDisplay as disabled] F --> H[Log sonnet5ThinkingDisplay as summarized] G --> I[Continue pipeline] H --> I subgraph Pi path P1[buildAnthropicRequest] --> P2{isClaudeSonnet5Model?} P2 -- Yes --> P3[body.thinking = adaptive plus summarized] P3 --> P4{options.reasoning set?} P4 -- Yes --> P5[body.output_config = effort] P4 -- No --> P6[No output_config] P2 -- No --> P7[Fable/Mythos or budget-based path] endComments Outside Diff (1)
packages/pi/src/convert.ts, line 1-15 (link)CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKINGimport was dropped and replaced withCLAUDE_SONNET_5_ADAPTIVE_THINKINGfor both model families. SinceCLAUDE_SONNET_5_ADAPTIVE_THINKINGis currently just an alias (= CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING), they are identical today — but the Fable/Mythos spread now silently depends on the Sonnet 5 constant. If a future commit adjustsCLAUDE_SONNET_5_ADAPTIVE_THINKINGto carry a Sonnet-5-specific value, Fable/Mythos behavior in this file changes without any signal at the call site. Keeping the original import alive makes each family independently evolvable.Reviews (2): Last reviewed commit: "refactor(pi): use each model family's ow..." | Re-trigger Greptile