diff --git a/docs/config/models.mdx b/docs/config/models.mdx index 98f9c99733..18767e6176 100644 --- a/docs/config/models.mdx +++ b/docs/config/models.mdx @@ -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` | | diff --git a/src/common/constants/codexOAuth.ts b/src/common/constants/codexOAuth.ts index 25f8b71763..0583cf16a9 100644 --- a/src/common/constants/codexOAuth.ts +++ b/src/common/constants/codexOAuth.ts @@ -94,6 +94,10 @@ export const CODEX_OAUTH_ALLOWED_MODELS = new Set([ "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", diff --git a/src/common/constants/knownModels.ts b/src/common/constants/knownModels.ts index 8789161631..dc4818b00f 100644 --- a/src/common/constants/knownModels.ts +++ b/src/common/constants/knownModels.ts @@ -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", }, @@ -101,6 +102,37 @@ 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). + // The API also offers `reasoning.mode: "pro"`, not yet implemented in Mux (planned + // as a thinking-slider toggle; tracked in issue #3704). + // 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", diff --git a/src/common/types/thinking.test.ts b/src/common/types/thinking.test.ts index a80b9a32b4..1c310c327e 100644 --- a/src/common/types/thinking.test.ts +++ b/src/common/types/thinking.test.ts @@ -22,6 +22,16 @@ describe("getThinkingDisplayLabel", () => { expect(getThinkingDisplayLabel("max", "mux-gateway:openai/gpt-5.2")).toBe("XHIGH"); }); + test("keeps xhigh and max distinguishable on GPT-5.6 (native max effort)", () => { + expect(getThinkingDisplayLabel("xhigh", "openai:gpt-5.6-sol")).toBe("XHIGH"); + expect(getThinkingDisplayLabel("max", "openai:gpt-5.6-sol")).toBe("MAX"); + expect(getThinkingDisplayLabel("max", "openai:gpt-5.6-terra")).toBe("MAX"); + expect(getThinkingDisplayLabel("max", "mux-gateway:openai/gpt-5.6-luna")).toBe("MAX"); + // Option labels (settings dropdowns) must diverge too + expect(getThinkingOptionLabel("xhigh", "openai:gpt-5.6-sol")).toBe("xhigh"); + expect(getThinkingOptionLabel("max", "openai:gpt-5.6-sol")).toBe("max"); + }); + test("returns MAX for xhigh/max when no model specified (default)", () => { expect(getThinkingDisplayLabel("xhigh")).toBe("MAX"); expect(getThinkingDisplayLabel("max")).toBe("MAX"); diff --git a/src/common/types/thinking.ts b/src/common/types/thinking.ts index 0ceb2b1d86..333a680ea8 100644 --- a/src/common/types/thinking.ts +++ b/src/common/types/thinking.ts @@ -38,8 +38,13 @@ export function getThinkingDisplayLabel(level: ThinkingLevel, modelString?: stri const normalized = modelString.trim().toLowerCase(); const withoutPrefix = normalized.replace(/^[a-z0-9_-]+:\s*/, ""); - // OpenAI: both xhigh and max resolve to "xhigh" reasoning effort - if (normalized.startsWith("openai:") || withoutPrefix.startsWith("openai/")) return "XHIGH"; + if (normalized.startsWith("openai:") || withoutPrefix.startsWith("openai/")) { + // GPT-5.6 has a native "max" reasoning effort distinct from xhigh, so both + // labels must stay distinguishable in settings dropdowns and the slider. + if (level === "max" && openaiSupportsNativeMaxEffort(modelString)) return "MAX"; + // Other OpenAI models: both xhigh and max resolve to "xhigh" reasoning effort + return "XHIGH"; + } // Anthropic Opus 4.7+: xhigh is a distinct effort level from max if (level === "xhigh" && anthropicSupportsNativeXhigh(modelString)) return "XHIGH"; @@ -237,6 +242,24 @@ export function anthropicSupportsNativeXhigh(modelString: string): boolean { ); } +/** + * Whether the given OpenAI model supports the native "max" reasoning effort + * (GPT-5.6 family — Sol/Terra/Luna, GA July 9, 2026 — including version-suffixed + * ids like `gpt-5.6-sol-2026-07-09`). 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. + * + * Note: GPT-5.6 also supports `reasoning.mode: "pro"` (more model work for + * reliability, single final answer). Mux does not implement it yet — planned as + * a toggle inside the thinking slider; tracked in issue #3704. + */ +export function openaiSupportsNativeMaxEffort(modelString: string): boolean { + return /^gpt-5\.6-(?:sol|terra|luna)(?!-[a-z])/.test(stripModelProviderPrefixes(modelString)); +} + /** * Whether the given Anthropic model rejects `thinking: { type: "disabled" }`. * @@ -276,6 +299,9 @@ export const OPENAI_REASONING_EFFORT: Record 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", }; diff --git a/src/common/utils/ai/providerOptions.test.ts b/src/common/utils/ai/providerOptions.test.ts index fc7d08385d..8c6bfe7450 100644 --- a/src/common/utils/ai/providerOptions.test.ts +++ b/src/common/utils/ai/providerOptions.test.ts @@ -415,6 +415,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, { diff --git a/src/common/utils/ai/providerOptions.ts b/src/common/utils/ai/providerOptions.ts index 35b38dbeb5..a477545fcc 100644 --- a/src/common/utils/ai/providerOptions.ts +++ b/src/common/utils/ai/providerOptions.ts @@ -23,6 +23,7 @@ import { GEMINI_THINKING_BUDGETS, OPENAI_REASONING_EFFORT, OPENROUTER_REASONING_EFFORT, + openaiSupportsNativeMaxEffort, } from "@/common/types/thinking"; import { isGeminiFlashThinkingLevelModelName } from "@/common/utils/thinking/policy"; import { resolveModelForMetadata } from "@/common/utils/providers/modelEntries"; @@ -353,8 +354,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 @@ -374,6 +373,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, diff --git a/src/common/utils/thinking/policy.test.ts b/src/common/utils/thinking/policy.test.ts index 4a6fee5eb7..bc4542ba1e 100644 --- a/src/common/utils/thinking/policy.test.ts +++ b/src/common/utils/thinking/policy.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import type { ProvidersConfigMap } from "@/common/orpc/types"; import { + clampThinkingLevelForRoute, getThinkingPolicyForModel, enforceThinkingPolicy, resolveThinkingInput, @@ -186,6 +187,77 @@ 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 (passthrough)", () => { + expect(getThinkingPolicyForModel("mux-gateway:openai/gpt-5.6-sol-2026-06-26")).toEqual([ + "off", + "low", + "medium", + "high", + "xhigh", + "max", + ]); + }); + + // Non-passthrough routes rebuild reasoning params with their own effort maps + // (OpenRouter clamps max→high, Copilot →xhigh), so native max must not be offered. + test.each(["openrouter:openai/gpt-5.6-sol", "github-copilot:openai/gpt-5.6-terra"])( + "excludes native max for GPT-5.6 on non-passthrough route %s", + (modelString) => { + expect(getThinkingPolicyForModel(modelString)).toEqual([ + "off", + "low", + "medium", + "high", + "xhigh", + ]); + } + ); + + // Route may be decided at request time (routePriority), not by the model string: + // an `openai:` GPT-5.6 model can resolve onto OpenRouter/Copilot. The post-route + // clamp must downgrade native max there while leaving passthrough routes alone. + describe("clampThinkingLevelForRoute", () => { + test.each(["openrouter", "github-copilot"])( + "clamps max to xhigh for openai:gpt-5.6-sol resolved onto %s", + (route) => { + expect(clampThinkingLevelForRoute("openai:gpt-5.6-sol", route, "max")).toBe("xhigh"); + } + ); + + test("keeps max on direct OpenAI and passthrough gateway routes", () => { + expect(clampThinkingLevelForRoute("openai:gpt-5.6-sol", "openai", "max")).toBe("max"); + expect(clampThinkingLevelForRoute("openai:gpt-5.6-sol", "mux-gateway", "max")).toBe("max"); + // No resolved route provided — falls back to model-string inference. + expect(clampThinkingLevelForRoute("openai:gpt-5.6-sol", undefined, "max")).toBe("max"); + expect(clampThinkingLevelForRoute("openrouter:openai/gpt-5.6-sol", undefined, "max")).toBe( + "xhigh" + ); + }); + + test("leaves non-max levels and non-GPT-5.6 models untouched", () => { + expect(clampThinkingLevelForRoute("openai:gpt-5.6-sol", "openrouter", "xhigh")).toBe("xhigh"); + // Non-GPT-5.6: max and xhigh already share a wire value; no clamp needed. + expect(clampThinkingLevelForRoute("openai:gpt-5.5", "openrouter", "max")).toBe("max"); + expect(clampThinkingLevelForRoute("anthropic:claude-opus-4-8", "openrouter", "max")).toBe( + "max" + ); + }); + }); + test("returns 5 levels including xhigh for gpt-5.5", () => { expect(getThinkingPolicyForModel("openai:gpt-5.5")).toEqual([ "off", diff --git a/src/common/utils/thinking/policy.ts b/src/common/utils/thinking/policy.ts index 7d47db9ea5..6018431532 100644 --- a/src/common/utils/thinking/policy.ts +++ b/src/common/utils/thinking/policy.ts @@ -12,6 +12,7 @@ * - Multiple elements = User can select from options */ +import { PROVIDER_DEFINITIONS } from "@/common/constants/providers"; import type { ProvidersConfigMap } from "@/common/orpc/types"; import { THINKING_LEVELS, @@ -19,6 +20,7 @@ import { THINKING_LEVEL_OFF, anthropicRejectsDisabledThinking, anthropicSupportsNativeXhigh, + openaiSupportsNativeMaxEffort, stripModelProviderPrefixes, type ThinkingLevel, type ParsedThinkingInput, @@ -54,6 +56,7 @@ export function isGeminiFlashThinkingLevelModelName(modelName: string): boolean * - openai:gpt-5.3-codex / Spark variants → * ["off", "low", "medium", "high", "xhigh"] (5 levels including xhigh) * - openai:gpt-5.2 / openai:gpt-5.5 → ["off", "low", "medium", "high", "xhigh"] + * - gpt-5.6 tiers (sol/terra/luna) → all 6 levels including native "max" * - openai:gpt-5.2-pro / openai:gpt-5.5-pro → ["medium", "high", "xhigh"] (3 levels) * - openai:gpt-5-pro → ["high"] (only supported level, legacy) * - Gemini Flash chat variants → ["off", "low", "medium", "high"] @@ -74,6 +77,59 @@ export function getThinkingPolicyForModel(modelString: string): ThinkingPolicy { */ const DEFAULT_THINKING_POLICY: ThinkingPolicy = ["off", "low", "medium", "high"]; +/** + * Whether a route provider delivers Mux-built openai providerOptions to OpenAI + * unchanged: OpenAI itself or a gateway marked passthrough in PROVIDER_DEFINITIONS + * (e.g. mux-gateway). Non-passthrough routes (openrouter, github-copilot, ...) + * rebuild reasoning params with their own effort maps and cannot express OpenAI's + * native "max". + */ +function isOpenAIPassthroughRoute(route: string): boolean { + if (route === "openai") return true; + const definition = PROVIDER_DEFINITIONS[route as keyof typeof PROVIDER_DEFINITIONS]; + return definition != null && "passthrough" in definition && definition.passthrough === true; +} + +/** + * Route inference from the model string alone (UI path, where the resolved route + * is not yet known). A bare/namespaced id without a route prefix is assumed direct; + * the request path re-clamps with the actually-resolved route via + * clampThinkingLevelForRoute, covering routePriority-based rerouting. + */ +function routeCanPassThroughOpenAIOptions(modelString: string): boolean { + const colonIndex = modelString.indexOf(":"); + if (colonIndex === -1) return true; // bare model id — no route info, assume direct + return isOpenAIPassthroughRoute(modelString.slice(0, colonIndex).trim().toLowerCase()); +} + +/** + * Clamp a thinking level after route resolution. + * + * The UI policy can only infer the route from the model string, but routing may be + * decided at request time (routePriority/overrides) — e.g. `openai:gpt-5.6-sol` + * resolved through OpenRouter. Non-passthrough routes map "max" via their own effort + * tables (OpenRouter → "high", Copilot → "xhigh"), silently sending less effort than + * the user selected. Clamping to "xhigh" here keeps the request, metadata, and wire + * consistent with the best effort the route can express (xhigh is those routes' policy + * ceiling; their effort maps handle any further provider-side clamping). + * + * Only GPT-5.6's native max needs this: for every other model "max" and "xhigh" + * already share a wire value, so clamping would be a no-op. + */ +export function clampThinkingLevelForRoute( + modelString: string, + routeProvider: string | undefined, + level: ThinkingLevel +): ThinkingLevel { + if (level !== "max") return level; + if (!openaiSupportsNativeMaxEffort(stripModelProviderPrefixes(modelString))) return level; + const passthrough = + routeProvider != null + ? isOpenAIPassthroughRoute(routeProvider) + : routeCanPassThroughOpenAIOptions(modelString); + return passthrough ? level : "xhigh"; +} + /** * Returns the policy for a model that matches an explicit reasoning rule, or `null` * when the model falls through to {@link DEFAULT_THINKING_POLICY}. @@ -125,7 +181,22 @@ function getExplicitThinkingPolicy(modelString: string): ThinkingPolicy | null { return ["medium", "high", "xhigh"]; } - // gpt-5.2, gpt-5.5 and the gpt-5.4-mini / gpt-5.4-nano variants support 5 reasoning levels including xhigh. + // GPT-5.6 tiers (sol/terra/luna) support a native "max" reasoning effort for + // demanding tasks that need more exploration and verification. Only expose "max" + // on routes that can actually deliver it — direct OpenAI or passthrough gateways + // (which forward the openai providerOptions namespace verbatim). Non-passthrough + // routes build their own reasoning params (OpenRouter clamps max→"high", Copilot + // →"xhigh"), so offering MAX there would silently send a lower effort than the + // slider promises; those routes keep the 5-level policy below. + if (openaiSupportsNativeMaxEffort(withoutProviderNamespace)) { + if (routeCanPassThroughOpenAIOptions(modelString)) { + return ["off", "low", "medium", "high", "xhigh", "max"]; + } + return ["off", "low", "medium", "high", "xhigh"]; + } + + // gpt-5.2, gpt-5.5 and the gpt-5.4-mini / gpt-5.4-nano variants support 5 reasoning + // levels including xhigh. if ( /^gpt-5\.2(?!-[a-z])/.test(withoutProviderNamespace) || /^gpt-5\.(?:4|5)(?:-(?:mini|nano))?(?!-[a-z])/.test(withoutProviderNamespace) diff --git a/src/common/utils/tokens/models-extra.ts b/src/common/utils/tokens/models-extra.ts index 09867e3842..9c25c29211 100644 --- a/src/common/utils/tokens/models-extra.ts +++ b/src/common/utils/tokens/models-extra.ts @@ -261,6 +261,66 @@ export const modelsExtra: Record = { supported_endpoints: ["/v1/responses"], }, + // GPT-5.6 family (Sol / Terra / Luna) - Previewed June 26, 2026; GA July 9, 2026. + // Published pricing per 1M tokens: Sol $5/$30, Terra $2.50/$15, Luna $1/$6. + // Prompt caching: cache reads keep the 90% discount; cache writes for GPT-5.6+ bill + // at 1.25x the uncached input rate (with explicit breakpoints and a 30-min minimum + // cache life). No long-context tiered pricing has been published for this family. + // KNOWN COST GAP: the API reports cache writes via input_tokens_details.cache_write_tokens + // (verified live), but @ai-sdk/openai (through at least 3.0.83) hardcodes cacheWrite to + // undefined and strips the field from its usage schema, so Mux cannot attribute the 1.25x + // write premium yet — cache-written tokens are costed at the base input rate (bounded + // underreport: 0.25x input rate on first-write tokens only). The + // cache_creation_input_token_cost values below are kept so pricing lights up automatically + // once the SDK surfaces cache-write usage. Tracked in issue #3705. + // OpenAI has not published official context specs in the GA announcement; we mirror + // GPT-5.5's window (1.05M in / 128K out) as a provisional value until the model card + // lands. (Preview reports suggest Sol may be larger, ~1.5M, but that's unconfirmed.) + "gpt-5.6-sol": { + max_input_tokens: 1050000, + max_output_tokens: 128000, + input_cost_per_token: 0.000005, // $5 per million input tokens + output_cost_per_token: 0.00003, // $30 per million output tokens + cache_read_input_token_cost: 0.0000005, // $0.50 per million cached input tokens (90% off) + cache_creation_input_token_cost: 0.00000625, // $6.25 per million tokens (1.25x input) + litellm_provider: "openai", + mode: "chat", + supports_function_calling: true, + supports_vision: true, + supports_reasoning: true, + supports_response_schema: true, + }, + + "gpt-5.6-terra": { + max_input_tokens: 1050000, + max_output_tokens: 128000, + input_cost_per_token: 0.0000025, // $2.50 per million input tokens + output_cost_per_token: 0.000015, // $15 per million output tokens + cache_read_input_token_cost: 0.00000025, // $0.25 per million cached input tokens (90% off) + cache_creation_input_token_cost: 0.000003125, // $3.125 per million tokens (1.25x input) + litellm_provider: "openai", + mode: "chat", + supports_function_calling: true, + supports_vision: true, + supports_reasoning: true, + supports_response_schema: true, + }, + + "gpt-5.6-luna": { + max_input_tokens: 1050000, + max_output_tokens: 128000, + input_cost_per_token: 0.000001, // $1 per million input tokens + output_cost_per_token: 0.000006, // $6 per million output tokens + cache_read_input_token_cost: 0.0000001, // $0.10 per million cached input tokens (90% off) + cache_creation_input_token_cost: 0.00000125, // $1.25 per million tokens (1.25x input) + litellm_provider: "openai", + mode: "chat", + supports_function_calling: true, + supports_vision: true, + supports_reasoning: true, + supports_response_schema: true, + }, + // GPT-5.4 mini - Released March 11, 2026 // Smaller/faster gpt-5.4-mini tier with 400K context, 128K max output, and published // pricing of $0.75/M input, $4.50/M output, and $0.075/M cached input. diff --git a/src/node/services/agentSkills/builtInSkillContent.generated.ts b/src/node/services/agentSkills/builtInSkillContent.generated.ts index 412815d093..65383840cb 100644 --- a/src/node/services/agentSkills/builtInSkillContent.generated.ts +++ b/src/node/services/agentSkills/builtInSkillContent.generated.ts @@ -3097,8 +3097,11 @@ export const BUILTIN_SKILL_FILES: Record> = { "| 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` | |", diff --git a/src/node/services/aiService.ts b/src/node/services/aiService.ts index 5318a2102d..0b090e1e2e 100644 --- a/src/node/services/aiService.ts +++ b/src/node/services/aiService.ts @@ -111,6 +111,7 @@ import { mergeGoalDefaults } from "@/common/utils/goals/resolveGoalSetIntent"; import { MULTI_PROJECT_CONFIG_KEY } from "@/common/constants/multiProject"; import { THINKING_LEVEL_OFF, type ThinkingLevel } from "@/common/types/thinking"; import { + clampThinkingLevelForRoute, enforceThinkingPolicy, resolveEffectiveThinkingLevel, resolveMinimumThinkingLevel, @@ -1087,7 +1088,7 @@ export class AIService extends EventEmitter { // disable thinking) so provider options, replay transforms, and metadata // all agree with the provider's actual thinking behavior. Providers config // is passed so aliases mapped to Mythos models get the same treatment. - const effectiveThinkingLevel: ThinkingLevel = resolveEffectiveThinkingLevel( + let effectiveThinkingLevel: ThinkingLevel = resolveEffectiveThinkingLevel( modelString, thinkingLevel, this.providerService.getConfig() @@ -1113,6 +1114,17 @@ export class AIService extends EventEmitter { routeProvider, } = modelResult.data; + // Re-clamp with the resolved route: routing may be decided at request time + // (routePriority/overrides), so an `openai:` GPT-5.6 model can end up on a + // non-passthrough route (OpenRouter/Copilot) that cannot express native "max". + // Must happen before provider options, headers, and metadata are built so + // they all agree with what the wire can deliver. + effectiveThinkingLevel = clampThinkingLevelForRoute( + canonicalModelString, + routeProvider, + effectiveThinkingLevel + ); + // Dump original messages for debugging log.debug_obj(`${workspaceId}/1_original_messages.json`, messages); @@ -2584,6 +2596,13 @@ export class AIService extends EventEmitter { return Err(formatSendMessageError(nextModelResult.error).message); } const next = nextModelResult.data; + // Same post-route clamp as the primary path: the fallback model's + // resolved route may not support GPT-5.6 native "max". + const routedNextThinkingLevel = clampThinkingLevelForRoute( + next.canonicalModelString, + next.routeProvider, + nextThinkingLevel + ); try { // Rebuild the toolset for the fallback model: provider-native @@ -2672,7 +2691,7 @@ export class AIService extends EventEmitter { prepareProviderRequestMessages( fallbackSourceMessages, next.canonicalProviderName, - nextThinkingLevel + routedNextThinkingLevel ); const nextFinalMessages = await prepareMessagesForProvider({ messagesWithSentinel: addInterruptedSentinel(nextProviderRequestMessages), @@ -2686,7 +2705,7 @@ export class AIService extends EventEmitter { workspacePath, abortSignal: combinedAbortSignal, providerForMessages: next.canonicalProviderName, - effectiveThinkingLevel: nextThinkingLevel, + effectiveThinkingLevel: routedNextThinkingLevel, modelString: next.canonicalModelString, anthropicCacheTtl: effectiveMuxProviderOptions.anthropic?.cacheTtl, workspaceId, @@ -2694,7 +2713,7 @@ export class AIService extends EventEmitter { const nextProviderOptions = buildProviderOptions( next.canonicalModelString, - nextThinkingLevel, + routedNextThinkingLevel, nextProviderRequestMessages, (id) => this.streamManager.isResponseIdLost(id), effectiveMuxProviderOptions, @@ -2711,7 +2730,7 @@ export class AIService extends EventEmitter { workspaceId, this.providerService.getConfig(), next.routeProvider, - nextThinkingLevel + routedNextThinkingLevel ); if (pendingRunMetadataId != null) { // Keep DevTools run correlation on fallback requests too. @@ -2756,7 +2775,7 @@ export class AIService extends EventEmitter { headers: nextHeaders, callSettingsOverrides: nextOverrides.standard, anthropicCacheTtl: effectiveMuxProviderOptions.anthropic?.cacheTtl ?? undefined, - thinkingLevel: nextThinkingLevel, + thinkingLevel: routedNextThinkingLevel, initialMetadataPatch: { routedThroughGateway: next.routedThroughGateway, ...(next.routeProvider != null ? { routeProvider: next.routeProvider } : {}),