🤖 feat: update default model to Claude Opus 4.7#3180
Conversation
- Update OPUS model from claude-opus-4-6 to claude-opus-4-7 - Add native xhigh effort level support (between high and max) - Add display: summarized for Opus 4.7 adaptive thinking - Add all 6 thinking levels for Opus 4.7 (off/low/med/high/xhigh/max) - Add model-aware effort mapping (xhigh -> native xhigh on 4.7, max on 4.6) - Add pricing entry and native 1M context support for Opus 4.7 - Update workflows, docs, and generated files
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bfc68e29dc
ℹ️ 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".
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
The @ai-sdk/anthropic Zod schema rejects 'xhigh' as an effort value and strips unknown keys on the thinking object, so we can't emit Opus 4.7's native xhigh effort or the required display: 'summarized' through providerOptions. Handle both in the Anthropic fetch wrapper: - Always wrap Anthropic fetch (direct + gateway) so Opus 4.7 wire transforms run even when disableBetaFeatures skips cache_control injection - Inject thinking.display='summarized' for any adaptive thinking request targeting claude-opus-4-7 or newer - Emit x-mux-anthropic-effort header from buildRequestHeaders when user selected xhigh ThinkingLevel on Opus 4.7; fetch wrapper strips the header and rewrites output_config.effort to 'xhigh' before sending to Anthropic
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de07765ead
ℹ️ 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 three test fixtures from claude-opus-4-6 to claude-opus-4-7 (cli run default help, useCreationWorkspace pendingStreamModel, useModelsFromSettings Copilot dot-form ID, slash command E2E) - Deduplicate THINKING_LABELS_LIST in CLI help (xhigh/max both map to MAX as default display label; was producing duplicated 'MAX, MAX') - Match any claude-opus variant in nightly-terminal-bench workflow so dispatched 4.6 runs keep xhigh thinking (addresses Codex review)
|
@codex review |
… support future Opus versions - Gate MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER emission on routePassesHeaders so non-passthrough gateways (OpenRouter, Bedrock) don't receive this Mux-internal header and forward it externally - Extend anthropicSupportsNativeXhigh regex to match Opus 4.7+ including future versions (4.8..4-99, Opus 5+) so xhigh doesn't silently collapse to max on upcoming models - Use the helper consistently in policy.ts and providerOptions.ts - Update providerModelFactory display injection regex to match same range - Add tests: hypothetical Opus 4.8, openrouter non-passthrough gateway, policy coverage for future Opus versions
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e06f8aa3fa
ℹ️ 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".
The AI SDK gateway wraps thinking/effort inside providerOptions.anthropic.*
instead of the direct Anthropic wire format, and identifies the model via
the ai-model-id header. Our fetch wrapper now patches both shapes:
- Direct Anthropic: top-level thinking / output_config.effort
- AI SDK gateway: providerOptions.anthropic.{thinking,effort}
Also added debug logs so users can verify display injection in action.
Adds test coverage for both wire shapes (5 direct + 2 gateway).
The mux-gateway path uses a different body shape than direct Anthropic:
{ prompt, providerOptions: { anthropic: { thinking, effort } } }
with the model exposed via the ai-model-id header rather than `model`
in the body. The fetch wrapper previously only touched the direct Anthropic
body, so thinking.display was never injected and effort was never rewritten
when routing through mux-gateway. This likely explains missing thinking
traces when using Opus 4.7 via the gateway.
Changes:
- Detect Opus 4.7+ via either `body.model` OR `ai-model-id` header
- Inject thinking.display=summarized into providerOptions.anthropic.thinking
for gateway requests in addition to the direct path
- Rewrite providerOptions.anthropic.effort to xhigh for gateway requests
when the override header is present
- Export wrapFetchWithAnthropicCacheControl + add unit tests covering both
body shapes (direct + gateway) for display injection, effort override,
header stripping, and backwards-compat with Opus 4.6
- Add debug log when display is injected to aid diagnosing thinking-trace
issues in live sessions
|
@codex review |
getThinkingDisplayLabel used a hardcoded 'opus-4-7' substring check, which would silently collapse xhigh/max to MAX for future Opus versions (4.8+, 5+) even though anthropicSupportsNativeXhigh already recognizes them. Use the shared helper so UI labels stay consistent with policy and header logic.
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ 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 Adds first-class support for [Claude Opus 4.8](https://www.anthropic.com/news/claude-opus-4-8) (released May 28, 2026) and promotes it to Mux's default `opus`/`OPUS` model in place of Opus 4.7. ## Background Anthropic shipped Opus 4.8 today as a same-shape successor to Opus 4.7: - **Identical pricing**: $5 / $25 per million input/output tokens (cache write $6.25, cache read $0.50). - **Identical envelope**: native 1M context window, 128K max output. - **Same effort/thinking surface**: `effort` defaults to `high`; `xhigh` and `max` remain distinct API effort values. - **API ID**: `claude-opus-4-8` (dateless, pinned snapshot — same convention as 4.6/4.7). Mux already routes Opus 4.7+ through the right wire-format transforms via regex (`anthropicSupportsNativeXhigh()` matches `claude-opus-4-(?:[7-9]|\d{2,})`), so the xhigh effort header rewrite and `thinking.display: "summarized"` injection apply to 4.8 automatically. This PR is therefore mostly a model-id bump plus new pricing metadata. ## Implementation ### Model registry & pricing - `knownModels.ts`: `OPUS` → `claude-opus-4-8` (alias `opus`, default model). Tokenizer override stays on `anthropic/claude-opus-4.5` since the upstream tokenizer hasn't published a newer encoding and tokenization is unchanged across the 4.x line. - `models-extra.ts`: added `claude-opus-4-8` capability/pricing entry mirroring 4.7 (1M input, 128K output, full reasoning/vision/PDF/tool support). - `models.ts`: added `claude-opus-4-8` to `ANTHROPIC_NATIVE_1M_PATTERNS` so the context window is treated as native (not beta-gated). ### Defaults & seed data - `providerService.ts`: first-time mux-gateway seeding now lists `anthropic/claude-opus-4-8` (was 4-7). - CI workflows: `auto-cleanup`, `auto-cleanup-fixup`, `nightly-terminal-bench`, and `terminal-bench` all reference the new default Opus ID. ### Tests & docs - Updated assertions that pinned the Opus 4.7 default (`cli/run.test.ts`, `providerService.test.ts`, `slashCommands.spec.ts` Playwright spec). - Regenerated `docs/config/models.mdx` and `docs/guides/github-actions.mdx` via `bun scripts/gen_docs.ts`. - Regenerated `builtInSkillContent.generated.ts`. The existing `policy.test.ts` and `providerOptions.test.ts` already contained "hypothetical Opus 4.8" guard tests that exercise the regex-based detectors — they pass unchanged and now exercise real production behaviour. ## Validation - `make typecheck` - `make static-check` - `bun test` on the touched suites: `knownModels`, `models`, `modelDisplay`, `policy`, `providerOptions`, `providerService`, `cli/run`, `StreamingMessageAggregator`. ## Risks Low. The wire-format risk (`xhigh` effort + `summarized` thinking display) was paid down in #3180 by detecting Opus 4.7+ via regex rather than literal model IDs, so 4.8 inherits those code paths without any branch changes. Pricing is identical to 4.7. The main user-visible effect is that anyone on the `opus` alias automatically routes to the new model — which is the intended behaviour. --- _Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `xhigh`_ <!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=xhigh -->
…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
Updates the default Opus model from
claude-opus-4-6toclaude-opus-4-7, adds support for the new nativexhigheffort level, and enablesdisplay: "summarized"for Opus 4.7's adaptive thinking to receive summarized thinking content.Background
Anthropic released Claude Opus 4.7 on April 16, 2026. Key changes relevant to Mux:
xhigheffort level: Opus 4.7 introduces a nativexhigh("extra high") effort level in the API betweenhighandmax, giving finer control over reasoning vs latency. On Opus 4.6, ourxhighThinkingLevel mapped to"max"effort (they were synonyms). On Opus 4.7,xhighandmaxare distinct API-level effort values.display: "summarized"on the thinking configuration to receive thinking content in responses. Without this, thinking blocks would be empty.Implementation
Model registry & pricing
knownModels.ts: OPUS entry →claude-opus-4-7with tokenizer override (upstream tokenizer not yet available)models-extra.ts: New pricing entry forclaude-opus-4-7(same pricing as 4.6)models.ts: Added native 1M context pattern for Opus 4.7Thinking level support (all 6 levels)
policy.ts: Opus 4.7 gets["off", "low", "medium", "high", "xhigh", "max"]— all 6 levels, sincexhighandmaxare distinct effort levels on this modelthinking.ts:AnthropicEffortLevelnow includes"xhigh".getAnthropicEffort()is model-aware: returns native"xhigh"for Opus 4.7,"max"for Opus 4.6 (backward compatible)Provider options
providerOptions.ts: Opus 4.7 uses adaptive thinking withdisplay: "summarized"to receive thinking content. AddedAnthropicProviderOptionsExtendedtype to accommodate the SDK not yet typing"xhigh"effort or"display"on adaptive thinking.Workflows & docs
auto-cleanup,nightly-terminal-bench) to useopus-4-7Risks
Low risk — this is a straightforward model version bump. The main subtlety is the
display: "summarized"requirement for Opus 4.7 thinking content and the newxhigheffort level. Both are additive and backward-compatible (Opus 4.6 paths are unchanged). TheAnthropicProviderOptionsExtendedtype will need cleanup once the SDK adds nativexhigheffort support.Generated with
mux• Model:anthropic:claude-opus-4-6• Thinking:xhigh• Cost:$1.69