From 0585aba5589d5a1f49243a13a8db249558e7c9e3 Mon Sep 17 00:00:00 2001 From: Minxi Hou Date: Wed, 30 Sep 2026 00:21:38 -0400 Subject: [PATCH] feat(models): add GPT-6.1 Sol catalogs, pricing, and Copilot routing OpenAI published gpt-6.1-sol and the live Codex catalog already serves it, but the built-in OpenAI/Codex tables on release/v3.8.52 do not. GitHub Copilot's static catalog stops at gpt-5.6, and the executor only sends names containing "codex" to /responses, so gpt-6-astra and gpt-6.1-sol miss that endpoint. Add the base ids to OpenAI, Codex, GitHub Copilot, and GHE Copilot. Keep the public API limits (1,050,000 context / 128,000 output) apart from Codex's live maximum context (872,000). Preserve max through translation. Route gpt-6* on Copilot to /responses even when the model is not in the static list. Bill a whole gpt-6.x-sol request at the long-context rate once input is above 272,000 tokens: 2x input and cache, 1.5x output. The bare gpt-6-sol id is left alone. The reasoning premium uses the scaled output price and cannot go negative, because reasoning tokens are already inside the output count. Bump the fallback Codex client pin from 0.156.1 to 0.159.2, including the image CLI. Caller-supplied versions still win. The public API default effort stays medium; Codex's own catalog default of low is not applied as a global fallback. Related to #15166 Signed-off-by: Minxi Hou --- Dockerfile | 2 +- .../config/providers/registry/codex/index.ts | 9 ++++ .../providers/registry/ghe-copilot/index.ts | 18 ++++++++ .../config/providers/registry/github/index.ts | 18 ++++++++ open-sse/executors/codex/reasoningSuffix.ts | 2 + open-sse/executors/github.ts | 2 +- .../request/openai-responses/helpers.ts | 2 +- src/lib/providers/codexFastTier.ts | 1 + src/lib/usage/costCalculator.ts | 33 +++++++++++---- src/shared/constants/codexClient.ts | 2 +- .../constants/pricing/oauth-subscriptions.ts | 17 ++++++++ src/shared/reasoning/effortStandardization.ts | 5 ++- tests/snapshots/provider/translate-path.json | 12 +++--- .../unit/8951-github-gpt56-responses.test.ts | 9 ++-- tests/unit/codex-fast-tier.test.ts | 1 + tests/unit/codex-gpt6-sol-luna.test.ts | 41 ++++++++++++++++++- tests/unit/executor-codex.test.ts | 6 +-- 17 files changed, 153 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 897c8dbe516e..878d82a410b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -363,7 +363,7 @@ RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-apt-cache,targe # build, not the floating `@latest`. RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \ npm install -g --no-audit --no-fund \ - @openai/codex@0.156.1 \ + @openai/codex@0.159.2 \ @anthropic-ai/claude-code@2.1.260 \ droid@0.212.0 \ openclaw@2026.9.1 diff --git a/open-sse/config/providers/registry/codex/index.ts b/open-sse/config/providers/registry/codex/index.ts index c135d8b99c82..35ee6bc3ca6d 100644 --- a/open-sse/config/providers/registry/codex/index.ts +++ b/open-sse/config/providers/registry/codex/index.ts @@ -66,6 +66,15 @@ export const codexProvider: RegistryEntry = { }, { id: "gpt-6-sol-medium", name: "GPT 6 Sol (Medium)", ...GPT_5_6_CODEX_CAPABILITIES }, { id: "gpt-6-sol-low", name: "GPT 6 Sol (Low)", ...GPT_5_6_CODEX_CAPABILITIES }, + // GPT-6.1 Sol: low..ultra effort aliases, same windows as Sol. + // https://developers.openai.com/api/docs/models/gpt-6.1-sol + { id: "gpt-6.1-sol", name: "GPT 6.1 Sol", ...GPT_5_6_CODEX_CAPABILITIES }, + { id: "gpt-6.1-sol-ultra", name: "GPT 6.1 Sol (Ultra)", ...GPT_5_6_CODEX_CAPABILITIES }, + { id: "gpt-6.1-sol-max", name: "GPT 6.1 Sol (Max)", ...GPT_5_6_CODEX_CAPABILITIES }, + { id: "gpt-6.1-sol-xhigh", name: "GPT 6.1 Sol (xHigh)", ...GPT_5_6_CODEX_CAPABILITIES, timeoutMs: 1200000 }, + { id: "gpt-6.1-sol-high", name: "GPT 6.1 Sol (High)", ...GPT_5_6_CODEX_CAPABILITIES, timeoutMs: 1200000 }, + { id: "gpt-6.1-sol-medium", name: "GPT 6.1 Sol (Medium)", ...GPT_5_6_CODEX_CAPABILITIES }, + { id: "gpt-6.1-sol-low", name: "GPT 6.1 Sol (Low)", ...GPT_5_6_CODEX_CAPABILITIES }, { id: "gpt-6-luna", name: "GPT 6 Luna", ...GPT_5_6_CODEX_CAPABILITIES }, { id: "gpt-6-luna-max", name: "GPT 6 Luna (Max)", ...GPT_5_6_CODEX_CAPABILITIES }, { diff --git a/open-sse/config/providers/registry/ghe-copilot/index.ts b/open-sse/config/providers/registry/ghe-copilot/index.ts index cb559b064d9c..7d7e7569a066 100644 --- a/open-sse/config/providers/registry/ghe-copilot/index.ts +++ b/open-sse/config/providers/registry/ghe-copilot/index.ts @@ -124,6 +124,24 @@ export const gheCopilotProvider: RegistryEntry = { contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "gpt-6-astra", + name: "GPT-6 Astra", + supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], + targetFormat: "openai-responses", + contextLength: 1050000, + maxOutputTokens: 128000, + }, + { + id: "gpt-6.1-sol", + name: "GPT-6.1 Sol", + supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], + targetFormat: "openai-responses", + contextLength: 1050000, + maxOutputTokens: 128000, + }, { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", diff --git a/open-sse/config/providers/registry/github/index.ts b/open-sse/config/providers/registry/github/index.ts index 96d42ee19652..01b3a8054536 100644 --- a/open-sse/config/providers/registry/github/index.ts +++ b/open-sse/config/providers/registry/github/index.ts @@ -160,6 +160,24 @@ export const githubProvider: RegistryEntry = { contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "gpt-6-astra", + name: "GPT-6 Astra", + supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], + targetFormat: "openai-responses", + contextLength: 1050000, + maxOutputTokens: 128000, + }, + { + id: "gpt-6.1-sol", + name: "GPT-6.1 Sol", + supportsReasoning: true, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], + targetFormat: "openai-responses", + contextLength: 1050000, + maxOutputTokens: 128000, + }, { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", diff --git a/open-sse/executors/codex/reasoningSuffix.ts b/open-sse/executors/codex/reasoningSuffix.ts index 627f2a339fb0..cfcba4ecd895 100644 --- a/open-sse/executors/codex/reasoningSuffix.ts +++ b/open-sse/executors/codex/reasoningSuffix.ts @@ -15,12 +15,14 @@ export const CODEX_MAX_ALIAS_MODELS = new Set([ "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", + "gpt-6.1-sol", ]); export const CODEX_ULTRA_ALIAS_MODELS = new Set([ "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra", "gpt-6-sol", + "gpt-6.1-sol", ]); /** Highest effort a max/ultra-tier base model accepts, or null for other models. */ diff --git a/open-sse/executors/github.ts b/open-sse/executors/github.ts index 238bd557b242..135370f8869d 100644 --- a/open-sse/executors/github.ts +++ b/open-sse/executors/github.ts @@ -110,7 +110,7 @@ export class GithubExecutor extends BaseExecutor { // 9router#1536: but never route Gemini/Claude variants to /responses (they 400) — // gate the whole decision on supportsResponsesEndpoint(). if ( - (targetFormat === "openai-responses" || /codex/i.test(model)) && + (targetFormat === "openai-responses" || /codex|^gpt-6/i.test(model)) && this.supportsResponsesEndpoint(model) ) { return ( diff --git a/open-sse/translator/request/openai-responses/helpers.ts b/open-sse/translator/request/openai-responses/helpers.ts index 8065bddb3853..9360162aeee7 100644 --- a/open-sse/translator/request/openai-responses/helpers.ts +++ b/open-sse/translator/request/openai-responses/helpers.ts @@ -51,7 +51,7 @@ export function imageUrlToText(value: unknown): string { } const CODEX_MAX_EFFORT_MODEL_PATTERN = - /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6-(?:astra|sol|luna))(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/; + /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6(?:\.\d+)?-(?:astra|sol|luna))(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/; const KIRO_GPT_5_6_MODEL_PATTERN = /^(?:kiro|kr)\/gpt-5\.6-(?:sol|terra|luna)(?:-(?:none|low|medium|high|xhigh|max))?$/; diff --git a/src/lib/providers/codexFastTier.ts b/src/lib/providers/codexFastTier.ts index 3715b81ffab6..e1e0ade7ba1c 100644 --- a/src/lib/providers/codexFastTier.ts +++ b/src/lib/providers/codexFastTier.ts @@ -16,6 +16,7 @@ export type CodexGlobalServiceMode = "none" | CodexServiceTier; export const CODEX_FAST_TIER_DEFAULT_SUPPORTED_MODELS: readonly string[] = [ "gpt-6-astra", "gpt-6-sol", + "gpt-6.1-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", diff --git a/src/lib/usage/costCalculator.ts b/src/lib/usage/costCalculator.ts index 5774fc4b56cd..2eaea0280dc7 100644 --- a/src/lib/usage/costCalculator.ts +++ b/src/lib/usage/costCalculator.ts @@ -103,8 +103,8 @@ export function getCodexFastCostMultiplier( const compactModelKey = modelKey.replace(/-/g, ""); // Codex GPT-6 Fast is 2.5x Standard (https://developers.openai.com/codex/pricing). if ( - /^gpt-6-(?:astra|sol|luna)$/.test(modelKey) || - /^gpt6(?:astra|sol|luna)$/.test(compactModelKey) + /^gpt-6(?:\.\d+)?-(?:astra|sol|luna)$/.test(modelKey) || + /^gpt6(?:\d+)?(?:astra|sol|luna)$/.test(compactModelKey) ) { return 2.5; } @@ -119,6 +119,19 @@ export function getCodexFastCostMultiplier( return 1; } +const GPT61_LONG_CONTEXT_INPUT_TOKENS = 272_000; + +function longContextPriceMultiplier( + model: string | null | undefined, + inputTokens: number +): { input: number; cache: number; output: number } { + const flat = { input: 1, cache: 1, output: 1 }; + if (inputTokens <= GPT61_LONG_CONTEXT_INPUT_TOKENS) return flat; + const modelKey = stripCodexEffortSuffix(normalizeModelName(String(model || "")).toLowerCase()); + if (!/^gpt-6\.\d+-sol$/.test(modelKey)) return flat; + return { input: 2, cache: 2, output: 1.5 }; +} + /** * Calculate cost for a usage entry. * @@ -162,20 +175,26 @@ export function computeCostFromPricing( // so we must subtract BOTH cache types to avoid pricing cache at the full // input rate in addition to their dedicated cache_* rates below. const nonCachedInput = Math.max(0, inputTokens - cachedTokens - cacheCreationTokens); - cost += nonCachedInput * (inputPrice / 1_000_000); - if (cachedTokens > 0) cost += cachedTokens * (cachedPrice / 1_000_000); + // GPT-6.1 bills the whole request at the long-context rate once input + // crosses 272K: 2x input and cache, 1.5x output. + // https://developers.openai.com/api/docs/models/gpt-6.1-sol + const longContext = longContextPriceMultiplier(options.model, inputTokens); + cost += nonCachedInput * ((inputPrice * longContext.input) / 1_000_000); + if (cachedTokens > 0) cost += cachedTokens * ((cachedPrice * longContext.cache) / 1_000_000); const outputTokens = tokens.output ?? tokens.completion_tokens ?? tokens.output_tokens ?? 0; - cost += outputTokens * (outputPrice / 1_000_000); + cost += outputTokens * ((outputPrice * longContext.output) / 1_000_000); // completion_tokens is reasoning-inclusive. Reasoning is already billed at // the output rate above, so a dedicated price contributes only its premium. const reasoningTokens = tokens.reasoning ?? tokens.reasoning_tokens ?? 0; if (reasoningTokens > 0 && pricing.reasoning !== undefined && pricing.reasoning !== null) { - cost += reasoningTokens * ((reasoningPrice - outputPrice) / 1_000_000); + cost += reasoningTokens * (Math.max(0, reasoningPrice - outputPrice * longContext.output) / 1_000_000); } - if (cacheCreationTokens > 0) cost += cacheCreationTokens * (cacheCreationPrice / 1_000_000); + if (cacheCreationTokens > 0) { + cost += cacheCreationTokens * ((cacheCreationPrice * longContext.cache) / 1_000_000); + } return cost * getCodexFastCostMultiplier(options.provider, options.model, options.serviceTier); } diff --git a/src/shared/constants/codexClient.ts b/src/shared/constants/codexClient.ts index 108f32a70361..e8d2b854d22e 100644 --- a/src/shared/constants/codexClient.ts +++ b/src/shared/constants/codexClient.ts @@ -3,7 +3,7 @@ // refresh this so the fingerprint OpenAI sees from the OAuth/Responses face // matches the real client version. Overridable per-deployment via // CODEX_CLIENT_VERSION. -export const DEFAULT_CODEX_CLIENT_VERSION = "0.156.1"; +export const DEFAULT_CODEX_CLIENT_VERSION = "0.159.2"; export const CODEX_CLI_RS_ORIGINATOR = "codex_cli_rs"; export function getCodexCliRsHeaders( diff --git a/src/shared/constants/pricing/oauth-subscriptions.ts b/src/shared/constants/pricing/oauth-subscriptions.ts index 84df7cf369e9..522829073b49 100644 --- a/src/shared/constants/pricing/oauth-subscriptions.ts +++ b/src/shared/constants/pricing/oauth-subscriptions.ts @@ -31,6 +31,16 @@ const GPT_6_SOL_CODEX_PRICING = { reasoning: 10.0, cache_creation: 2.5, }; +// GPT-6.1 Sol Standard, from models.dev openai/gpt-6.1-sol and the OpenAI +// model page. Cached input is $0.10, not the $0.20 GPT-6 Sol rate. +// https://developers.openai.com/api/docs/models/gpt-6.1-sol +const GPT_6_1_SOL_CODEX_PRICING = { + input: 2.0, + output: 10.0, + cached: 0.1, + reasoning: 10.0, + cache_creation: 2.5, +}; const GPT_6_LUNA_CODEX_PRICING = { input: 0.1, output: 0.5, @@ -122,6 +132,13 @@ export const DEFAULT_PRICING_OAUTH = { "gpt-6-sol-high": GPT_6_SOL_CODEX_PRICING, "gpt-6-sol-medium": GPT_6_SOL_CODEX_PRICING, "gpt-6-sol-low": GPT_6_SOL_CODEX_PRICING, + "gpt-6.1-sol": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-ultra": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-max": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-xhigh": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-high": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-medium": GPT_6_1_SOL_CODEX_PRICING, + "gpt-6.1-sol-low": GPT_6_1_SOL_CODEX_PRICING, "gpt-6-luna": GPT_6_LUNA_CODEX_PRICING, "gpt-6-luna-max": GPT_6_LUNA_CODEX_PRICING, "gpt-6-luna-xhigh": GPT_6_LUNA_CODEX_PRICING, diff --git a/src/shared/reasoning/effortStandardization.ts b/src/shared/reasoning/effortStandardization.ts index 7f34ddc3f984..2d8ff1f5b70a 100644 --- a/src/shared/reasoning/effortStandardization.ts +++ b/src/shared/reasoning/effortStandardization.ts @@ -41,7 +41,7 @@ export function extendCodexGpt56EffortValues( } const match = normalizedModel.match( - /^gpt-(?:5\.6-(sol|terra|luna)|6-(astra|sol|luna))(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/ + /^gpt-(?:5\.6-(sol|terra|luna)|6(?:\.\d+)?-(astra|sol|luna))(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/ ); if (!match) return values; @@ -53,7 +53,8 @@ export function extendCodexGpt56EffortValues( if (normalizedProvider !== "codex" && normalizedProvider !== "cx") return values; const nativeValues = ["low", "medium", "high", "xhigh", "max"]; - return (match[1] || match[2]) === "luna" ? nativeValues : [...nativeValues, "ultra"]; + const family = match[1] || match[2]; + return family === "luna" ? nativeValues : [...nativeValues, "ultra"]; } /** diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index b16d46e13506..7b21664b4e99 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -1334,23 +1334,23 @@ "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses_websockets=2026-02-06", - "User-Agent": "codex-cli/0.156.1 (; )", - "Version": "0.156.1" + "User-Agent": "codex-cli/0.159.2 (; )", + "Version": "0.159.2" }, "nonStream": { "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses_websockets=2026-02-06", - "User-Agent": "codex-cli/0.156.1 (; )", - "Version": "0.156.1" + "User-Agent": "codex-cli/0.159.2 (; )", + "Version": "0.159.2" }, "oauth": { "Accept": "text/event-stream", "Authorization": "Bearer ", "Content-Type": "application/json", "Openai-Beta": "responses_websockets=2026-02-06", - "User-Agent": "codex-cli/0.156.1 (; )", - "Version": "0.156.1" + "User-Agent": "codex-cli/0.159.2 (; )", + "Version": "0.159.2" } }, "url": { diff --git a/tests/unit/8951-github-gpt56-responses.test.ts b/tests/unit/8951-github-gpt56-responses.test.ts index 82085120a9e2..c7f7efddd132 100644 --- a/tests/unit/8951-github-gpt56-responses.test.ts +++ b/tests/unit/8951-github-gpt56-responses.test.ts @@ -6,14 +6,15 @@ import { GithubExecutor } from "../../open-sse/executors/github.ts"; test("#8951 GitHub GPT-5.6 models must use the Responses endpoint", () => { const executor = new GithubExecutor(); const urls = Object.fromEntries( - ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"].map((model) => [ - model, - executor.buildUrl(model, false), - ]) + ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6.1-sol"].map( + (model) => [model, executor.buildUrl(model, false)] + ) ); assert.deepEqual(urls, { "gpt-5.6-sol": "https://api.githubcopilot.com/responses", "gpt-5.6-terra": "https://api.githubcopilot.com/responses", "gpt-5.6-luna": "https://api.githubcopilot.com/responses", + "gpt-6-astra": "https://api.githubcopilot.com/responses", + "gpt-6.1-sol": "https://api.githubcopilot.com/responses", }); }); diff --git a/tests/unit/codex-fast-tier.test.ts b/tests/unit/codex-fast-tier.test.ts index f686eeca0579..d31a850dc1ad 100644 --- a/tests/unit/codex-fast-tier.test.ts +++ b/tests/unit/codex-fast-tier.test.ts @@ -48,6 +48,7 @@ test("Codex global service mode distinguishes no setting from explicit tiers", ( supportedModels: [ "gpt-6-astra", "gpt-6-sol", + "gpt-6.1-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", diff --git a/tests/unit/codex-gpt6-sol-luna.test.ts b/tests/unit/codex-gpt6-sol-luna.test.ts index 1504ad504358..a6b1f886bc21 100644 --- a/tests/unit/codex-gpt6-sol-luna.test.ts +++ b/tests/unit/codex-gpt6-sol-luna.test.ts @@ -5,7 +5,7 @@ import { getModelsByProviderId } from "../../open-sse/config/providerModels.ts"; import { CodexExecutor } from "../../open-sse/executors/codex.ts"; import { openaiToOpenAIResponsesRequest } from "../../open-sse/translator/request/openai-responses/toResponses.ts"; import { getPricingForModel } from "../../src/shared/constants/pricing.ts"; -import { getCodexFastCostMultiplier } from "../../src/lib/usage/costCalculator.ts"; +import { computeCostFromPricing, getCodexFastCostMultiplier } from "../../src/lib/usage/costCalculator.ts"; import { extendCodexGpt56EffortValues } from "../../src/shared/reasoning/effortStandardization.ts"; import * as reasoningMetadata from "../../src/lib/vscode/reasoningMetadata.ts"; @@ -13,6 +13,7 @@ import * as reasoningMetadata from "../../src/lib/vscode/reasoningMetadata.ts"; // low..max, mirroring the GPT-5.6 Sol/Luna split. const FAMILIES = [ { model: "gpt-6-sol", efforts: ["ultra", "max", "xhigh", "high", "medium", "low"] }, + { model: "gpt-6.1-sol", efforts: ["ultra", "max", "xhigh", "high", "medium", "low"] }, { model: "gpt-6-luna", efforts: ["max", "xhigh", "high", "medium", "low"] }, ] as const; @@ -112,6 +113,10 @@ test("Catalog effort tiers follow the live Codex levels for GPT-6 models", () => for (const provider of ["codex", "cx"]) { assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6-astra", base), withUltra); assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6-sol", base), withUltra); + assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6.1-sol", base), withUltra); + // A later minor of the same family keeps ultra without a new allowlist entry. + assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6.2-sol", base), withUltra); + assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6.1-luna", base), withMax); assert.deepEqual(extendCodexGpt56EffortValues(provider, "gpt-6-luna-max", base), withMax); } // Kiro's GPT-5.6 max extension does not reach models Kiro does not serve. @@ -179,6 +184,7 @@ test("GPT-6 Sol and Luna Codex pricing and Fast multiplier match the credit rate // Sol 50 / 5 / 250, Luna 2.5 / 0.25 / 12.5. Fast is 2.5x Standard for both. const expected = { "gpt-6-sol": { input: 2, cached: 0.2, output: 10 }, + "gpt-6.1-sol": { input: 2, cached: 0.1, output: 10 }, "gpt-6-luna": { input: 0.1, cached: 0.01, output: 0.5 }, }; for (const { model, efforts } of FAMILIES) { @@ -196,3 +202,36 @@ test("GPT-6 Sol and Luna Codex pricing and Fast multiplier match the credit rate } } }); + +test("GPT-6.1 Sol bills the whole request at the long-context rate past 272K input", () => { + const pricing = { input: 2, cached: 0.1, output: 10, cache_creation: 2.5, reasoning: 10 }; + const tokens = { input: 300_000, cache_read_input_tokens: 50_000, output: 1_000 }; + const standard = computeCostFromPricing( + pricing, + { input: 272_000, output: 1_000 }, + { model: "gpt-6.1-sol" } + ); + const elevated = computeCostFromPricing( + pricing, + { ...tokens, reasoning_tokens: 200 }, + { model: "gpt-6.1-sol-high" } + ); + const olderSol = computeCostFromPricing(pricing, tokens, { model: "gpt-6-sol" }); + + const standardExpected = (272_000 * 2 + 1_000 * 10) / 1_000_000; + // Output is already billed at 1.5x, which is above the reasoning price. + // The premium cannot go negative or the request would be under-charged. + const elevatedExpected = (250_000 * 4 + 50_000 * 0.2 + 1_000 * 15) / 1_000_000; + const olderExpected = (250_000 * 2 + 50_000 * 0.1 + 1_000 * 10) / 1_000_000; + assert.ok(Math.abs(standard - standardExpected) < 1e-9); + assert.ok(Math.abs(elevated - elevatedExpected) < 1e-9); + assert.ok(Math.abs(olderSol - olderExpected) < 1e-9); +}); + +test("GPT-6.1 Sol stays at the standard rate one token under 272K and doubles one token over", () => { + const pricing = { input: 2, output: 10 }; + const under = computeCostFromPricing(pricing, { input: 271_999, output: 0 }, { model: "gpt-6.1-sol" }); + const over = computeCostFromPricing(pricing, { input: 272_001, output: 0 }, { model: "gpt-6.1-sol" }); + assert.ok(Math.abs(under - (271_999 * 2) / 1_000_000) < 1e-9); + assert.ok(Math.abs(over - (272_001 * 4) / 1_000_000) < 1e-9); +}); diff --git a/tests/unit/executor-codex.test.ts b/tests/unit/executor-codex.test.ts index bc061ae6e3fb..25df2cf5376e 100644 --- a/tests/unit/executor-codex.test.ts +++ b/tests/unit/executor-codex.test.ts @@ -184,10 +184,10 @@ test("CodexExecutor.buildHeaders binds workspace ids and disables SSE accept for assert.equal(standardHeaders.Authorization, "Bearer codex-token"); assert.equal(standardHeaders.Accept, "text/event-stream"); assert.equal(standardHeaders["chatgpt-account-id"], "workspace-1"); - assert.equal(standardHeaders.Version, "0.156.1"); + assert.equal(standardHeaders.Version, "0.159.2"); assert.equal(standardHeaders["Openai-Beta"], "responses_websockets=2026-02-06"); assert.equal(standardHeaders["X-Codex-Beta-Features"], undefined); - assert.equal(standardHeaders["User-Agent"], "codex-cli/0.156.1 (Windows 10.0.26200; x64)"); + assert.equal(standardHeaders["User-Agent"], "codex-cli/0.159.2 (Windows 10.0.26200; x64)"); assert.equal(compactHeaders.Accept, "application/json"); }); @@ -213,7 +213,7 @@ test("CodexExecutor.buildHeaders honors safe env overrides for Version and User- }, () => { const headers = executor.buildHeaders({ accessToken: "codex-token" }, true); - assert.equal(headers.Version, "0.156.1"); + assert.equal(headers.Version, "0.159.2"); assert.equal(headers["User-Agent"], "custom-codex/9.9.9"); } );