Skip to content

🤖 feat: integrate GPT-5.6 Sol/Terra/Luna with native max effort and pro-mode toggle#3708

Merged
ThomasK33 merged 35 commits into
mainfrom
mux-integration-xp2n
Jul 10, 2026
Merged

🤖 feat: integrate GPT-5.6 Sol/Terra/Luna with native max effort and pro-mode toggle#3708
ThomasK33 merged 35 commits into
mainfrom
mux-integration-xp2n

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

First-class integration of OpenAI's GPT-5.6 family (released July 9, 2026): Sol (flagship), Terra (balanced), and Luna (fast/cheap) — all with a 1.05M-token context window and 272K-threshold long-context pricing — become built-in models with gpt/sol, terra, and luna aliases; the bare gpt-5.5 entry is retired (GPT-5.5 Pro stays). On top of that, a per-workspace PRO reasoning-mode toggle delivers the Responses API's new reasoning.mode: "pro", available on every GPT-5.6 model.

Background

  • OpenAI launched GPT-5.6 with durable tier names and two new Responses API knobs: a native max reasoning effort above xhigh, and a reasoning.mode: "standard" | "pro" field orthogonal to effort ("Sol Pro" is the same model ID served in pro mode; per-token pricing unchanged). Early preview coverage called max Sol-only and pro Sol/Terra-only; the GA reasoning guide and API changelog confirm both are family-wide (Sol/Terra/Luna and the bare gpt-5.6 alias), and the GA model pages publish 1.05M contexts plus a 272K long-context pricing tier (2x input / 1.5x output for the full request, cache writes 1.25x the active input rate) — folded in during review round 8.
  • Follows the repo precedent from the gpt-5.5 promotion (797d5a254) and Sonnet 5 integration (e1169d7f5).

Implementation

Model family — registry entries (knownModels.ts), flat-rate pricing incl. 1.25x cache writes / 90%-off reads (models-extra.ts; no long-context tier published, unlike GPT-5.5), thinking policy (Sol: 6 levels incl. max; Terra/Luna: 5), Codex OAuth allow-list, docs + regenerated skill content. The legacy gpt-5.5 stats entry is retained so historical sessions keep pricing.

Sol-native max effort@ai-sdk/openai accepts the effort as a plain string, so a model-aware getOpenAIReasoningEffort() maps max -> "max" on Sol only (all other OpenAI models keep max -> "xhigh"), applied at both providerOptions call sites (direct + gateway).

Pro mode — the AI SDK drops unknown provider-option keys, so reasoning.mode rides the proven Anthropic-xhigh pattern: buildRequestHeaders emits a Mux-internal x-mux-openai-reasoning-mode header (passthrough routes only), and a strip-first fetch wrapper rewrites the JSON body (reasoning.mode: "pro", preserving effort/summary) on Responses-shaped requests. Codex OAuth routes get defense-in-depth: no injection at composition time AND normalizeCodexResponsesBody deletes the field (the ChatGPT backend is stricter; a silent standard-mode no-op beats a hard 4xx). Setting is an optional reasoningMode on workspace AI settings (legacy configs parse unchanged), threaded along the exact path thinkingLevel takes, including backend-initiated turns (startup retry, compaction, continuations, heartbeats). UI: a small PRO chip next to the thinking slider (pro-capable models only, hidden ≤420px container width) plus a "Toggle Pro Reasoning Mode" command-palette action.

