Skip to content
5 changes: 4 additions & 1 deletion docs/config/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ Mux ships with curated models kept up to date with the frontier. Use any custom
| Opus 4.8 | anthropic:claude-opus-4-8 | `opus` | ✓ |
| Sonnet 5 | anthropic:claude-sonnet-5 | `sonnet` | |
| Haiku 4.5 | anthropic:claude-haiku-4-5 | `haiku` | |
| GPT-5.5 | openai:gpt-5.5 | `gpt`, `gpt-5.5` | |
| GPT-5.5 | openai:gpt-5.5 | `gpt-5.5` | |
| GPT-5.5 Pro | openai:gpt-5.5-pro | `gpt-pro`, `gpt-5.5-pro` | |
| GPT-5.6 Sol | openai:gpt-5.6-sol | `gpt`, `sol`, `gpt-5.6-sol` | |
| GPT-5.6 Terra | openai:gpt-5.6-terra | `terra`, `gpt-5.6-terra` | |
| GPT-5.6 Luna | openai:gpt-5.6-luna | `luna`, `gpt-5.6-luna` | |
| GPT-5.4 Mini | openai:gpt-5.4-mini | `gpt-mini` | |
| GPT-5.4 Nano | openai:gpt-5.4-nano | `gpt-nano` | |
| Codex 5.3 | openai:gpt-5.3-codex | `codex`, `codex-5.3` | |
Expand Down
3 changes: 3 additions & 0 deletions src/common/config/schemas/providersConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export const AnthropicProviderConfigSchema = BaseProviderConfigSchema.extend({

export const OpenAIProviderConfigSchema = BaseProviderConfigSchema.extend({
serviceTier: ServiceTierSchema.optional(),
// GPT-5.6 pro mode (reasoning.mode: "pro"). Provider-level intent; requests are
// still gated per-model to the GPT-5.6 family in buildRequestHeaders.
reasoningMode: z.enum(["default", "pro"]).optional(),
organization: z.string().optional(),
codexOauthDefaultAuth: CodexOauthDefaultAuthSchema.optional(),
codexOauth: z.record(z.string(), z.unknown()).optional(),
Expand Down
4 changes: 4 additions & 0 deletions src/common/constants/codexOAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export const CODEX_OAUTH_ALLOWED_MODELS = new Set<string>([
"gpt-5.2",
"gpt-5.4-mini",
"gpt-5.5",
// GPT-5.6 tiers ship in both Codex and the public API (GA July 9, 2026).
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.2-codex",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
Expand Down
35 changes: 33 additions & 2 deletions src/common/constants/knownModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ const MODEL_DEFINITIONS = {
aliases: ["haiku"],
tokenizerOverride: "anthropic/claude-3.5-haiku",
},
// GPT alias tracks the latest stable GPT-5 tier.
// Previous flagship tier; kept selectable via its explicit id alias.
// The bare `gpt` alias moved to GPT_56_SOL when GPT-5.6 went GA (July 9, 2026).
GPT: {
provider: "openai",
providerModelId: "gpt-5.5",
aliases: ["gpt", "gpt-5.5"],
aliases: ["gpt-5.5"],
warm: true,
tokenizerOverride: "openai/gpt-5",
},
Expand All @@ -101,6 +102,36 @@ const MODEL_DEFINITIONS = {
warm: true,
tokenizerOverride: "openai/gpt-5",
},
// GPT-5.6 family - previewed June 26, 2026; generally available July 9, 2026 across
// ChatGPT, Codex, and the API (verified callable via plain API keys). New naming
// system: the number is the generation, while Sol/Terra/Luna are durable capability
// tiers. Cache writes bill at 1.25x input; cache reads keep the 90% discount.
// All tiers support a native "max" reasoning effort (see the thinking policy) and
// Responses API `reasoning.mode: "pro"` (see MUX_OPENAI_REASONING_MODE_HEADER).
// GPT-5.6 Sol - flagship tier. $5/M input, $30/M output. Bare `gpt` alias lives here:
// Sol succeeds gpt-5.5 as the flagship at the same list price.
GPT_56_SOL: {
provider: "openai",
providerModelId: "gpt-5.6-sol",
aliases: ["gpt", "sol", "gpt-5.6-sol"],
warm: true,
tokenizerOverride: "openai/gpt-5",
},
// GPT-5.6 Terra - balanced tier; GPT-5.5-competitive at half the cost.
// $2.50/M input, $15/M output.
GPT_56_TERRA: {
provider: "openai",
providerModelId: "gpt-5.6-terra",
aliases: ["terra", "gpt-5.6-terra"],
tokenizerOverride: "openai/gpt-5",
},
// GPT-5.6 Luna - fastest/cheapest tier. $1/M input, $6/M output.
GPT_56_LUNA: {
provider: "openai",
providerModelId: "gpt-5.6-luna",
aliases: ["luna", "gpt-5.6-luna"],
tokenizerOverride: "openai/gpt-5",
},
// GPT Mini alias tracks the latest stable GPT-5 mini tier.
GPT_54_MINI: {
provider: "openai",
Expand Down
8 changes: 8 additions & 0 deletions src/common/schemas/providerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export const MuxProviderOptionsSchema = z.object({
store: z.boolean().optional().meta({
description: "Whether OpenAI stores responses. Set false for zero data retention (ZDR).",
}),
reasoningMode: z
.enum(["default", "pro"])
.optional()
.meta({
description:
'OpenAI reasoning mode: "pro" performs more model work for reliability on difficult ' +
"tasks (GPT-5.6 family, Responses API only); default/unset uses standard reasoning",
}),
forceContextLimitError: z.boolean().optional().meta({
description: "Force context limit error (used in integration tests to simulate overflow)",
}),
Expand Down
33 changes: 33 additions & 0 deletions src/common/types/thinking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,36 @@ export function anthropicSupportsNativeXhigh(modelString: string): boolean {
);
}

/**
* GPT-5.6 family (Sol / Terra / Luna), including version-suffixed ids like
* `gpt-5.6-sol-2026-07-09`. Kept as one predicate so the two GPT-5.6-only
* capabilities below stay in sync.
*/
const GPT_56_FAMILY_REGEX = /^gpt-5\.6-(?:sol|terra|luna)(?!-[a-z])/;

/**
* Whether the given OpenAI model supports the native "max" reasoning effort
* (GPT-5.6 family, GA July 9, 2026). OpenAI recommends max effort "for demanding
* tasks that need more exploration and verification".
*
* Only valid on the Responses API path: the @ai-sdk/openai Responses schema
* accepts arbitrary effort strings, but the Chat Completions schema rejects
* anything outside its enum — callers must clamp to "xhigh" there.
*/
export function openaiSupportsNativeMaxEffort(modelString: string): boolean {
return GPT_56_FAMILY_REGEX.test(stripModelProviderPrefixes(modelString));
}

/**
* Whether the given OpenAI model supports `reasoning.mode: "pro"` on the
* Responses API (GPT-5.6 family). Pro mode performs more model work to improve
* reliability on difficult tasks and returns a single final answer — intended
* for when quality matters more than latency and token usage.
*/
export function openaiSupportsProReasoningMode(modelString: string): boolean {
return GPT_56_FAMILY_REGEX.test(stripModelProviderPrefixes(modelString));
}

/**
* Whether the given Anthropic model rejects `thinking: { type: "disabled" }`.
*
Expand Down Expand Up @@ -276,6 +306,9 @@ export const OPENAI_REASONING_EFFORT: Record<ThinkingLevel, string | undefined>
medium: "medium",
high: "high",
xhigh: "xhigh", // Maps 1:1 to OpenAI's reasoning effort value
// Shared clamp for models without a native max effort. GPT-5.6 (see
// openaiSupportsNativeMaxEffort) overrides this to "max" in buildProviderOptions
// on the Responses API path.
max: "xhigh",
};

Expand Down
106 changes: 106 additions & 0 deletions src/common/utils/ai/providerOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
resolveProviderOptionsNamespaceKey,
ANTHROPIC_1M_CONTEXT_HEADER,
MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER,
MUX_OPENAI_REASONING_MODE_HEADER,
MUX_WORKSPACE_ID_HEADER,
} from "./providerOptions";

Expand Down Expand Up @@ -415,6 +416,45 @@ describe("buildProviderOptions - OpenAI", () => {
expect(openai!.parallelToolCalls).toBe(true);
});

describe("GPT-5.6 native max reasoning effort", () => {
test.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])(
"sends native max effort for %s on the Responses API",
(modelId) => {
const result = buildProviderOptions(`openai:${modelId}`, "max");
const openai = getOpenAIOptions(result);

expect(openai).toBeDefined();
expect(openai!.reasoningEffort).toBe("max");
}
);

test("clamps max to xhigh on the Chat Completions wire format", () => {
const result = buildProviderOptions("openai:gpt-5.6-sol", "max", undefined, undefined, {
openai: { wireFormat: "chatCompletions" },
});
const openai = getOpenAIOptions(result);

expect(openai).toBeDefined();
expect(openai!.reasoningEffort).toBe("xhigh");
});

test("keeps the max→xhigh clamp for non-GPT-5.6 models", () => {
const result = buildProviderOptions("openai:gpt-5.5", "max");
const openai = getOpenAIOptions(result);

expect(openai).toBeDefined();
expect(openai!.reasoningEffort).toBe("xhigh");
});

test("non-max levels stay on the shared mapping for GPT-5.6", () => {
const result = buildProviderOptions("openai:gpt-5.6-sol", "high");
const openai = getOpenAIOptions(result);

expect(openai).toBeDefined();
expect(openai!.reasoningEffort).toBe("high");
});
});

describe("store option", () => {
test("should include store: false when muxProviderOptions sets store to false", () => {
const result = buildProviderOptions("openai:gpt-5", "medium", undefined, undefined, {
Expand Down Expand Up @@ -1044,6 +1084,72 @@ describe("buildRequestHeaders", () => {
}
});

describe("GPT-5.6 pro reasoning mode header", () => {
for (const { name, model, options, routeProvider, expected } of [
{
name: "emits pro mode header for gpt-5.6-sol with reasoningMode pro",
model: "openai:gpt-5.6-sol",
options: { openai: { reasoningMode: "pro" } },
routeProvider: undefined,
expected: { [MUX_OPENAI_REASONING_MODE_HEADER]: "pro" },
},
{
name: "emits pro mode header for version-suffixed gpt-5.6-terra",
model: "openai:gpt-5.6-terra-2026-07-09",
options: { openai: { reasoningMode: "pro" } },
routeProvider: "openai",
expected: { [MUX_OPENAI_REASONING_MODE_HEADER]: "pro" },
},
{
name: "does not emit header for non-GPT-5.6 model even with reasoningMode pro",
model: "openai:gpt-5.5",
options: { openai: { reasoningMode: "pro" } },
routeProvider: undefined,
expected: undefined,
},
{
name: "does not emit header when reasoningMode is default",
model: "openai:gpt-5.6-sol",
options: { openai: { reasoningMode: "default" } },
routeProvider: undefined,
expected: undefined,
},
{
name: "does not emit header when reasoningMode is unset",
model: "openai:gpt-5.6-sol",
options: { openai: {} },
routeProvider: undefined,
expected: undefined,
},
{
name: "does not emit header on the Chat Completions wire format",
model: "openai:gpt-5.6-sol",
options: { openai: { reasoningMode: "pro", wireFormat: "chatCompletions" } },
routeProvider: undefined,
expected: undefined,
},
{
name: "does not emit header for gateway-routed GPT-5.6 (wrapper not applied there)",
model: "mux-gateway:openai/gpt-5.6-sol",
options: { openai: { reasoningMode: "pro" } },
routeProvider: "mux-gateway",
expected: undefined,
},
] as const) {
test(name, () => {
expect(
buildRequestHeaders(
model,
options as Parameters<typeof buildRequestHeaders>[1],
undefined,
undefined,
routeProvider
)
).toEqual(expected);
});
}
});

for (const { name, model, options, workspaceId, expected } of [
{
name: "should include X-Mux-Workspace-Id for non-Anthropic provider when workspaceId provided",
Expand Down
44 changes: 42 additions & 2 deletions src/common/utils/ai/providerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
GEMINI_THINKING_BUDGETS,
OPENAI_REASONING_EFFORT,
OPENROUTER_REASONING_EFFORT,
openaiSupportsNativeMaxEffort,
openaiSupportsProReasoningMode,
} from "@/common/types/thinking";
import { isGeminiFlashThinkingLevelModelName } from "@/common/utils/thinking/policy";
import { resolveModelForMetadata } from "@/common/utils/providers/modelEntries";
Expand All @@ -39,6 +41,16 @@ import { normalizeToCanonical, supports1MContext } from "./models";
*/
export const MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER = "x-mux-anthropic-effort";

/**
* Mux-internal request header that asks the OpenAI fetch wrapper to inject
* `reasoning.mode` into the Responses API body on the wire. The @ai-sdk/openai
* Responses schema has no mode field (it only builds reasoning.effort/summary),
* so — like the Anthropic effort override above — the option rides a header to
* the fetch wrapper, which rewrites the body and strips the header before the
* request leaves Mux. Value is the reasoning mode to inject (currently "pro").
*/
export const MUX_OPENAI_REASONING_MODE_HEADER = "x-mux-openai-reasoning-mode";

/**
* OpenRouter reasoning options
* @see https://openrouter.ai/docs/use-cases/reasoning-tokens
Expand Down Expand Up @@ -353,8 +365,6 @@ export function buildProviderOptions(

// Build OpenAI-specific options
if (formatProvider === "openai") {
const reasoningEffort = OPENAI_REASONING_EFFORT[effectiveThinking];

// Mux always sends the latest conversation history explicitly. OpenAI's
// previous_response_id is an alternative state-management path, not an additive one.
// Chaining it on top of explicit history double-counts prior turns and caused GPT-5.4
Expand All @@ -374,6 +384,15 @@ export function buildProviderOptions(
const truncationMode = openaiTruncationMode ?? "disabled";
const shouldSendReasoningSummary = supportsOpenAIReasoningSummary(capModelName);

// GPT-5.6 supports a native "max" reasoning effort for demanding tasks that need
// more exploration and verification. The Responses schema in @ai-sdk/openai accepts
// arbitrary effort strings, so "max" passes through unchanged; the Chat Completions
// schema enum rejects it, so that path keeps the shared max→"xhigh" clamp.
const reasoningEffort =
effectiveThinking === "max" && isResponses && openaiSupportsNativeMaxEffort(capModelName)
? "max"
: OPENAI_REASONING_EFFORT[effectiveThinking];

log.debug("buildProviderOptions: OpenAI config", {
reasoningEffort,
shouldSendReasoningSummary,
Expand Down Expand Up @@ -620,5 +639,26 @@ export function buildRequestHeaders(
headers[MUX_ANTHROPIC_EFFORT_OVERRIDE_HEADER] = "xhigh";
}

// GPT-5.6 pro mode (`reasoning.mode: "pro"`): more model work for reliability on
// difficult tasks, returning a single final answer — for when quality matters more
// than latency and token usage. The SDK cannot send reasoning.mode, so emit a
// Mux-internal header for the OpenAI fetch wrapper to rewrite the body on the wire.
//
// Gates:
// - direct OpenAI route only: unlike the Anthropic wrapper, the OpenAI fetch
// wrapper is not applied on passthrough gateways, so a header emitted there
// would leak to the gateway verbatim instead of being consumed.
// - Responses API only (reasoning.mode does not exist on Chat Completions).
// - GPT-5.6 family only; other models would reject the parameter.
if (
origin === "openai" &&
(routeProvider == null || routeProvider === "openai") &&
muxProviderOptions?.openai?.reasoningMode === "pro" &&
(muxProviderOptions.openai.wireFormat ?? "responses") === "responses" &&
openaiSupportsProReasoningMode(modelString)
) {
headers[MUX_OPENAI_REASONING_MODE_HEADER] = "pro";
}

return Object.keys(headers).length > 0 ? headers : undefined;
}
25 changes: 25 additions & 0 deletions src/common/utils/thinking/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,31 @@ describe("getThinkingPolicyForModel", () => {
]);
});

test.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])(
"returns all 6 levels including native max for %s",
(modelId) => {
expect(getThinkingPolicyForModel(`openai:${modelId}`)).toEqual([
"off",
"low",
"medium",
"high",
"xhigh",
"max",
]);
}
);

test("returns all 6 levels for gpt-5.6-sol behind mux-gateway with version suffix", () => {
expect(getThinkingPolicyForModel("mux-gateway:openai/gpt-5.6-sol-2026-06-26")).toEqual([
"off",
"low",
"medium",
"high",
"xhigh",
"max",
]);
});

test("returns 5 levels including xhigh for gpt-5.5", () => {
expect(getThinkingPolicyForModel("openai:gpt-5.5")).toEqual([
"off",
Expand Down
Loading
Loading