🤖 feat: add support for Claude Sonnet 5#3664
Conversation
|
@codex review |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b235d5c78f
ℹ️ 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".
|
Addressed the Codex P2 (
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6da973cf3
ℹ️ 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".
|
Addressed the second Codex P2 ( Making the minor segment optional let the regex misparse legacy major-first IDs like @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed36bbb30e
ℹ️ 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".
|
Addressed the third Codex P2 ( Verified against Anthropic's Sonnet 5 "what's new": non-default Fix (latest commit): @codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ef83a7b8f
ℹ️ 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".
Update the built-in Sonnet alias to claude-sonnet-5 and wire its native 1M context, adaptive thinking, native xhigh effort, native web_fetch, mux-gateway defaults, docs, mocks, and tests.\n\n---\n\n_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `2617797{MUX_COSTS_USD:-0}`_\n\n<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=6.81 -->
|
@codex review Please take another look. Addressed the native web_fetch regex finding by requiring an alphabetic Claude variant segment and adding regression coverage for |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
## Summary This is the long-lived **auto-cleanup** PR. Each run, the auto-cleanup agent reviews new commits merged to `main`, rebases onto the latest `main`, and applies at most one extremely low-risk, behavior-preserving cleanup. The branch accumulates a small stack of independent cleanups until it is merged. **This run** extracts the duplicated model-string normalization chain (`trim` → `toLowerCase` → strip `provider:` prefix → strip `namespace/` segment) that `anthropicSupportsNativeXhigh` (`src/common/types/thinking.ts`) and `getExplicitThinkingPolicy` (`src/common/utils/thinking/policy.ts`) each inlined verbatim into a shared `stripModelProviderPrefixes` helper. This area was just touched by coder#3664 (Claude Sonnet 5), which relies on `anthropicSupportsNativeXhigh` for capability detection. Behavior-preserving. ## Included cleanups (diff vs `main`) 1. **`refactor: extract shared stripAnsiControlChars helper`** — `src/node/services/bashMonitorWakeStore.ts` and `src/node/services/backgroundProcessManager.ts` each defined a byte-for-byte identical `ANSI_ESCAPE_PATTERN` regex and inlined the same strip-ANSI + drop-control-chars expression (`sanitizeBashMonitorWakeLine` / `sanitizeMonitorLine`). Both now import the new `stripAnsiControlChars` from `src/node/utils/ansi.ts`. 2. **`refactor: dedupe InstructionsTab formatBytes into shared helper`** — `src/browser/components/InstructionsTab/InstructionsTab.tsx` defined a local `formatBytes(n)` that was byte-for-byte identical (only the parameter name differed) to the already-shared `formatBytes` in `src/common/utils/formatBytes.ts` (used by `FileReadToolCall`, `WebFetchToolCall`, `AttachFileToolCall`, `AgentSkillReadFileToolCall`, and the CLI tool formatters). The local copy is removed and the shared helper is imported. 3. **`refactor: drop dead hasTrailingNewline ternary branches in getOutput`** — `src/node/services/backgroundProcessManager.ts` had two `hasTrailingNewline ? allLines.slice(0, -1) : allLines.slice(0, -1)` ternaries inside `getOutput` whose two branches were identical, so the condition never affected the result. Both are replaced with the unconditional `allLines.slice(0, -1)`. In the first occurrence the now-unused `hasTrailingNewline` local is also removed; in the second it is kept because it is still consulted when computing `incompleteLineBuffer`. 4. **`refactor: extract shared stripModelProviderPrefixes helper`** — `anthropicSupportsNativeXhigh` (`src/common/types/thinking.ts`) and `getExplicitThinkingPolicy` (`src/common/utils/thinking/policy.ts`) each inlined the identical `trim → toLowerCase → strip 'provider:' → strip 'namespace/'` chain to reduce a model string to its bare model id for capability matching. The chain moves into a new exported `stripModelProviderPrefixes` helper in `thinking.ts`; both call sites now use it. In `getExplicitThinkingPolicy` the two intermediate locals (`normalized`, `withoutPrefix`) collapse into the single `withoutProviderNamespace` the helper returns. ## Background These cleanups remove duplicated or dead logic that drifts silently. - Runs 1–2 deduped byte-identical helpers (`stripAnsiControlChars`, `formatBytes`) that had been copied across files. - Run 3 followed up coder#3663, which simplified the `hasTrailingNewline ? X : X` pattern in `processMonitorContent` but left two identical instances in `getOutput` untouched. - Run 4 targets the model-string normalization that coder#3664 (Claude Sonnet 5) leaned on: `getExplicitThinkingPolicy` and `anthropicSupportsNativeXhigh` both need the bare model id and had copy-pasted the same normalization. Consolidating it keeps capability predicates consistent about how `provider:` prefixes and gateway `namespace/` segments are stripped. ## Implementation - `stripAnsiControlChars` (run 1): new `src/node/utils/ansi.ts` strips ANSI/VT escape sequences and drops non-printable control characters (keeping tab and any code point >= 0x20). The regex was copied verbatim from the prior inline definitions, so it is byte-identical. - `formatBytes` dedup (run 2): the local function in `InstructionsTab.tsx` is deleted and `formatBytes` is imported from `@/common/utils/formatBytes`; the shared helper is character-for-character equivalent, so the single call site renders identical output. - `hasTrailingNewline` dedup (run 3): the two dead ternaries in `getOutput` collapse to `allLines.slice(0, -1)`. The polling-loop occurrence dropped its `hasTrailingNewline` declaration (it had no other use in that scope); the final-processing occurrence keeps the declaration since `proc.incompleteLineBuffer` still reads `!hasTrailingNewline`. - `stripModelProviderPrefixes` (run 4): the new exported helper runs the same two `.replace()` calls in the same order as the prior inline chains, so it is behavior-identical. `getThinkingDisplayLabel` in the same file is intentionally left alone — it only strips the `provider:` prefix and still inspects `withoutPrefix.startsWith("openai/")`, so it needs the un-stripped namespace and does not fit this helper. ## Validation - `bun test src/common/utils/thinking/policy.test.ts src/common/utils/ai/providerOptions.test.ts src/common/utils/ai/models.test.ts src/common/utils/tools/tools.test.ts` (226 pass) exercises the normalization paths. - `make static-check` passes for these TS-only changes (ESLint, both TypeScript configs, and Prettier all green). The run's only failure is `fmt-shell-check` aborting because `shfmt` is not installed in this environment; the changes touch no shell/Docker files. ## Risks None. All four cleanups are behavior-preserving: the shared helpers reuse the verbatim prior logic, and collapsing a `cond ? X : X` ternary cannot change the result since both branches are identical. --- Auto-cleanup checkpoint: e1169d7 --- _Generated with `mux` • Model: `anthropic:claude-opus-4-8` • Thinking: `xhigh` • Cost: `$0.00`_ <!-- mux-attribution: model=anthropic:claude-opus-4-8 thinking=xhigh costs=0.00 --> --------- Co-authored-by: mux-bot[bot] <264182336+mux-bot[bot]@users.noreply.github.com>
…oder#3718) ## Summary Changing the thinking/reasoning slider during an active turn now applies to the **next model step** (the next provider request after the current tool execution), instead of only taking effect on the next user message. Idle-time changes behave exactly as before. ## Background Each assistant turn spans multiple provider requests separated by tool executions, and long agentic turns can run for many minutes. Previously the thinking level was frozen at send time, so lowering effort on an obviously-mechanical stretch (or raising it when the agent got stuck) required interrupting the stream. Provider APIs are stateless per request — nothing prevents per-step reasoning changes; Mux just never rebuilt provider options mid-turn. As a prerequisite, the Anthropic xhigh wire hack (header-marker + fetch-wrapper body rewrite) is removed: the installed `@ai-sdk/anthropic` passes `effort: "xhigh"` and adaptive `thinking.display` through provider options directly, so mid-turn rebuilds produce correct wire output without a parallel hack path. ## Implementation - **Per-turn override holder** (`src/node/services/thinkingOverride.ts`): a small mutable `{ pending, applied, onApplied }` object created by `AgentSession` once a turn is durably accepted, shared by reference through AIService into StreamManager. Created before any async startup work, which closes the race where a slider change lands while the turn is still PREPARING (applies to its first request). - **New ORPC route** `workspace.setActiveTurnThinkingLevel`: writes `pending` (last-write-wins) on the live session; returns `accepted: false` when idle/unknown. The frontend `ThinkingContext` fires it best-effort after the existing persistence path, so workspace-setting semantics are unchanged. - **Consumption in `prepareStep`** (StreamManager): the pending level is consumed exactly once per change; provider options are rebuilt via an AIService-owned closure that repeats the send-time pipeline (min-level floor → policy clamp → effective-level resolution → provider options → `providers.jsonc` extras). The live `request.providerOptions` object is mutated **in place** because the AI SDK deep-merges per-step options and cannot remove stale nested keys (e.g. Anthropic `thinking` on a → off transition); the rebuilt options are also returned from `prepareStep` as defense in depth. - **Model fallback** rebinds the same holder with a closure bound to the fallback model; same-stream retries preserve holder and mutated options. - Out of scope (persisted setting still applies next turn): xAI `grok-4-1-fast` off↔non-off (requires a model-instance swap), model changes, OpenAI Pro `reasoningMode`, ACP sessions. ## Validation Dogfooded against the live Anthropic API (direct route) in a dev-server sandbox, with per-step wire evidence from `devtools.jsonl` (`rawRequest.thinking` / `.output_config`, steps identified by message-count progression within one turn): - **low → high mid-turn** (Sonnet 4.5): `budget_tokens` 4000 → 20000 at the next step. - **high → off mid-turn**: `thinking` key deleted from subsequent requests, zero API errors (in-place deletion path). - **off → medium mid-turn**: `thinking` added mid-turn. - **max → xhigh mid-turn** (Sonnet 5, adaptive): `output_config.effort` "max" → "xhigh". - **Post-turn slider change**: route returns `accepted: false`; only the persisted setting updates. - **Phase 0 gate**: steady-state native xhigh sends `output_config.effort` + `thinking: {type: "adaptive", display: "summarized"}` directly — no effort header, no fetch-wrapper rewrite. - Persisted assistant `metadata.thinkingLevel` reflects the final applied level in every scenario; UI verified at 375px width. ## Risks - **StreamManager `prepareStep`** is on the hot path of every stream. The override branch is a cheap early-out when no holder/pending exists; regression risk concentrates in multi-step turns with a mid-turn change (mitigated by consume-once tests, fallback/retry tests, and live dogfooding above). - **In-place `providerOptions` mutation** relies on the SDK reading the object captured at `streamText()` time. If a future SDK snapshot-copies options, the `prepareStep` return value covers it (asserted by test). - **Anthropic xhigh hack removal** changes the wire path for existing xhigh users of Opus 4.6/Sonnet 4.6 (non-native `max` fallback) and native models; both shapes are covered by provider-option tests and were dogfooded live. --- <details> <summary>📋 Implementation Plan</summary> # Mid-turn thinking-level changes: apply at the next model step ## Goal When the user changes the thinking/reasoning slider while a turn is streaming, the new level should take effect at the **next model step** of the active stream (a "step" = one provider request; steps are separated by tool executions). Today the level is captured once at send time and is fixed for the whole turn. Chosen semantics (per user): **auto-apply at next step** — no separate "Apply now" button, no interrupt/continue. Changing the slider mid-turn both (a) persists the workspace setting as today and (b) requests a next-step override on the active stream. ## Verified current behavior (evidence) - Send path captures `thinkingLevel` in `SendMessageOptions` (`src/browser/hooks/useSendMessageOptions.ts`); backend clamps once via `enforceThinkingPolicy` in `AgentSession` (`src/node/services/agentSession.ts:3616-3640`, floor = `config.json` `minThinkingLevelByModel` → `resolveMinimumThinkingLevel`). - `AIService.streamMessage` builds `providerOptions` once (`buildProviderOptions`, `src/node/services/aiService.ts:2393-2405`) and merges `providers.jsonc` extras (`:2422-2449`), then calls `StreamManager.startStream` with the fixed level (`:2800-2832`). - `streamText()` is invoked once per stream (`StreamManager.createStreamResult`, `src/node/services/streamManager.ts:1617-1660`) with `providerOptions: request.providerOptions`. Its `prepareStep` hook already returns per-step overrides (messages, `activeTools`) and follows the shared-mutable-state precedent of `toolSearchState`. - AI SDK 7 (`ai@7.0.19`): `prepareStep` may return per-step `providerOptions`, which are **deep-merged** (`mergeObjects`) with the request-level options each step (`node_modules/ai/src/generate-text/stream-text.ts:1861-1864`). Omitted keys survive; `undefined` cannot delete a key. `PrepareStepResult` has **no `headers` field**. - The SDK reads the outer `providerOptions` reference on every step (`mergeObjects(providerOptions, …)`), so in-place mutation of `streamInfo.request.providerOptions` is visible to subsequent steps. - Slider chain: `ThinkingSlider` → `useThinkingLevel` → `ThinkingContext.setThinkingLevel` (`src/browser/contexts/ThinkingContext.tsx:219-238`) → `persistAgentAiSettings` → `api.workspace.updateAgentAISettings`. Nothing reaches the active stream. - Precedent for renderer→in-flight-stream mutation: `api.workspace.interruptStream` → `WorkspaceService.interruptStream` (`workspaceService.ts:8308-8381`) → `getOrCreateSession(workspaceId)` → `AgentSession.interruptStream` → `AIService.stopStream` → `StreamManager.stopStream`. - Metadata: `streamInfo.thinkingLevel` feeds partials (`streamManager.ts:2223-2225`), stream-end metadata (`:3059-3061`) and the persisted assistant message; the model-fallback path already mutates it mid-stream (`:2425-2428`). - **Anthropic xhigh wire hack is obsolete** (verified against installed `@ai-sdk/anthropic@4.0.11` source): - The hack's own comments state it existed because the SDK Zod schema "doesn't accept xhigh yet" (`src/common/types/thinking.ts:188-190`, `providerOptions.ts:641-648`); introduced with Opus 4.7 (coder#3180), extended for Sonnet 5 (coder#3664). Mux sends `effort: "max"` through the SDK plus `x-mux-anthropic-effort: xhigh`, and the Anthropic fetch wrapper rewrites `output_config.effort` on the wire (`providerModelFactory.ts:342-401`). - SDK 4.0.11 now accepts it: `effort: z.enum(['low','medium','high','xhigh','max'])` (`anthropic-language-model-options.ts:209`). Explicit provider options bypass the SDK's `supportsXhighEffort` model gating — that gating only applies to the SDK's top-level `reasoning` CallOption mapping, which Mux does not use; explicit `anthropicOptions.effort` flows verbatim into `output_config.effort` (`anthropic-language-model.ts:398-465`). This also covers Mythos (absent from the SDK capability table) exactly like today's wrapper rewrite. - Same for `thinking.display`: the SDK schema now has `display: z.enum(['omitted','summarized'])` on adaptive thinking and forwards it (`anthropic-language-model-options.ts:95-102`, `anthropic-language-model.ts:433-457`), so the wrapper's `display ??= "summarized"` injection is also expressible directly in provider options. - Route safety: the anthropic options branch runs iff `formatProvider === "anthropic"`, which is the **same condition** as `routePassesHeaders` (`resolveProviderOptionsNamespaceKey(origin, routeProvider) === origin`). So every route that emits anthropic `effort` today also gets the header + wrapper rewrite — direct and mux-gateway requests already carry `"xhigh"` (and `display`) on the wire in production. Sending them directly via provider options is **wire-field equivalent** (same Anthropic body fields; the mux-internal header disappears, but the wrapper stripped it before upstream anyway). Non-passthrough gateways (OpenRouter/copilot) use different provider-options branches and are unaffected. - All Anthropic request builders go through `buildProviderOptions` (main stream `aiService.ts:2393`, fallback `:2710`, advisor `tools/advisor.ts:159`), so no other path depends on the wrapper rewrites. ## Design overview Core primitive: a **session-owned, per-turn live holder** (one object per turn, shared by reference down the whole pipeline — same pattern as `toolSearchState`): ```ts interface ActiveTurnThinkingOverride { /** Raw level requested mid-turn; consumed at the next prepareStep (incl. step 1). */ pending?: ThinkingLevel; /** Effective level after the most recent successful application. */ applied?: ThinkingLevel; /** Sink wired by StreamManager to the owning streamInfo (metadata). */ onApplied?: (level: ThinkingLevel) => void; } ``` ```text ThinkingContext.setThinkingLevel (mid-turn) ├─ persistAgentAiSettings(...) (unchanged; future turns) └─ api.workspace.setActiveTurnThinkingLevel (new, fire-and-forget) → router → WorkspaceService → sessions.get(id)?.setActiveTurnThinkingLevel(level) └─ session.activeTurnThinkingOverride != null (turn active: PREPARING or STREAMING) ? holder.pending = level → { accepted: true } : { accepted: false } (idle: persisted settings already cover next turn) AgentSession turn lifecycle ├─ turn durably accepted, options finalized (sendMessage / resumeStream): create holder │ BEFORE the user-message emit / onAccepted / any await, then │ pass explicitly into streamWithHistory → StreamMessageOptions │ → AIService → buildStreamRequestConfig → request.thinkingOverrideState (same object) └─ turn end (setTurnPhase→IDLE, interrupt, dispose): identity-guarded clear (if (this.activeTurnThinkingOverride === holder) … = null) — pending expires StreamManager prepareStep (runs before EVERY step, including step 1 — so a change during PREPARING/startup applies to the turn's first provider request) ├─ pending? → rebuildForLevel(pending) // closure supplied by AIService │ ├─ policy clamp + resolveEffectiveThinkingLevel for the STREAM's model │ ├─ not applicable / no-op → clear pending, keep current level │ └─ applicable → │ • in-place replace streamInfo.request.providerOptions content │ • state.applied = effectiveLevel; state.onApplied(effectiveLevel) │ (onApplied sets streamInfo.thinkingLevel → partials/final metadata) │ • return { providerOptions: rebuilt } from prepareStep (defense in depth) └─ no pending → existing behavior ``` Why both in-place mutation **and** a per-step return: the SDK deep-merge cannot delete keys (e.g. Anthropic `thinking` object when moving to `off`), so mutating the live request object is the authoritative mechanism; returning the rebuilt options from `prepareStep` is redundant-but-harmless insurance if the SDK ever changes how it snapshots options. Why a session-owned holder instead of a StreamManager setter: a slider change during AIService startup (model creation, MCP setup — seconds) would miss a not-yet-registered stream. With the holder created at turn start and threaded down by reference, writes land in the same object `prepareStep` reads, closing the PREPARING window with zero extra consumption points; and because `prepareStep` also runs before step 1, no special pre-stream handling is needed. ## Scope rules (what can/cannot switch mid-turn) Applied inside the rebuild closure; when a transition is not applicable, the pending override is dropped for this turn (the new level still applies to the next turn via persisted settings, exactly as today): 1. **Clamp first**: target level is clamped with `enforceThinkingPolicy(streamModel, target, minThinkingLevel, providersConfig)`. If clamped target == currently effective level → no-op. 2. **xAI `grok-4-1-fast`**: `off` ↔ non-`off` selects a different model instance (`providerModelFactory.ts:2014-2020`) → skipped. (Non-off↔non-off is a no-op on xAI anyway — no effort field in provider options.) 3. Everything else applies at the next step: OpenAI `reasoningEffort`, **all Anthropic levels including into/out of native `xhigh`** (after Phase 0 removes the header hack — effort becomes pure provider options), Anthropic off↔on via in-place replacement, Google `thinkingConfig`, OpenRouter `reasoning`. After Phase 0, `buildRequestHeaders` is thinking-level-independent (the only remaining conditional header is the 1M-context beta, keyed on model+config), so `prepareStep`'s inability to change headers no longer constrains any level transition. Out of scope (v1): OpenAI Pro `reasoningMode` toggle mid-turn; model changes mid-turn; already-queued messages (they keep the options captured at queue time); an explicit "Apply now" interrupt+continue action; ACP sessions; emitting a dedicated chat event for the change (metadata carries the final level; can be added later if requested). Known cosmetic limitation: the already-emitted `stream-start` event keeps the initial level, so `WorkspaceStore.currentThinkingLevel` shows the starting level until stream end. The persisted assistant message metadata reflects the **last applied** level. --- ## Implementation ### Phase 0 — Remove the Anthropic xhigh wire hack (~ −50 LoC) Prerequisite refactor that makes Anthropic effort a pure provider-options concern (wire-field-equivalent output, verified above), unblocking mid-turn xhigh transitions and deleting two wrapper rewrites. The refactor must stay **model-aware**: today the wire-level `"xhigh"` and `display` are applied only when `anthropicSupportsNativeXhigh(model)` matches (header gating + wrapper detection). The provider-options replacement mirrors exactly that gating so non-native adaptive models (Opus 4.6 / Sonnet 4.6: `effort: "max"`, **no** `display`) keep identical request fields: 1. `src/common/types/thinking.ts`: - `AnthropicEffortLevel` gains `"xhigh"`. `getAnthropicEffort` becomes model-aware: `getAnthropicEffort(level, capabilityModel)` returns `"xhigh"` only when `level === "xhigh" && anthropicSupportsNativeXhigh(capabilityModel)`, else the current mapping (xhigh→`"max"`). The `ANTHROPIC_EFFORT` table itself keeps `xhigh: "max"` as the non-native fallback. - Replace the stale "SDK doesn't accept xhigh yet" comment with the new rationale (explicit provider options pass through verbatim as of `@ai-sdk/anthropic@4.0.11`; add an upgrade-coupling note: the SDK's `supportsXhighEffort` gating applies only to top-level `reasoning` mapping — re-verify on major SDK upgrades). 2. `src/common/utils/ai/providerOptions.ts`: - Anthropic adaptive branch: pass `capabilityModel` to `getAnthropicEffort`, and set `thinking: { type: "adaptive", display: "summarized" }` **only when `anthropicSupportsNativeXhigh(capabilityModel)`** (matching the wrapper's `targetsNativeXhighModel` condition; other adaptive models keep `{ type: "adaptive" }` with no `display`, as today). Drop the stale comment. - `buildRequestHeaders`: delete the xhigh header branch and the now-unused `thinkingLevel` parameter (update both call sites, `aiService.ts:2413-2420` and `:2726`); delete the `MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER` export. 3. `src/node/services/providerModelFactory.ts` (Anthropic fetch wrapper): remove the effort-override header handling and the `display ??=` injection block (incl. the `targetsNativeXhighModel` detection that exists solely for them). Cache-control injection stays untouched. 4. Update existing tests: `providerOptions.test.ts` (effort mapping/xhigh header assertions), `providerModelFactory.test.ts` (wrapper rewrite tests → assert pass-through/no rewrite). Add assertions for both sides of the gate: - native-xhigh model at xhigh → `effort: "xhigh"` + `thinking.display: "summarized"`; - non-native adaptive model (e.g. Opus 4.6) at its ladder top → `effort` unchanged from today (`"max"`/clamped) and **no** `display` field. 5. Gate: targeted tests + one manual devtools.jsonl check that a steady-state xhigh request is **wire-field equivalent** to today: same Anthropic body fields (`output_config.effort`, `thinking.display`), with the mux-internal header gone (it was stripped before reaching Anthropic anyway, so upstream requests are unchanged). Keep this phase a separate commit so it can be reverted independently of the mid-turn feature. ### Phase 1 — StreamManager: consume the holder in prepareStep (~65 LoC) `src/node/services/streamManager.ts` 1. Type: `ActiveTurnThinkingOverride` lives in a small **node-local** shared module (e.g. `src/node/services/thinkingOverride.ts`) — it carries a callback and is node runtime state, not IPC/shared data, so it does not belong in `src/common/types/`. 2. Extend `StreamRequestConfig` with: - `thinkingOverrideState?: ActiveTurnThinkingOverride` — **the session's holder, by reference; never created inside StreamManager** (a locally-created object would be invisible to the session's setter). - `rebuildProviderOptionsForThinkingLevel?: (level: ThinkingLevel) => { effectiveLevel: ThinkingLevel; providerOptions: Record<string, unknown> } | null` (closure built by AIService; `null` ⇒ not applicable / no-op per scope rules) 3. Thread both through `buildStreamRequestConfig` → `createStreamAtomically` → `startStream` params (same plumbing as `onChunk` / `toolSearchState`). 4. **Stable providerOptions object**: in `buildStreamRequestConfig`, when a rebuild closure is present, normalize `providerOptions` to a guaranteed mutable object (`const finalProviderOptions = providerOptions ?? {}`) before it is captured by `streamText()`. Without this, an initially-`undefined` options value would make later in-place mutation unobservable (the SDK closes over the value passed at creation). Unit-test this identity behavior. 5. In `createStreamResult`'s `prepareStep` (before the existing `if (rewritten === stepMessages && activeTools === undefined) return undefined;` early-return), add: ```ts const thinkingOverride = applyPendingThinkingOverride(request); // helper below ``` and include `...(thinkingOverride ? { providerOptions: thinkingOverride } : {})` in the returned object (adjust the early-return condition accordingly). Because `prepareStep` runs before **every** step including the first, a change made during AIService startup is applied to the turn's first provider request. 6. New private helper `applyPendingThinkingOverride(request)`: - reads `request.thinkingOverrideState?.pending`; returns `undefined` if unset; - always clears `pending` (consume-once; a failed/no-op application must not retry every step); - calls `request.rebuildProviderOptionsForThinkingLevel(pending)`; on `null` → `log.debug` (include workspace, from/to level, skip reason) + return `undefined`; - **in-place** replaces the contents of `request.providerOptions` (delete own keys, `Object.assign` the rebuilt object — object identity preserved) so the SDK's per-step deep-merge and all retry paths observe it; - sets `state.applied = effectiveLevel`, invokes `state.onApplied?.(effectiveLevel)`, returns the rebuilt options object. 7. Wire the metadata sink in `createStreamAtomically`, immediately after `streamInfo` is constructed (and **before** any code path can trigger a step): ```ts if (request.thinkingOverrideState) { request.thinkingOverrideState.onApplied = (level) => { streamInfo.thinkingLevel = level; }; if (request.thinkingOverrideState.applied) streamInfo.thinkingLevel = request.thinkingOverrideState.applied; } ``` (The catch-up sync covers re-attachment of a holder that already applied a level.) Partial writes (`:2223`), stream-end metadata (`:3059`), and the final assistant message then pick up the new level with no polling in the process loop. Note: `createStreamResult` is called before `streamInfo` exists (`:1698-1727`); the SDK does not invoke `prepareStep` synchronously during `streamText()` construction, but the catch-up sync makes ordering safe regardless. 8. No StreamManager setter method is needed — the session writes to the shared holder directly (see Phase 3). 9. Retry/fallback interplay: - **Empty-output retry & previousResponseId retry**: both reuse/spread `streamInfo.request`, so the mutated `providerOptions`, holder reference, closure, and `onApplied` sink carry forward automatically (`streamManager.ts:2484-2521`, `:3540-3589`). No changes needed beyond confirming in tests. - **Model fallback** (`:2331-2445`): the fallback prepare context (second argument of `ModelFallbackOptions.prepare`, currently `{ continuation?: … }`) gains `thinkingLevelOverride?: ThinkingLevel`, populated from `request.thinkingOverrideState?.pending ?? request.thinkingOverrideState?.applied` (then clear `pending` — it is folded into the fallback's baseline). `PreparedModelFallback` (`prepared.data`) gains the fallback-model-bound `rebuildProviderOptionsForThinkingLevel`. `nextRequest` is built with the **same holder object** (so the session setter keeps working) and the new closure — both attached in `buildStreamRequestConfig(...)` **before** `createStreamResult(nextRequest, …)` is called, in case the SDK eagerly prepares the first fallback step; `onApplied` still points at the same `streamInfo`, so no re-wiring is required. ### Phase 2 — AIService: rebuild closure (+ fallback) (~70 LoC) `src/node/services/aiService.ts` (inside `streamMessage`, after `mergedProviderOptions` is computed at ~`:2449`) 1. Build the closure, capturing everything already in scope. It must reproduce the **exact initial effective-level pipeline** (AgentSession floor clamp → AIService `resolveEffectiveThinkingLevel`, `:1097-1101`), not just `enforceThinkingPolicy`: ```ts const rebuildProviderOptionsForThinkingLevel = (level: ThinkingLevel) => { const clamped = enforceThinkingPolicy(modelString, level, minThinkingLevel, providersConfig); const effective = resolveEffectiveThinkingLevel(modelString, clamped, this.providerService.getConfig()); if (effective === currentEffectiveLevelRef.current) return null; // no-op if (isXaiGrokFastVariantSwap(modelString, currentEffectiveLevelRef.current, effective)) return null; const rebuilt = buildProviderOptions(modelString, effective, providerRequestMessages, (id) => this.streamManager.isResponseIdLost(id), effectiveMuxProviderOptions, workspaceId, truncationMode, this.providerService.getConfig(), routeProvider, promptCacheScope, reasoningMode); const merged = mergeModelParameterExtras(rebuilt); // shared helper, see note below currentEffectiveLevelRef.current = effective; return { effectiveLevel: effective, providerOptions: merged }; }; ``` Notes: - `minThinkingLevel`: `AgentSession` already computes the floor at `:3616-3640`; pass it down via a new optional `StreamMessageOptions.minThinkingLevel` field instead of re-resolving in AIService (single source of truth). Fallback when absent (internal callers): `resolveMinimumThinkingLevel(modelString, undefined, providersConfig)`. - `currentEffectiveLevelRef` is a tiny `{ current: ThinkingLevel }` initialized to `effectiveThinkingLevel` so consecutive overrides diff against the live level, not the send-time one. - Factor the extras-merge at `:2431-2449` into a local `mergeModelParameterExtras(options)` used by both the initial build and the closure (DRY, no behavior change). - The grok skip predicate is a small pure helper colocated in `src/common/utils/thinking/policy.ts` (literal `grok-4-1-fast` check mirroring `providerModelFactory.ts:2016`). No Anthropic predicate is needed after Phase 0. 2. New optional `StreamMessageOptions` fields: `minThinkingLevel?: ThinkingLevel` and `activeTurnThinkingOverride?: ActiveTurnThinkingOverride` (the session holder). Pass the holder and `rebuildProviderOptionsForThinkingLevel` into `startStream` (new params, adjacent to `onChunk`). When no holder is supplied (internal callers: compaction, sub-agent paths that don't need it yet), the feature is inert for that stream. 3. Fallback prepare (`:2575-2765`): accept the new `thinkingLevelOverride` context field from StreamManager; compute `nextThinkingLevel = resolveEffectiveThinkingLevel(nextModelString, enforceThinkingPolicy(nextModelString, thinkingLevelOverride ?? effectiveThinkingLevel, minThinkingLevelForNextModel, …), …)`, and include a **new closure bound to the fallback model** (with its own `currentEffectiveLevelRef` initialized to `nextThinkingLevel`) in the returned `prepared.data` so mid-turn changes keep working after a fallback hop. ### Phase 3 — ORPC route + session holder lifecycle (~55 LoC) 1. `src/common/orpc/schemas/api.ts` — add to the `workspace` namespace (near `updateAgentAISettings`), using the repo-standard `Result` wrapper like sibling mutating routes: ```ts setActiveTurnThinkingLevel: { input: z.object({ workspaceId: z.string(), thinkingLevel: ThinkingLevelSchema }), output: ResultSchema(z.object({ accepted: z.boolean() }), z.string()), }, ``` `accepted: false` (success) = nothing active to override — persisted settings still cover the next turn. `accepted: true` = the level will be used for the current turn's next model step **if one occurs**; it expires silently otherwise. Errors are reserved for invalid input/disposed session. 2. `src/node/orpc/router.ts` — handler mirroring `updateAgentAISettings`'s shape, calling `context.workspaceService.setActiveTurnThinkingLevel(input.workspaceId, input.thinkingLevel)`. 3. `src/node/services/workspaceService.ts`: ```ts setActiveTurnThinkingLevel(workspaceId: string, level: ThinkingLevel): Result<{ accepted: boolean }, string> { const session = this.sessions.get(workspaceId.trim()); if (!session) return Ok({ accepted: false }); // no session ⇒ nothing running return Ok(session.setActiveTurnThinkingLevel(level)); } ``` (Deliberately `sessions.get`, not `getOrCreateSession` — creating a session to tell it "change the turn you don't have" is pointless.) 4. `src/node/services/agentSession.ts` — owns the holder lifecycle: - New private field `activeTurnThinkingOverride: ActiveTurnThinkingOverride | null = null`. - **Create after the user-message append/persistence succeeds and `optionsForStream` is finalized — before emitting the user-message chat event, before `internal.onAccepted`, and before any other await/callback** that could let the renderer's slider route arrive. NOT inside `streamWithHistory` (which runs only after runtime warmup). Concretely: in `sendMessage`, just ahead of the `emitChatEvent({ …userMessage })` block (`:2820-2828`, well before `setTurnPhase(TurnPhase.PREPARING)`; covers normal, edit, and queued-dispatch turns), and at `resumeStream`'s equivalent point after its options are finalized. Assign `this.activeTurnThinkingOverride = holder` and pass `holder` **explicitly as a parameter** into `streamWithHistory`, which forwards it (plus `minThinkingLevel`) via the new `StreamMessageOptions` fields into `aiService.streamMessage`. - **Pre-stream failure cleanup**: any path that returns after holder creation without reaching a stream (`onAccepted` throws, startup abort via `preparedTurnAbortController`, `streamWithHistory` error) must identity-clear the holder (`if (this.activeTurnThinkingOverride === holder) this.activeTurnThinkingOverride = null`) — the existing `startPreparedStream` finally-block that resets PREPARING→IDLE is the natural place. - **Setter**: ```ts setActiveTurnThinkingLevel(level: ThinkingLevel): { accepted: boolean } { this.assertNotDisposed("setActiveTurnThinkingLevel"); const holder = this.activeTurnThinkingOverride; if (!holder) return { accepted: false }; // idle: persisted settings cover the next turn holder.pending = level; // last write wins return { accepted: true }; } ``` The holder exists exactly while a turn is active (PREPARING through STREAMING), so no `isBusy()` heuristics are needed — and messages sitting in the queue are untouched (they dispatch later with their own captured options and a fresh holder). - **Clear with an identity guard** wherever the turn ends — `if (this.activeTurnThinkingOverride === holder) this.activeTurnThinkingOverride = null;` — in the turn's completion/abort paths (`setTurnPhase`→`IDLE` bookkeeping, `interruptStream`, `dispose()`). The identity guard ensures a stale aborted turn's cleanup (e.g. an edit preempting a PREPARING turn) cannot clear the replacement turn's holder. - Note: because `prepareStep` runs before step 1, a change landing during PREPARING/startup is applied to the first provider request via the normal pending path — no separate consumption point in `streamWithHistory` is needed. ### Phase 4 — Frontend wiring (~12 LoC) `src/browser/contexts/ThinkingContext.tsx`, inside `setThinkingLevel` (`:219-238`), after `persistAgentAiSettings(...)`: ```ts if (props.workspaceId) { api?.workspace .setActiveTurnThinkingLevel({ workspaceId: props.workspaceId, thinkingLevel: level }) .catch(() => {/* best-effort: no active stream / transient IPC failure */}); } ``` - No renderer gating on "is streaming" — the backend no-ops cheaply when idle, and gating via `useWorkspaceState(workspaceId).canInterrupt` would break Storybook/test mounts of `ThinkingProvider` that have no registered store workspace. - Keybind path (`INCREASE_THINKING`/`DECREASE_THINKING`) already funnels through `setThinkingLevel`, so it inherits the behavior. - One-shot `/model+level` sends and `setReasoningMode` are untouched. ### Phase 5 — Tests (~150 LoC test code; not counted in product LoC) 1. `src/node/services/streamManager.test.ts` (follow the tool-search suite pattern: `spyOn(aiSdk, "streamText")`, capture `prepareStep` from `mock.calls[0][0]`, invoke directly): - pending override → `prepareStep` returns rebuilt `providerOptions`, `request.providerOptions` is replaced **in place** (same object identity, old provider-namespace keys gone), `pending` cleared, `applied` recorded, `onApplied` invoked (streamInfo.thinkingLevel updated). - initially-`undefined` providerOptions: request config normalizes to a stable object so `streamText` receives the same reference that later mutation targets (identity assertion). - rebuild returns `null` → no `providerOptions` in the step result, `pending` cleared (no retry storm), request options untouched. - accepted-but-no-next-step: pending set after the final step → stream ends normally, final metadata keeps the last **applied** (or original) level, no leak into a later stream. - empty-output retry / previousResponseId retry: after application, re-created stream still carries mutated options (assert on `streamInfo.request.providerOptions`). - model fallback: `prepare` receives `thinkingLevelOverride` equal to the pending/applied level; `nextRequest` reuses the **same holder object** and gets the fallback-bound closure attached before `createStreamResult` runs. 2. `src/node/services/aiService.test.ts`: closure behavior — clamps to floor then applies `resolveEffectiveThinkingLevel`, no-op when effective == current, skips xAI grok-4-1-fast off↔on, applies Anthropic transitions into/out of `xhigh` as plain rebuilds, produces merged extras identical in shape to the initial build for a plain level change. 3. `src/node/services/agentSession.*.test.ts` / `workspaceService.test.ts`: - `setActiveTurnThinkingLevel` → `{ accepted: false }` when idle (no holder) and for unknown workspace; `{ accepted: true }` while a turn is active; last-write-wins on consecutive calls (holder.pending reflects the last level). - holder lifecycle: created immediately after durable acceptance/options finalization (before emit/onAccepted/any await), identity-cleared on pre-stream failure / turn end / interrupt / dispose (a post-turn setter call returns `accepted: false`); an edit-preempted turn's cleanup does not clear the replacement turn's holder. - PREPARING window: turn durably accepted, **before** `setTurnPhase(PREPARING)` / `streamWithHistory` / stream registration → slider route writes `pending` → the turn's first provider request consumes it (assert via captured `prepareStep`); pre-stream failure after holder creation identity-clears the holder. - queued messages are NOT retroactively changed: queue a follow-up mid-turn, change the level, let the queue dispatch → the follow-up turn streams with its captured options (fresh holder, no inherited pending). 4. `src/browser/contexts/ThinkingContext.test.tsx`: `setThinkingLevel` fires `api.workspace.setActiveTurnThinkingLevel` with the workspaceId and level; NOT fired when the provider has no `workspaceId` (project/global scope). No tautological tests: every assertion above exercises branching (queued vs not, applied vs skipped, merge-deletion semantics), not copy. ### Phase 6 — Validation gates & dogfooding Local gates (run between phases, not only at the end): - After Phase 0: `bun test src/common/utils/ai/providerOptions.test.ts src/node/services/providerModelFactory.test.ts` + manual xhigh wire-bytes regression check (step 0 below). - After Phase 1–2: `bun test src/node/services/streamManager.test.ts src/node/services/aiService.test.ts` (targeted; QuickJS-heavy suites excluded as usual). - After Phase 3–4: `MUX_ESLINT_CONCURRENCY=1 make static-check` + `bun test src/browser/contexts/ThinkingContext.test.tsx`. Dogfooding (evidence required; use `dev-server-sandbox` skill + `agent-browser`): 0. **Phase 0 regression check**: on an Anthropic native-xhigh model at steady xhigh (no mid-turn change), capture one request in `devtools.jsonl` and diff `output_config.effort` + `thinking.display` against a pre-change capture — must match (`"xhigh"` / `"summarized"`), and the `x-mux-anthropic-effort` header must be absent from `update.requestHeaders`. Also capture one Opus/Sonnet 4.6 request at its top level — `effort` unchanged and no `display` field. 1. Start a sandboxed dev server (`dev-server-sandbox` skill); ensure a **direct** provider route (disable sandbox gateway block or set `routePriority: ["direct"]`) so `devtools.jsonl` shows wire-level requests. 2. Enable API Debug Logs. In a test workspace, send a prompt guaranteed to run several tool steps (e.g. "read these 3 files then summarize") with thinking = low. 3. While the agent is mid-tool, move the slider to high (screenshot the slider + streaming state via `agent-browser screenshot`). 4. After the turn: inspect `~/.mux/sessions/<ws>/devtools.jsonl` `step-update.update.rawRequest` entries — step N (pre-change) must show the low reasoning config, step N+1 the high config (e.g. OpenAI `reasoning.effort`, Anthropic `thinking.budget_tokens`/`output_config.effort`). Capture the two JSON excerpts. 5. Verify the persisted assistant message metadata (`chat.jsonl`) has `thinkingLevel: "high"` (last applied). 6. Repeat once on an Anthropic model for the off→high in-place-replacement case (key deletion path), and once for high→xhigh on a native-xhigh model (`output_config.effort` flips to `"xhigh"` mid-turn — the transition Phase 0 unblocked). 7. Mobile check (repo rule): capture a ~375px-wide viewport screenshot of the chat footer with the slider mid-turn — no layout change is expected, but narrow-viewport verification is mandatory for UI-adjacent changes. 8. Record a screen capture of the slider change mid-turn (ffmpeg x11grab per repo notes; compress with libvpx to fit the 10MB attach limit). If recording is environmentally blocked, document the exact blocker and rely on the step-by-step screenshots + devtools excerpts. 9. Attach all screenshots/video via `attach_file` and include the two rawRequest JSON excerpts in the summary. ## Risks & mitigations | Risk | Mitigation | | --- | --- | | SDK deep-merge can't delete keys (off-transitions) | In-place mutation of the live `request.providerOptions` object is authoritative; per-step return is only defense-in-depth. Unit test asserts old namespace keys are gone. | | SDK later snapshots/clones providerOptions at `streamText()` time | Per-step returned `providerOptions` still overrides conflicting leaves; add a code comment flagging the coupling for SDK upgrades (like the MCP OAuth note). | | Prompt-cache churn (Anthropic breakpoints / OpenAI implicit caching are level-independent; only reasoning params change) | No cache keys are touched by the rebuild (same `promptCacheKey`/scope captured). Verified: `buildProviderOptions` derives caching from workspace/scope, not level. | | `prepareProviderRequestMessages` used the send-time level for Anthropic reasoning-part replay | Mid-turn steps use the SDK's accumulated step messages, not re-prepared history; divergence only affects reasoning-part filtering of *old* history within the same turn. Accept + document as v1 limitation in a code comment. | | Fallback/retry paths dropping the override | Explicit carry-through (Phase 1.9 / Phase 2.3) + tests. | | Model-swap (grok off↔on) transitions silently doing the wrong thing | Explicit skip predicate + debug log; level then applies next turn exactly as today. | | Phase 0: SDK upgrade re-introduces effort gating for explicit provider options | Coupling comment at `ANTHROPIC_EFFORT` (like the MCP OAuth note); Phase 0 test asserts `effort: "xhigh"` survives to the request; wire-field regression check in dogfooding. | | Phase 0: mux-gateway rejects direct `xhigh`/`display` in provider options | Already receiving these exact values today via the wrapper rewrite of the gateway body shape — proven in production; regression check covers it. | | Phase 0: over-broad mapping sends `xhigh`/`display` to non-native models (Opus/Sonnet 4.6) | Model-aware gating on `anthropicSupportsNativeXhigh(capabilityModel)` in both `getAnthropicEffort` and the `display` field, mirroring today's header/wrapper conditions; both-sides-of-the-gate unit tests + 4.6 dogfooding capture. | | Slider change lands during PREPARING / AIService startup (stream not yet registered) and is lost | Session-owned per-turn holder created at turn start and threaded down by reference; `prepareStep` runs before step 1, so the first provider request already honors it (Phase 3.4). | | Initially-`undefined` providerOptions makes in-place mutation invisible to the SDK | Normalize to a stable object in `buildStreamRequestConfig` (Phase 1.4) + identity unit test. | | Override bleeding into queued follow-up turns | Holder is per-turn and cleared at turn end; queued messages dispatch with their own captured options + fresh holder. Regression test in Phase 5.3. | ## Net LoC estimate (product code) | Phase | Est. net LoC | | --- | --- | | 0 Remove xhigh wire hack | ~ −50 | | 1 StreamManager | ~65 | | 2 AIService | ~70 | | 3 ORPC/session holder lifecycle | ~55 | | 4 Frontend | ~12 | | **Total** | **~150 net** (upper bound ~200 with type plumbing overhead) | ## Acceptance criteria 1. Changing the thinking slider during an active multi-step turn changes the reasoning parameters of the next provider request within the same turn (verified in `devtools.jsonl`). 2. Changing the slider when idle behaves exactly as today (persisted settings only; route returns `accepted: false`). 3. A change during the PREPARING window (turn accepted, stream not yet started) applies to that turn's first model request. 4. Clamping: a mid-turn request below the model floor / above the ceiling applies the clamped effective level; a no-op change leaves the request untouched. 5. Anthropic transitions into/out of native `xhigh` apply mid-turn; steady-state requests are wire-field equivalent to pre-refactor (native-xhigh: `output_config.effort: "xhigh"` + `thinking.display: "summarized"`; non-native adaptive: unchanged `effort`, no `display`; no `x-mux-anthropic-effort` header anywhere). 6. The one excluded transition (xAI grok-4-1-fast off↔on) does not alter the in-flight stream and still applies on the next turn. 7. Final assistant message metadata records the last applied level; interrupted/error partials also carry it. 8. Empty-output retry, previousResponseId retry, and model fallback preserve the applied override. 9. All targeted tests + `make static-check` pass. </details> --- _Generated with `mux` • Model: `anthropic:claude-fable-5` • Thinking: `xhigh` • Cost: `$150.32`_ <!-- mux-attribution: model=anthropic:claude-fable-5 thinking=xhigh costs=150.32 -->
Summary
Adds first-class support for Claude Sonnet 5, released by Anthropic on June 30, 2026. The curated
SONNETmodel and baresonnetalias now resolve toanthropic:claude-sonnet-5, with matching capability wiring across context limits, thinking levels, provider request shaping, native web fetch, docs, mocks, and tests.Background
This follows the same built-in Sonnet bump pattern used for the earlier Sonnet 4.6 update: update the curated model entry, preserve legacy model metadata, refresh docs/defaults/mocks, and cover changed model behavior with targeted tests. Sonnet 5 uses the API id
claude-sonnet-5, has native 1M context, supports 128K max output, uses adaptive thinking with effort control including nativexhigh, and standard pricing is $3/M input and $15/M output.Implementation
KNOWN_MODELS.SONNETand docs/generated skill content to point atclaude-sonnet-5.web_fetchversion parsing so major-only IDs likeclaude-sonnet-5qualify while dated pre-4.6 IDs remain unsupported.Validation
bun test src/common/utils/ai/models.test.ts src/common/utils/ai/providerOptions.test.ts src/common/utils/thinking/policy.test.ts src/common/utils/tools/tools.test.ts src/node/services/providerModelFactory.test.ts src/node/services/providerService.test.ts src/node/services/tools/task.test.ts src/browser/utils/slashCommands/parser.test.tsmake typecheckmake fmt-checkenv MUX_ESLINT_CONCURRENCY=1 make lintmake static-checkgit diff --checkRisks
Low. The behavioral changes are limited to the curated Sonnet alias and Sonnet 5 capability predicates. Legacy Sonnet 4.6 entries remain available for explicit model strings, and the new native web-fetch parser is covered for both major-only IDs and dated Claude 4 IDs.
Generated with
mux• Model:openai:gpt-5.5• Thinking:xhigh• Cost:$6.81