Validation

  • Live wire verification via a dev-server sandbox with real provider keys and llmDebugLogs: the Responses API response.created event echoes the effective config — direct route with PRO on echoes "reasoning":{...,"mode":"pro"}; PRO off echoes "mode":"standard"; Sol at max thinking sends reasoningEffort: "max" and OpenAI accepts it. Cost telemetry priced a 9,987-token Sol turn at $0.05 ($5/M ✓); context meter shows 1M.
  • Known limitation: mux-gateway routing drops reasoning.mode (the gateway's own SDK filters unknown keys — same root cause as the client-side SDK). Pro mode currently only reaches the wire on direct-API-key routing; needs a gateway-side change to close.
  • UI dogfooding (agent-browser): PRO chip visible on GPT-5.6 models, absent on Opus; state persists across model switches and backend restarts; palette command toggles it; mobile 375px verified after fixing a right-edge overflow found during dogfooding (chip now hidden with the thinking paddles at narrow widths). (Dogfooding predates round 8: the chip originally rendered for Sol/Terra only; family-wide gating incl. Luna and the bare alias is covered by the openaiProModeAvailable unit table.)

Risks

  • Model routing/aliasing: the gpt alias now resolves to gpt-5.6-sol; users pinned to openai:gpt-5.5 are unaffected (stats retained), but bare-gpt users get the new model silently — intended, matches prior promotions.
  • Wire rewrite: the new OpenAI fetch wrapper parses/rewrites request bodies on a hot path. It is strip-first with byte-identical passthrough when the header is absent, gated to Responses-shaped POSTs, and covered by wrapper unit tests (merge/create/invalid-value/malformed-JSON/gateway-shape/Codex cases). Regression severity if wrong: OpenAI request failures — mitigated by the header only being emitted for pro-enabled GPT-5.6 workspaces.
  • Settings shape: reasoningMode is optional everywhere; wholesale-replace clobbering at updateAgentAISettings call sites was audited and fixed via merge-preserve.

📋 Implementation Plan — GPT-5.6 Sol/Terra/Luna family

Plan: Integrate OpenAI GPT-5.6 Sol / Terra / Luna into Mux

Context

Per the implementation brief (source material; launch facts are brief-derived, not independently verified): OpenAI released the GPT-5.6 family on July 9, 2026 — Sol (gpt-5.6-sol, flagship), Terra (gpt-5.6-terra, balanced), Luna (gpt-5.6-luna, fast/cheap). All are Responses-API models with vision, function calling, and reasoning; also served via Codex. Sol introduces a new native max reasoning effort (Sol only). Follow repo precedent from commits 797d5a254 (gpt-5.5 promotion / gpt-5.4 retirement) and e1169d7f5 (Sonnet 5).

Verified repo facts (via exploration):

  • src/common/constants/knownModels.ts: GPT entry (lines ~89–95) is openai / gpt-5.5, aliases ["gpt", "gpt-5.5"], warm: true, tokenizerOverride: "openai/gpt-5". GPT_PRO is gpt-5.5-pro. MODEL_ABBREVIATIONS is derived from entry aliases.
  • src/common/utils/tokens/models-extra.ts: "gpt-5.5" entry (lines ~216–240) uses tiered pricing (*_above_200k_tokens, tiered_pricing_threshold_tokens: 272000); entries conform to ModelData (Record<string, ModelData>), which includes cache_creation_input_token_cost.
  • src/common/utils/thinking/policy.ts: getExplicitThinkingPolicy strips provider/namespace prefixes via stripModelProviderPrefixes, then matches regexes like /^gpt-5\.(?:2|5)-pro(?!-[a-z])/ (negative lookahead tolerates -2026-04-23 date suffixes but rejects -mini-style variants). GPT-5.5 gets ["off","low","medium","high","xhigh"] via /^gpt-5\.(?:4|5)(?:-(?:mini|nano))?(?!-[a-z])/.
  • src/common/types/thinking.ts: THINKING_LEVELS = ["off","low","medium","high","xhigh","max"]. OPENAI_REASONING_EFFORT maps max -> "xhigh" for ALL OpenAI models (lines ~273–280). getThinkingDisplayLabel returns "XHIGH" for both xhigh/max on any openai:-prefixed model; Anthropic native-xhigh is detected by anthropicSupportsNativeXhigh(modelString) (lines ~229–238), the pattern to mirror. getThinkingOptionLabel derives from getThinkingDisplayLabel.
  • src/common/utils/ai/providerOptions.ts: two OPENAI_REASONING_EFFORT[effectiveThinking] call sites — direct OpenAI branch (~line 356) and the OpenAI-origin-via-gateway branch (~line 508, github-copilot options). modelString is in scope at both.
  • src/common/constants/codexOAuth.ts: CODEX_OAUTH_ALLOWED_MODELS (lines ~91–101), CODEX_OAUTH_REQUIRED_MODELS (only spark), CODEX_OAUTH_CONTEXT_WINDOW_OVERRIDES (only gpt-5.5: 272_000).
  • docs/config/models.mdx: built-in models table rows at lines ~21–28; prose at ~75–78 (/gpt-pro+0 → GPT-5.5 Pro minimum) refers to GPT_PRO and stays valid.
  • formatModelDisplayName("gpt-5.6-sol") already yields "GPT-5.6 Sol"no modelDisplay changes.
  • src/node/services/providerService.ts first-time mux-gateway model list includes openai/gpt-5.5 — out of scope, stays unchanged.
  • Precedent commit 797d5a254 also regenerated src/node/services/agentSkills/builtInSkillContent.generated.ts (docs snapshot embedded in built-in skills) — must re-run the generator after touching docs/config/models.mdx.

Non-goals (from the brief)

  • No Ultra mode / subagent orchestration; no explicit cache-breakpoints API (keep promptCacheKey).
  • No change to mux-gateway first-time defaults (providerService.ts), DEFAULT_MODEL (stays OPUS), gpt-5.4-mini/nano/codex retirements, or terminal-bench CI model configs.
  • No UI redesign — model picker and thinking slider derive from registries.
  • No PR, no push — local commit(s) only.

Implementation steps

0. Pre-implementation safety grep

  • Grep production code (src/, excluding tests) for gpt-5.5, OPENAI_REASONING_EFFORT, and GPT-5.x regexes to catch any model-gated helper the brief didn't list. Expected retained refs: models-extra.ts, GPT-5.5 Pro entries, providerService.ts first-time gateway defaults, comments.
  • Specifically check supportsOpenAIReasoningSummary (used in the OpenAI branch of providerOptions.ts) and any other GPT-5.x whitelist: if gpt-5.6 would miss intended existing GPT-5 behavior (e.g. reasoning summaries), extend the match — only where GPT-5.5 already gets the behavior and GPT-5.6 clearly should inherit it. Report anything ambiguous instead of guessing.

1. src/common/constants/knownModels.ts (~net +14 LoC)

  • Replace the GPT entry: keep key GPT (comment: "GPT alias tracks the latest stable GPT tier"), provider: "openai", providerModelId: "gpt-5.6-sol", aliases: ["gpt", "sol"], warm: true, tokenizerOverride: "openai/gpt-5".
  • Add GPT_56_TERRA (gpt-5.6-terra, aliases ["terra"], tokenizerOverride "openai/gpt-5", no warm) and GPT_56_LUNA (gpt-5.6-luna, aliases ["luna"], no warm), placed after the Sol entry, before GPT_PRO.
  • Doc comments in existing style: release date July 9, 2026; pricing ($5/$30, $2.50/$15, $1/$6 per M); context (1M / 1M / 400K launch values); note Sol-only native max reasoning effort; note the tokenizer reuse of openai/gpt-5 as an approximation (same as gpt-5.5 did).
  • The bare gpt-5.5 alias retires with the old entry (per 797d5a254 precedent); openai:gpt-5.5 keeps working as a custom model string because its stats stay in models-extra.
  • GPT_PRO unchanged (no GPT-5.6 Pro exists).
  • Assumption (matches brief verbatim): aliases do NOT include the full ids (gpt-5.6-sol etc.), unlike the old ["gpt", "gpt-5.5"] pattern — sol/terra/luna are the durable tier abbreviations.

2. src/common/utils/tokens/models-extra.ts (~net +60 LoC)

Add three flat-rate entries after "gpt-5.5" (keep "gpt-5.5" — legacy models remain), styled like it but WITHOUT tiered fields (no long-context tier published):

// GPT-5.6 Sol — Released July 9, 2026 (flagship tier).
// Launch-time docs: 1M context, flat pricing (no long-context tier unlike GPT-5.5).
// Max output not published; assume 128K matching the GPT-5.5 family.
// $5/M input, $30/M output; cache writes 1.25x input ($6.25/M), cache reads 90% off ($0.50/M).
"gpt-5.6-sol": {
  max_input_tokens: 1000000,
  max_output_tokens: 128000,
  input_cost_per_token: 0.000005,
  output_cost_per_token: 0.00003,
  cache_read_input_token_cost: 0.0000005,
  cache_creation_input_token_cost: 0.00000625,
  litellm_provider: "openai",
  mode: "chat",
  supports_function_calling: true,
  supports_vision: true,
  supports_reasoning: true,
  supports_response_schema: true,
},
  • Terra: 1,000,000 / 128,000; input 0.0000025, output 0.000015, cache read 0.00000025, cache creation 0.000003125.
  • Luna: 400,000 / 128,000; input 0.000001, output 0.000006, cache read 0.0000001, cache creation 0.00000125.
  • Omit knowledge_cutoff (not published — per brief, don't guess).

3. Thinking levels / reasoning effort (~net +30 LoC)

3a. src/common/utils/thinking/policy.ts

  • Add branches in getExplicitThinkingPolicy (before the gpt-5.2/5.4/5.5 branch):
// gpt-5.6-sol supports the new native "max" reasoning effort (Sol only).
if (/^gpt-5\.6-sol(?!-[a-z])/.test(withoutProviderNamespace)) {
  return ["off", "low", "medium", "high", "xhigh", "max"];
}
// gpt-5.6-terra / gpt-5.6-luna support the same 5 levels as gpt-5.5.
if (/^gpt-5\.6-(?:terra|luna)(?!-[a-z])/.test(withoutProviderNamespace)) {
  return ["off", "low", "medium", "high", "xhigh"];
}

The (?!-[a-z]) lookahead follows file convention: tolerates date suffixes (gpt-5.6-sol-2026-07-09), rejects hypothetical gpt-5.6-sol-mini. Prefix stripping is already handled by stripModelProviderPrefixes.

  • Update the getThinkingPolicyForModel doc-comment examples (add the two gpt-5.6 lines).

3b. src/common/types/thinking.ts

  • Add predicate mirroring anthropicSupportsNativeXhigh:
/** GPT-5.6 Sol ships a native "max" reasoning effort (Sol only; Terra/Luna top out at xhigh). */
export function openaiSupportsNativeMaxEffort(modelString: string): boolean {
  const withoutPrefix = stripModelProviderPrefixes(modelString);
  return /^gpt-5\.6-sol(?!-[a-z])/.test(withoutPrefix);
}
  • Add a small wrapper so both providerOptions call sites stay DRY:
export function getOpenAIReasoningEffort(level: ThinkingLevel, modelString: string): string | undefined {
  // Assumption: the wire value for GPT-5.6 Sol's new top effort is the string "max"
  // on the Responses API (launch docs name the effort "max"; not yet in the SDK types).
  if (level === "max" && openaiSupportsNativeMaxEffort(modelString)) return "max";
  return OPENAI_REASONING_EFFORT[level];
}
  • getThinkingDisplayLabel: inside the existing OpenAI branch, return "MAX" when level === "max" && openaiSupportsNativeMaxEffort(modelString); keep "XHIGH" otherwise. xhigh on Sol still displays "XHIGH" (distinct native efforts). getThinkingOptionLabel needs no change — it derives from the display label (Sol max"max", Sol xhigh"xhigh").

3c. src/common/utils/ai/providerOptions.ts

  • At both call sites (~356 and ~508), replace OPENAI_REASONING_EFFORT[effectiveThinking] with getOpenAIReasoningEffort(effectiveThinking, modelString). Pass the full modelString (NOT capModelName) — the helper strips provider/gateway prefixes itself. Update imports (OPENAI_REASONING_EFFORTgetOpenAIReasoningEffort) if the map is no longer referenced there.
  • No other behavior changes; non-Sol OpenAI models (incl. gpt-5.5-pro) keep max -> "xhigh".
  • If any SDK/provider-option type rejects the "max" string, widen the local type/union minimally (never as any) — today the effort flows as string | undefined, so no friction is expected.

4. src/common/constants/codexOAuth.ts (~net +3 LoC)

  • Add "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna" to CODEX_OAUTH_ALLOWED_MODELS.
  • Do NOT touch CODEX_OAUTH_REQUIRED_MODELS (public API exists) or CODEX_OAUTH_CONTEXT_WINDOW_OVERRIDES (no evidence of a lower ChatGPT-route cap).

5. docs/config/models.mdx + generated skill content

  • Replace the GPT-5.5 row with three rows:
    • GPT-5.6 Sol | openai:gpt-5.6-sol | \gpt`, `sol``
    • GPT-5.6 Terra | openai:gpt-5.6-terra | \terra``
    • GPT-5.6 Luna | openai:gpt-5.6-luna | \luna``
  • Keep GPT-5.5 Pro row. Prose at ~75–78 (/gpt-pro+0 → GPT-5.5 Pro) still valid — verify no other stale gpt-5.5 prose.
  • Built-in skill content: verify the exact generator command from repo tooling first (Makefile / scripts/gen_builtin_skills.ts797d5a254 regenerated builtInSkillContent.generated.ts after model changes), run it after the docs edit, and commit the generated file only if it diffs.

6. Tests (behavioral only, no tautologies)

Update breaking assertions and add new coverage:

File Action
src/common/utils/thinking/policy.test.ts Add: Sol → 6 levels incl. max (also with mux-gateway:openai/ prefix + date suffix); Terra/Luna → 5 levels; gpt-5.6-sol-mini-style non-match falls to default. Existing gpt-5.5/5.5-pro tests stay (models still matched).
src/common/types/thinking.test.ts Add: getThinkingDisplayLabel("max", "openai:gpt-5.6-sol")"MAX"; ("xhigh", sol)"XHIGH"; ("max", "openai:gpt-5.5-pro")"XHIGH" unchanged; option labels (max/xhigh on Sol); openaiSupportsNativeMaxEffort for prefixed/dated/-mini inputs.
src/common/utils/ai/providerOptions.test.ts Add: Sol at maxreasoningEffort: "max"; Sol at xhigh"xhigh"; gpt-5.5-pro at max"xhigh"; gateway path (github-copilot:gpt-5.6-sol at max) → "max" via second call site.
src/common/utils/tokens/modelStats.test.ts Add stats assertions for the three ids (pricing incl. cache creation, max_input_tokens); add a dated-fallback row (openai:gpt-5.6-sol-2026-07-09openai:gpt-5.6-sol) alongside the existing test.each cases; existing gpt-5.5 fallback tests still pass (entry retained). Line 18 uses KNOWN_MODELS.GPT.id — passes once models-extra entry exists.
src/common/constants/codexOAuth.test.ts Add: three ids allowed-but-not-required. Existing gpt-5.5 tests unaffected (stays in allowed set).
src/common/constants/knownModels.test.ts Inspect during implementation; update alias/id expectations if hardcoded. Assert MODEL_ABBREVIATIONS["gpt-5.5"] is undefined (alias retired) and gpt/sol/terra/luna map to the new ids.
src/common/routing/resolve.test.ts, src/browser/hooks/useModelsFromSettings.test.ts, tests/ui/config/customModels.test.ts, src/common/utils/ai/models.test.ts Expected to pass unchanged (openai:gpt-5.5 remains valid; dynamic KNOWN_MODELS.GPT.id refs auto-track). Run them; fix only actual breaks.

7. Format, validate, commit

  • make fmt, then targeted tests (below), then MUX_ESLINT_CONCURRENCY=1 make static-check.
  • Before committing, read the pull-requests skill via agent_skill_read (AGENTS.md requires this before any commit, PR or not) and follow its attribution/footer conventions for the commit message.
  • Commit locally with a message following 797d5a254 style (e.g. 🤖 promote gpt-5.6-sol as GPT default; add gpt-5.6 terra/luna; retire bare gpt-5.5). No PR, no push.

Quality gates between phases

  • After steps 1–2 (registry + stats): run the MODEL_ABBREVIATIONS and getModelStats bun -e smoke checks before moving on.
  • After step 3 (thinking/reasoning): run policy.test.ts, thinking.test.ts, providerOptions.test.ts.
  • After step 5 (docs): run the generator check + mintlify broken-links (part of static-check).
  • Dogfooding (below) runs after all code phases pass, before the final commit.

Validation

Targeted test runs (avoid the monolithic bun test src and QuickJS-heavy workflow tests):

bun test src/common/constants/knownModels.test.ts src/common/constants/codexOAuth.test.ts \
  src/common/types/thinking.test.ts src/common/utils/thinking/policy.test.ts \
  src/common/utils/tokens/modelStats.test.ts src/common/utils/ai/models.test.ts \
  src/common/utils/ai/providerOptions.test.ts src/common/routing/resolve.test.ts
bun test src/browser/hooks/useModelsFromSettings.test.ts tests/ui/config/customModels.test.ts
MUX_ESLINT_CONCURRENCY=1 make static-check   # includes typecheck via Make-prepared generated modules

Quick functional smoke checks:

bun -e 'import {MODEL_ABBREVIATIONS} from "./src/common/constants/knownModels.ts"; console.log(MODEL_ABBREVIATIONS["gpt"], MODEL_ABBREVIATIONS["sol"], MODEL_ABBREVIATIONS["terra"], MODEL_ABBREVIATIONS["luna"])'
# expect: openai:gpt-5.6-sol openai:gpt-5.6-sol openai:gpt-5.6-terra openai:gpt-5.6-luna

bun -e 'import {getModelStats} from "./src/common/utils/tokens/modelStats.ts"; console.log(getModelStats("openai:gpt-5.6-sol"))'
# expect: input 0.000005, output 0.00003, cache read 0.0000005, cache creation 0.00000625, max_input_tokens 1000000

bun -e 'import {formatModelDisplayName} from "./src/common/utils/ai/modelDisplay.ts"; console.log(formatModelDisplayName("gpt-5.6-sol"))'
# expect: GPT-5.6 Sol (no code change needed — sanity only)

Dogfooding (reviewable evidence)

The user-visible surfaces are the model picker and the thinking-level slider, both registry-derived. Produce screenshots via the dev-server sandbox + agent-browser (works headless here):

  1. Start an isolated instance per the dev-server-sandbox skill (temp MUX_ROOT, free ports).
  2. agent-browser open http://localhost:<port>; create/open a workspace.
  3. Recording (required): agent-browser record start before the picker/slider flow; record stop after and attach the webm. If recording is environmentally blocked, document the exact blocker and fall back to a step-by-step screenshot series. (Known quirk: record stop can hang — kill the daemon by PID if needed, the webm is still written.)
  4. Model picker: open the model selector, screenshot it showing GPT-5.6 Sol / Terra / Luna; verify typing sol, terra, luna, and gpt abbreviations resolve (screenshot at least gpt → GPT-5.6 Sol).
  5. Thinking slider: select openai:gpt-5.6-sol, screenshot the slider showing MAX as top level and XHIGH as the distinct level below; select Terra, screenshot slider topping out at XHIGH.
  6. Mobile check (required): resize to ~375px width (agent-browser viewport option) and re-screenshot the model picker and thinking slider — verify no overflow/clipping at narrow width per repo mobile-validation rule.
  7. Wire-level check without API keys: enable API Debug Logs ("llmDebugLogs": true) in the sandbox config, send a message with Sol at max (it will fail auth, but devtools.jsonl rawRequest should carry reasoningEffort: "max"); otherwise rely on the providerOptions unit tests and note why.
  8. Regression evidence: screenshot showing the picker still lists GPT-5.5 Pro; confirm openai:gpt-5.5 still resolves stats (UI context/pricing display or the bun -e check above).
  9. Attach all screenshots + recording with attach_file.

Acceptance criteria

  1. MODEL_ABBREVIATIONS: gptopenai:gpt-5.6-sol, solopenai:gpt-5.6-sol, terraopenai:gpt-5.6-terra, lunaopenai:gpt-5.6-luna; alias gpt-5.5 no longer present.
  2. getModelStats("openai:gpt-5.6-sol") → input 0.000005, output 0.00003, cache read 0.0000005, cache creation 0.00000625, max_input_tokens 1,000,000; Terra/Luna analogous (Luna 400K). getModelStats("openai:gpt-5.5") unchanged.
  3. Thinking policy: Sol = ["off","low","medium","high","xhigh","max"]; Terra/Luna = ["off","low","medium","high","xhigh"]; gpt-5.5-pro policy unchanged.
  4. Provider options: Sol@max → reasoningEffort: "max"; Sol@xhigh → "xhigh"; gpt-5.5-pro@max → "xhigh"; Anthropic paths untouched.
  5. Display labels: Sol max → "MAX", Sol xhigh → "XHIGH", other OpenAI models unchanged ("XHIGH" for both).
  6. Codex OAuth: three ids allowed, none required, no context-window overrides added.
  7. Docs table updated (3 new rows, GPT-5.5 row removed, GPT-5.5 Pro kept); built-in skill content regenerated if the generator embeds it.
  8. All targeted tests pass; MUX_ESLINT_CONCURRENCY=1 make static-check passes; make fmt run before committing; committed locally, no PR/push.

Risks / assumptions

  • Wire value "max": assumed string for the Responses API per launch reporting; noted in a code comment at the mapping site. If the AI SDK's typed reasoningEffort union rejects "max", pass it via the same string channel the existing "xhigh" uses (they're both plain strings today per OPENAI_REASONING_EFFORT: Record<ThinkingLevel, string | undefined>).
  • 128K max output: assumption matching GPT-5.5 family; commented in models-extra.
  • Alias shape ["gpt","sol"] (no full-id alias) follows the brief verbatim; openai:gpt-5.6-sol full model strings work regardless.
  • hasNative1MContext / supports1MContext (src/common/utils/ai/models.ts) return false for gpt-5.5 today despite its 1.05M window (Anthropic-beta-specific helpers); leaving gpt-5.6 out of them matches existing OpenAI behavior — no change.
  • Generated built-in skill content may or may not embed models.mdx; re-run the generator and commit only if it diffs.

Net LoC estimate (product code, excl. tests/docs/generated)

~+110 LoC: knownModels +14, models-extra +60 (3 entries + comments), policy.ts +10, thinking.ts +18, providerOptions.ts ±2, codexOAuth.ts +3.

📋 Implementation Plan — Pro-mode toggle

Plan: GPT-5.6 Pro-Mode Toggle (Option B — reasoning.mode: "pro" wire injection)

Summary

Add a per-workspace "PRO" reasoning-mode toggle for OpenAI GPT-5.6 Sol/Terra. Since @ai-sdk/openai@^3.0.62 drops unknown provider-option keys, the mode is delivered via the proven Mux-internal-header + fetch-wrapper body-rewrite pattern already used for Anthropic xhigh (x-mux-anthropic-effort): emit x-mux-openai-reasoning-mode: pro in buildRequestHeaders, then strip the header and merge reasoning.mode: "pro" into the JSON body in an OpenAI fetch wrapper in providerModelFactory.ts.

Net product-code estimate: ~330 LoC (breakdown per phase below; tests ~280 LoC additional, not counted).

Verified repo facts (from Explore reports)

  • buildRequestHeaders(modelString, muxProviderOptions, workspaceId, providersConfig, routeProvider, thinkingLevel)src/common/utils/ai/providerOptions.ts L579–626. Gates Anthropic xhigh header on origin === "anthropic" && routePassesHeaders && thinkingLevel === "xhigh" && anthropicSupportsNativeXhigh(modelString), where routePassesHeaders = resolveProviderOptionsNamespaceKey(origin, routeProvider) === origin. Call sites: src/node/services/aiService.ts ~L2343 (main) and ~L2624 (model-fallback prepare); thinkingLevel arrives via StreamMessageOptions (aiService.ts L216–227).
  • wrapFetchWithAnthropicCacheControl (src/node/services/providerModelFactory.ts L325–461) is the strip-and-rewrite template: early-return non-POST/non-string-body; read + delete x-mux header; parse JSON; handle direct shape (json.model, top-level fields) and gateway shape (json.prompt + json.providerOptions.anthropic, model via ai-model-id header); delete content-length; on JSON-parse failure still forward with the header stripped.
  • OpenAI fetch chain (openai block, ~L1211–1460), request-flow order (SDK-outward): webSocketTransport.fetchfetchWithOpenAICodexNormalization → devtools-header strip → network. normalizeCodexResponsesBody runs only when shouldRouteThroughCodexOauth (L1355–1372); shouldRouteThroughCodexOauth (L1242) is in scope where the chain is composed. CODEX_ALLOWED_PARAMS keeps top-level reasoning, so pruning would NOT remove a nested reasoning.mode — an explicit decision is required (we strip; see D3).
  • gpt-5.6-sol/gpt-5.6-terra/gpt-5.6-luna are in CODEX_OAUTH_ALLOWED_MODELS (not required) — the Codex route is reachable for Sol/Terra (src/common/constants/codexOAuth.ts L91–105).
  • mux-gateway:openai/... models are handled by a separate provider block (~L1693–1786) that composes wrapFetchWithAnthropicCacheControl only for Anthropic models; an OpenAI wrapper added only in the openai block would never see gateway requests, and the header would leak to the gateway. Gateway bodies are { prompt, providerOptions: { openai: {...} } } with model in the ai-model-id header.
  • Setting plumbing for thinkingLevel: ThinkingProvider (src/browser/contexts/ThinkingContext.tsx) owns state via usePersistedState(getThinkingLevelKey(scopeId)), hydrates fallback from workspace metadata, and persists via api.workspace.updateAgentAISettings({ workspaceId, agentId, aiSettings: { model, thinkingLevel } }). Send path: useThinkingLevel()useSendMessageOptionsbuildSendMessageOptions (src/browser/utils/messages/buildSendMessageOptions.ts) → SendMessageOptionsSchema (src/common/orpc/schemas/stream.ts L725–769, has optional thinkingLevel) → agentSession.ts (~L3601 enforceThinkingPolicy) → aiService.streamMessagebuildRequestHeaders.
  • Clobber hazard: api.workspace.updateAgentAISettings is called with a full { model, thinkingLevel } object from 4 sites: ThinkingContext.tsx L150, App.tsx L487 (model change), ChatInput/index.tsx L840 (send-time persistence), useCreationWorkspace.ts L528. The backend replaces aiSettingsByAgent[agentId] wholesale, so every caller must include reasoningMode or it gets wiped on model change / send.
  • UI: ThinkingSliderComponent renders in ChatInput/index.tsx (~L3268–3347) inside data-component="ThinkingSliderGroup"; active levels use var(--color-thinking-mode) / var(--color-thinking-mode-light) + font-weight ramp; shared Tooltip used throughout. Palette: src/browser/utils/commands/sources.ts (thinkingSetLevel pattern with CommandIds); keybind hints hidden on mobile via .mobile-hide-shortcut-hints.
  • Tests precedents: predicate tests in src/common/types/thinking.test.ts (openaiSupportsNativeMaxEffort describe, L70); header tests in src/common/utils/ai/providerOptions.test.ts (buildRequestHeaders describe, L968); wrapper tests in src/node/services/providerModelFactory.test.ts (L1563–1708, createCapturingFetch harness, direct + gateway body shapes); UI harness in tests/ui/agents/thinkingPersistence.test.ts (createAppHarness + selectModel). No existing schema test for workspaceAiSettings.ts.

Design decisions

  • D1 — Wire delivery: header + fetch-wrapper rewrite, mirroring Anthropic xhigh exactly (SDK drops unknown providerOptions keys, so providerOptions alone can't work). Header emitted only when origin === "openai" && routePassesHeaders && reasoningMode === "pro" && openaiSupportsProMode(modelString) — independent of thinkingLevel (mode is orthogonal to effort).
  • D2 — Wrapper placement (openai block): compose the new wrapper outermost (SDK-facing), wrapping webSocketTransport.fetch, so (a) the body rewrite happens before any transport decision, and (b) the x-mux header is stripped before the Codex/WS layers regardless of route. Codex normalization runs later in the request flow and keeps top-level reasoning, so ordering is safe.
  • D3 — Codex OAuth route (defense in depth): (a) pass { inject: false } to the wrapper when shouldRouteThroughCodexOauth is true (flag is in scope at composition time), AND (b) explicitly delete json.reasoning.mode inside normalizeCodexResponsesBody (runs only on Codex-OAuth routes), preserving effort/summary; if reasoning is left empty, delete the whole reasoning key — so Codex can never see reasoning.mode even if a future path emits it. Rationale (leave as code comment): the ChatGPT Codex backend is stricter than the public API and unknown nested reasoning fields risk a hard 4xx; a silent standard-mode no-op is strictly safer. Header is still stripped on that route.
  • D4 — mux-gateway route: also compose the wrapper in the mux-gateway block when modelId.startsWith("openai/") (mirrors the Anthropic conditional there). It strips the header (never leak upstream, even to our own gateway) and best-effort injects providerOptions.openai.reasoningMode = "pro" into the gateway body shape (creating providerOptions.openai if the body is otherwise gateway-shaped, i.e. has a prompt array), with a comment that gateway-side SDK translation of this key is unverified (the gateway's @ai-sdk/openai may drop it); dogfooding checks empirically via the Responses API echo. Gateway delivery is explicitly NOT part of the guaranteed-wire acceptance criteria.
  • D5 — Setting shape: reasoningMode?: "standard" | "pro", absent ⇒ standard. Type + Zod enum live in src/common/types/thinking.ts next to the predicate (mirrors ThinkingLevelSchema placement). Optional everywhere so legacy persisted configs parse unchanged (self-healing rule).
  • D6 — Frontend state ownership: extend ThinkingProvider with a sibling reasoningMode state (persisted at getReasoningModeKey(scopeId)), exposed via a useReasoningMode() hook — the provider already owns the aiSettings sync + pending machinery, so this is the minimal change (no new context). All 4 updateAgentAISettings call sites include the current reasoningMode to avoid the clobber hazard.
  • D7 — Backend-initiated turns: mechanical threading, no policy exception: the backend paths that already reconstruct { model, thinkingLevel } from persisted aiSettings/aiSettingsByAgent get reasoningMode threaded alongside, reading from the same settings object each path already uses: agentSession startup retry (~L1414), auto-compaction (~L1438/L3240), idle compaction (workspaceService ~L9907), goal/workflow continuations (~L9703), heartbeats (~L10297). This matches the brief's "same path as thinkingLevel" and avoids a surprising silent downgrade on restart-retry. Compaction naturally stays standard unless the user set pro on the compact agent's settings (it reads aiSettingsByAgent.compact), so no special-case policy is needed.
  • D8 — Keyboard access: one command-palette action ("Toggle Pro Reasoning Mode"), shown only when the active model supports pro mode. No new global keybind (palette is keyboard-accessible; avoids burning a chord for a two-model feature). No shortcut hint rendered, so nothing to hide on mobile.
  • D9 — Responses-only direct injection: pro mode is a Responses API field. The direct-body rewrite only applies when the request URL path is Responses-like (/responses); chat-completions requests get header-strip only, body untouched (blindly adding top-level reasoning there could hard-fail). Header values other than "pro" are treated as invalid: strip header, leave body unchanged.
  • D10 — Never leak, ever: the wrapper strips the x-mux header unconditionally — the strip happens before the non-POST / non-string-body / malformed-JSON early returns — so no request shape can forward it upstream.

Implementation steps

Phase 1 — Capability predicate + setting type (src/common/types/thinking.ts)

  1. Add next to openaiSupportsNativeMaxEffort (~L252):
    export const OPENAI_REASONING_MODES = ["standard", "pro"] as const;
    export type OpenAIReasoningMode = (typeof OPENAI_REASONING_MODES)[number];
    export const OpenAIReasoningModeSchema = z.enum(OPENAI_REASONING_MODES); // colocate with ThinkingLevelSchema
    export function openaiSupportsProMode(modelString: string): boolean {
      const withoutPrefix = stripModelProviderPrefixes(modelString);
      return /^gpt-5\.6-(?:sol|terra)(?!-[a-z])/.test(withoutPrefix);
    }
    Doc comment: pro mode = reasoning.mode: "pro" on the Responses API; Sol/Terra only (no Luna Pro); lookahead tolerates date suffixes (gpt-5.6-sol-2026-07-09), rejects named variants (-mini). If ThinkingLevelSchema actually lives elsewhere, colocate OpenAIReasoningModeSchema with it instead. (~15 LoC)

Phase 2 — Header constant + emission (src/common/utils/ai/providerOptions.ts)

  1. Add export const MUX_OPENAI_REASONING_MODE_HEADER = "x-mux-openai-reasoning-mode"; next to MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER (L40).
  2. Extend buildRequestHeaders with a 7th param reasoningMode?: OpenAIReasoningMode and emit:
    if (origin === "openai" && routePassesHeaders && reasoningMode === "pro" && openaiSupportsProMode(modelString)) {
      headers[MUX_OPENAI_REASONING_MODE_HEADER] = "pro";
    }
    Comment mirrors the Anthropic block: SDK drops unknown providerOptions keys; the OpenAI fetch wrapper strips this header and rewrites reasoning.mode on the wire; passthrough-route gating keeps it away from non-passthrough gateways (OpenRouter, github-copilot). (~15 LoC)

Phase 3 — Fetch wrapper + composition (src/node/services/providerModelFactory.ts)

  1. New exported wrapFetchWithOpenAIReasoningMode(baseFetch: typeof fetch, options?: { inject?: boolean }): typeof fetch, modeled on wrapFetchWithAnthropicCacheControl but strip-first (D10):
    • First: read MUX_OPENAI_REASONING_MODE_HEADER from init.headers; if absent → forward untouched (no parse, byte-identical). If present → delete it from a Headers copy; every path below forwards the stripped headers.
    • If non-POST or body is not a string → forward with stripped headers, body untouched.
    • If inject === false (Codex route, D3 comment) or header value ≠ "pro" (D9 defensive) → forward with stripped headers, body untouched.
    • Otherwise parse JSON:
      • Gateway shape (body has a prompt array): set providerOptions.openai.reasoningMode = "pro", creating providerOptions/openai records as needed (D4 comment: unverified gateway-side support).
      • Direct shape: only when the URL path is Responses-like (/responses, D9), deriving the pathname defensively from the string | URL | Request input (if it can't be determined, do NOT inject — strip-only): json.reasoning = { ...(isRecord(json.reasoning) ? json.reasoning : {}), mode: "pro" } — never clobber existing effort/summary. Non-Responses URLs: body untouched.
      • Delete content-length, re-serialize, forward.
    • catch on JSON parse: forward with stripped headers, body untouched.
      (~60 LoC)
  2. Compose in the openai block (~L1408–1420): const providerFetch = wrapFetchWithOpenAIReasoningMode(webSocketTransport.fetch, { inject: !shouldRouteThroughCodexOauth }); and pass to createOpenAI. (~5 LoC)
  3. Compose in the mux-gateway block (~L1709, next to the Anthropic conditional): when modelId.startsWith("openai/"), wrap fetchWithAutoLogout with wrapFetchWithOpenAIReasoningMode(..., { inject: true }). (~5 LoC)
    6b. normalizeCodexResponsesBody (~L759): if isRecord(json.reasoning), delete json.reasoning.mode (preserve effort/summary); if reasoning is then empty, delete json.reasoning entirely. D3 "why" comment. Codex-only by construction — this function runs solely on shouldRouteThroughCodexOauth requests (verified L1355–1372). (~7 LoC)

Phase 4 — Setting plumbing (schema → send options → aiService)

  1. src/common/orpc/schemas/workspaceAiSettings.ts: add reasoningMode: OpenAIReasoningModeSchema.optional() with a description noting it currently applies only to OpenAI GPT-5.6 Sol/Terra and is inert elsewhere. (~5 LoC)
  2. src/common/orpc/schemas/stream.ts SendMessageOptionsSchema (L725): add reasoningMode: OpenAIReasoningModeSchema.optional(). (~3 LoC)
  3. src/browser/utils/messages/buildSendMessageOptions.ts: add reasoningMode?: OpenAIReasoningMode to SendMessageOptionsInput and pass through. (~3 LoC)
  4. src/browser/hooks/useSendMessageOptions.ts: read useReasoningMode() and include in the built options. (~4 LoC)
  5. Backend: src/node/services/agentSession.ts (~L3601 region where options?.thinkingLevel is consumed): pass options?.reasoningMode into aiService.streamMessage. src/node/services/aiService.ts: add reasoningMode?: OpenAIReasoningMode to StreamMessageOptions (L216–227) and pass to both buildRequestHeaders call sites (~L2343 main and ~L2624 fallback-prepare; the predicate re-gates per fallback model). (~10 LoC)
    11b. Backend-initiated turns (D7, mechanical): wherever a path already reads thinkingLevel from persisted aiSettings/aiSettingsByAgent to build send/stream options, read reasoningMode from the same object and thread it alongside: agentSession startup retry (~L1414–1439), auto-compaction defaults (~L3240), workspaceService idle compaction (~L9907), goal/workflow continuations (~L9703), heartbeats (~L10297). No new resolution logic — same fallback order as thinkingLevel at each site. (~20 LoC)

Phase 5 — Frontend state + persistence

  1. src/common/constants/storage.ts: add getReasoningModeKey(scopeId)`reasoningMode:${scopeId}` next to getThinkingLevelKey (L177). (~7 LoC)
  2. src/browser/contexts/ThinkingContext.tsx: sibling state usePersistedState<OpenAIReasoningMode | null>(getReasoningModeKey(scopeId), null, { listener: true }); derived value falls back to workspace-metadata aiSettings (getWorkspaceAiSettingsFromMetadata) then "standard"; setReasoningMode mirrors setThinkingLevel (localStorage + markPendingWorkspaceAiSettings + updateAgentAISettings including { model, thinkingLevel, reasoningMode }). Export useReasoningMode() hook (new file src/browser/hooks/useReasoningMode.ts mirroring useThinkingLevel.ts, or colocated per existing pattern). Extend getWorkspaceAiSettingsFromMetadata to surface reasoningMode. (~45 LoC)
  3. Fix the clobber hazard at all 4 updateAgentAISettings call sites: ThinkingContext.tsx L150 (setThinkingLevel must include the current reasoningMode, and setReasoningMode the current thinkingLevel — step 13), App.tsx L487, ChatInput/index.tsx L840, useCreationWorkspace.ts L528 (read via hook where available, else readPersistedState(getReasoningModeKey(...)) — never raw localStorage). (~10 LoC)

Phase 6 — UI toggle + palette command

  1. New src/browser/components/ThinkingSlider/ProModeToggle.tsx (colocated with the slider it sits beside): self-contained; props = modelString (like ThinkingSliderComponent); returns null unless openaiSupportsProMode(modelString); renders a small PRO text button with aria-pressed, data-component="ProModeToggle"; inactive: var(--color-text-secondary)/fw 400; active: var(--color-thinking-mode)/fw 700 (matches the slider's MAX treatment); shared Tooltip ("Pro reasoning mode: slower, more thorough responses. Saved per workspace."); onClick toggles via useReasoningMode(). No emoji, no native title. (~55 LoC)
  2. Render in ChatInput/index.tsx inside/next to data-component="ThinkingSliderGroup" (~L3290): <ProModeToggle modelString={baseModel} />. Verify at ~375px container width that the toolbar doesn't overflow (the group is shrink-0; PRO adds ~3ch — check the @container breakpoints still behave; adjust hide rules only if overflow is observed). (~3 LoC)
  3. Palette: add CommandIds.toggleProReasoning() + entry in src/browser/utils/commands/sources.ts pushed only when openaiSupportsProMode(p.selectedWorkspaceState?.currentModel ?? ""); title: "Toggle Pro Reasoning Mode", subtitle showing current state; run calls a new p.onToggleReasoningMode(workspaceId) wired where onSetThinkingLevel is wired (App.tsx / palette host). (~40 LoC)

Phase 7 — Tests (behavioral; no tautologies)

  1. src/common/types/thinking.test.tsopenaiSupportsProMode truth table: openai:gpt-5.6-sol, openai:gpt-5.6-terra, mux-gateway:openai/gpt-5.6-sol, gpt-5.6-terra-2026-07-09 → true; openai:gpt-5.6-luna, openai:gpt-5.5-pro, openai:gpt-5.6-sol-mini → false.
  2. src/common/utils/ai/providerOptions.test.tsbuildRequestHeaders: header present for openai+pro+sol/terra direct and via mux-gateway routeProvider (passthrough); absent when mode "standard"/undefined, for luna, for anthropic origin + pro, and for non-passthrough routeProvider (openrouter / github-copilot).
  3. src/node/services/providerModelFactory.test.tswrapFetchWithOpenAIReasoningMode (reuse createCapturingFetch/parseSentBody), against a Responses-like URL (https://api.openai.com/v1/responses) unless noted:
    • direct body { model, reasoning: { effort, summary } } + header → reasoning === { effort, summary, mode: "pro" } and header stripped;
    • body without reasoning → object created with only mode;
    • no header → body byte-identical, no parse side effects;
    • non-POST with the header present → header stripped, body untouched (D10);
    • non-Responses URL (chat completions, e.g. /v1/chat/completions) + header → header stripped, body unchanged (D9);
    • header value other than "pro" → header stripped, body unchanged (D9);
    • gateway shape (prompt array, ai-model-id: openai/gpt-5.6-sol) → providerOptions.openai.reasoningMode === "pro" (including when providerOptions.openai is initially absent);
    • { inject: false } → header stripped, body unchanged (Codex decision);
    • malformed JSON + header → forwarded with header stripped.
      20b. normalizeCodexResponsesBody: body with reasoning: { effort, summary, mode: "pro" }mode removed, effort/summary preserved; body with reasoning: { mode: "pro" } only → no top-level reasoning key remains (D3).
  4. New src/common/orpc/schemas/workspaceAiSettings.test.ts — legacy { model, thinkingLevel } parses (no reasoningMode); { ..., reasoningMode: "pro" } parses; reasoningMode: "ultra" rejected.
  5. New tests/ui/agents/proModeToggle.test.ts (mirror thinkingPersistence.test.ts harness + selectModel): select openai:gpt-5.6-sol[data-component="ProModeToggle"] visible; click → active (aria-pressed="true") and survives model switch away/back (persistence); select openai:gpt-5.6-luna and an Anthropic model → toggle absent. If the palette command source is unit-testable in the harness (or as a pure-function test on sources.ts), assert the "Toggle Pro Reasoning Mode" entry appears only when the current model supports pro; otherwise cover via dogfood step 2.

Phase 8 — Docs

  1. docs/config/models.mdx: one short note — GPT-5.6 Sol/Terra support OpenAI pro reasoning mode via the PRO toggle next to the thinking slider (per-workspace; sends reasoning.mode: "pro"; same per-token pricing, more tokens/longer runs; not available for Luna or via non-passthrough gateways). Then run bun scripts/gen_docs.ts check && bun scripts/gen_builtin_skills.ts check --sync-mux-docs-skill and sync if flagged.

Validation

  • MUX_ESLINT_CONCURRENCY=1 make static-check (memory-safe lint concurrency).
  • D7 propagation checklist (prevents silent missed-path regressions): grep the final diff to confirm every persisted-settings read of thinkingLevel used to build send/stream options also threads reasoningMode from the same object with the same fallback order — agentSession ~L1414–1439 and ~L3240; workspaceService ~L9703, ~L9907, ~L10297. Add targeted assertions where those paths already have test harnesses; where they don't, the checklist + typecheck is the gate (do not build new harnesses just for this).
  • Targeted tests only (no monolithic bun test src, no QuickJS-heavy workflow tests):
    bun test src/common/types/thinking.test.ts \
             src/common/utils/ai/providerOptions.test.ts \
             src/node/services/providerModelFactory.test.ts \
             src/common/orpc/schemas/workspaceAiSettings.test.ts
    bun test tests/ui/agents/proModeToggle.test.ts
    bun test tests/ui/agents/thinkingPersistence.test.ts   # regression: shared context touched
    
  • Local commits only; no PR, no push (per brief).

Dogfooding (reviewable evidence)

Setup: make dev-server-sandbox (pins BACKEND_PORT/VITE_PORT, seeds real providers), set "llmDebugLogs": true in the sandbox config.json (picked up without restart). Drive with agent-browser; record a session (agent-browser record start/stop → webm) covering steps 2–4 and attach screenshots via attach_file.

  1. Visibility matrix (screenshots): model picker → gpt-5.6-sol (toggle visible), gpt-5.6-terra (visible), gpt-5.6-luna (hidden), an Anthropic model (hidden). Picker options render in a portal invisible to snapshot -i — select via agent-browser eval on [role=option], [cmdk-item] dispatching mousedown/mouseup/click, and verify state after each click.
  2. Persistence (screenshots): enable PRO on Sol → switch to Luna → back to Sol → PRO still active; reload page → still active. Also toggle via command palette (Ctrl+Shift+P → "Toggle Pro Reasoning Mode") and screenshot the palette entry.
  3. Wire check (key evidence): send a REAL short message on Sol with PRO on; then inspect sessions/<ws>/devtools.jsonl step-update entries:
    • update.requestHeaders contains x-mux-openai-reasoning-mode: pro (SDK layer),
    • update.rawResponse shows the Responses API echoing "mode": "pro" in its reasoning config — this empirically confirms on-wire delivery and settles the gateway-vs-direct question. If the sandbox routes via mux-gateway and the echo is missing, record it as a documented limitation (D4), not a failure — the direct-route rewrite is proven by the Phase-7 unit tests.
    • Repeat once with PRO off and confirm the header/echo are absent (regression check).
  4. Mobile: 375px-wide screenshot of the chat-input toolbar with the toggle visible and no right-edge overflow.

Acceptance criteria

  1. openaiSupportsProMode matches the exact truth table (step 18).
  2. PRO enabled on Sol ⇒ buildRequestHeaders output contains x-mux-openai-reasoning-mode, and the post-wrapper direct-route body contains reasoning: { ...existing effort/summary..., mode: "pro" } (unit-proven; dogfood-confirmed via devtools.jsonl where routing allows).
  3. PRO disabled, mode "standard", or unsupported model ⇒ wire body byte-identical to today; all pre-existing providerOptions / providerModelFactory tests pass unchanged.
  4. Codex-OAuth-routed requests never contain reasoning.mode (wrapper no-inject and normalizeCodexResponsesBody strip) and never leak the x-mux header; mux-gateway and chat-completions requests never leak the x-mux header; no request shape can forward the header upstream (strip-before-early-return).
  5. Toggle renders only for Sol/Terra; state persists per workspace (survives model switches and reload; not clobbered by model changes or sends — the 4 updateAgentAISettings call sites include it); palette command toggles it.
  6. Legacy persisted settings without reasoningMode parse unchanged (schema test).
  7. MUX_ESLINT_CONCURRENCY=1 make static-check passes; all targeted test files pass; docs generators clean.

Non-goals / known limitations

  • No new picker entries or pseudo-models; no pricing/models-extra changes; no ultra mode; no streaming/timeout redesign; no providers.jsonc modelParameters; no Anthropic-path changes; no PR/push.
  • Gateway-side (mux-gateway) delivery of reasoning.mode is best-effort and unverified (D4); direct-route delivery is the guaranteed path and the only one in the wire acceptance criteria.
  • Codex OAuth route intentionally strips pro mode (D3) — silent standard-mode no-op, never a hard failure.

Risks

  • Toolbar overflow at narrow widths: PRO adds a small fixed-width chip inside a shrink-0 group; step 16 + dogfood step 4 verify 375px explicitly.
  • buildRequestHeaders signature growth (7 positional params): stay consistent with existing style; converting to an options object is out of scope (would churn Anthropic tests).
  • Fallback models: fallback-prepare call site re-evaluates the predicate per candidate model, so pro never leaks onto unsupported fallbacks.
  • Stale-closure hazard in ThinkingContext (step 13/14): setThinkingLevel and setReasoningMode each need the other setting's current value for the updateAgentAISettings payload — read the current values at call time (not from a possibly stale render closure).
  • D7 discipline: keep backend threading purely mechanical (same settings object, same fallback order); do not invent new resolution policy, and do not build new backend test harnesses just for this — the propagation checklist + typecheck is the gate.

Plan reviewed with advisor (openai:gpt-5.5-pro, xhigh) across three rounds; final verdict: "fully approved as implementation-ready."


Generated with mux • Model: anthropic:claude-fable-5 • Thinking: xhigh • Cost: $287.02

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@mintlify

mintlify Bot commented Jul 9, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟢 Ready View Preview Jul 9, 2026, 9:51 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acb47ad1d7

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/browser/contexts/ThinkingContext.tsx Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f437558e8

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/browser/utils/messages/sendOptions.ts Outdated
Comment thread src/browser/components/ThinkingSlider/ProModeToggle.tsx Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f74e2c4651

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/providerOptions.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75ffab3124

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/models.ts Outdated
Comment thread src/browser/contexts/ThinkingContext.tsx Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

1 similar comment
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0cd3ee5d6b

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/models.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d8fe65c16

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/proMode.ts Outdated
Comment thread src/browser/contexts/WorkspaceContext.tsx Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51e82436c3

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/providerOptions.ts
Comment thread src/browser/features/ChatInput/index.tsx
@ThomasK33 ThomasK33 force-pushed the mux-integration-xp2n branch from 51e8243 to 0043870 Compare July 10, 2026 06:03
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0043870b08

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/proMode.ts Outdated
Comment thread src/common/utils/ai/proMode.ts Outdated
Comment thread src/common/utils/ai/providerOptions.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

1 similar comment
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca2606390f

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/types/thinking.ts Outdated
Comment thread src/common/utils/tokens/models-extra.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 929ab42526

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/thinking/policy.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: baefa06202

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/types/thinking.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

…agent types

When a parent workspace stores per-agent AI settings, pro mode is toggled
on the parent's ACTIVE agent bucket, so spawning a task with a different
agentId (e.g. exec parent -> explore child) resolved the child bucket and
dropped the user's pro choice. Fall back to the parent's persisted
selected-agent bucket (default exec), then legacy workspace settings.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16823b59c7

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/node/services/aiService.ts
The continuation options built in onBackgroundRunTerminal copied the
turn's model/thinkingLevel/providerOptions but not reasoningMode, so a
pro-mode GPT-5.6 turn that ran a dynamic workflow resumed in standard
mode.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 847231a4ca

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/common/utils/ai/providerOptions.ts
Comment thread src/node/services/taskService.ts
The workspace-turn path only read the exec bucket + legacy settings for
reasoningMode, so a turn launched while a non-exec parent agent had pro
enabled silently dropped to standard. Fall back to the parent's
active-agent bucket, mirroring resolveTaskAISettings.

Also clarify the chat-completions effort comment: "none" is a
first-class member of @ai-sdk/openai's Chat Completions enum (only
"max" is missing), so it must NOT be stripped there — omitting it
would default GPT-5.6 back to medium reasoning.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5f12ca0fb

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/node/services/taskService.ts Outdated
resolveTaskAISettings clamped inherited/requested thinking levels without
the providers config, so mapped GPT-5.6 aliases (mappedToModel, e.g.
openai:team-sol -> gpt-5.6-sol) fell back to the default four-level
ladder and max/xhigh tasks were silently downgraded to high before
sending and persisting. Thread aiService.getProvidersConfig() through
the clamp and the numeric override resolver (resolveThinkingInput now
accepts providersConfig), including the workspace-turn path.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77f0ce5db5

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/node/services/taskService.ts Outdated
Comment thread src/common/orpc/schemas/stream.ts
Plan -> Exec handoff read only legacy workspace.aiSettings for
reasoningMode, but a PRO toggle during the plan phase persists under
aiSettingsByAgent.plan — resolve the still-current plan agent's bucket
first (red/green regression test included).

ACP sessions built prompt options with only model/thinkingLevel/agentId,
so workspaces with reasoningMode: "pro" ran every ACP prompt in
standard mode. ResolvedAiSettings now carries the optional field
(populated from workspace metadata buckets in refreshSessionState) and
all ACP send sites (prompt, skill, fork/new start messages, compaction
options + follow-up) thread it through.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26abe11575

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/node/services/providerModelFactory.ts Outdated
When the wrapper was called as fetch(new Request(...)) with headers/body
on the Request and no init, it saw an empty header set, took the fast
path, never injected reasoning.mode, and let the internal
x-mux-openai-reasoning-mode header leak upstream inside the Request.
Read headers/method/body from the same source the underlying fetch uses
(init members override the Request's per fetch semantics; Request bodies
are read from a clone), and pass the stripped header set via init so it
overrides Request-input headers on every forwarding path.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 8eb5dda3b9

ℹ️ 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".

@ThomasK33 ThomasK33 added this pull request to the merge queue Jul 10, 2026
Merged via the queue into main with commit 3c0faa1 Jul 10, 2026
56 of 60 checks passed
@ThomasK33 ThomasK33 deleted the mux-integration-xp2n branch July 10, 2026 13:10
mux-bot Bot added a commit that referenced this pull request Jul 10, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 11, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 11, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 12, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 13, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 13, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
mux-bot Bot added a commit that referenced this pull request Jul 14, 2026
Both getThinkingPolicyForModel and hasExplicitThinkingPolicy inlined the
identical getExplicitThinkingPolicy(resolveModelForMetadata(model, providersConfig ?? null))
call after #3708 added alias resolution to each. Extract a private
getExplicitThinkingPolicyForModel helper; behavior-preserving.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant