Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions changelog.d/features/15171-gpt-61-sol-catalog-pricing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(providers):** Add GPT-6.1 Sol to the OpenAI and Codex catalogs with provider-specific limits, Standard pricing, Codex reasoning/Fast wiring, and a synchronized Codex 0.159.2 client identity ([#15171](https://github.com/diegosouzapw/OmniRoute/pull/15171)) — thanks @xiaoyaner0201
18 changes: 18 additions & 0 deletions open-sse/config/providers/registry/codex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ export const codexProvider: RegistryEntry = {
tokenUrl: "https://auth.openai.com/oauth/token",
},
models: [
// Live Codex catalog: 872K maximum window, low default, low..ultra.
{ 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 },
// Astra shares GPT-5.6's Codex limits: the live OAuth catalog reports
// max_context_window=872000 (context_window=272000 is the pricing tier).
{ id: "gpt-6-astra", name: "GPT 6 Astra", ...GPT_5_6_CODEX_CAPABILITIES },
Expand Down
8 changes: 8 additions & 0 deletions open-sse/config/providers/registry/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export const openaiProvider: RegistryEntry = {
authHeader: "bearer",
defaultContextLength: 128000,
models: [
// https://developers.openai.com/api/docs/models/gpt-6.1-sol
// API: 1.05M context, low..max (no none/minimal); tools require Responses.
{
id: "gpt-6.1-sol",
name: "GPT-6.1 Sol",
...GPT_5_6_API_CAPABILITIES,
unsupportedParams: ["temperature", "top_p", "top_logprobs", "logprobs"],
},
// Astra shares the public GPT-5.6 limits; tool calling requires Responses.
// https://developers.openai.com/api/docs/guides/latest-model
{
Expand Down
2 changes: 1 addition & 1 deletion open-sse/executors/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,7 @@ export class CodexExecutor extends BaseExecutor {
const explicitReasoning = normalizeEffortValue(reasoningRecord?.effort);
const requestReasoningEffort = normalizeEffortValue(body.reasoning_effort);
const fallbackReasoningEffort = allowConnectionReasoningDefaults
? requestDefaults.reasoningEffort || "medium"
? requestDefaults.reasoningEffort || (cleanModel === "gpt-6.1-sol" ? "low" : "medium")
: undefined;
// Issue #2331: model suffix aliases (for example gpt-5.5-xhigh) represent an
// explicit model selection, so they must override client-injected defaults such
Expand Down
2 changes: 2 additions & 0 deletions open-sse/executors/codex/reasoningSuffix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const CODEX_EFFORT_ORDER = [
] as const;
export type CodexEffortLevel = (typeof CODEX_EFFORT_ORDER)[number];
export const CODEX_MAX_ALIAS_MODELS = new Set([
"gpt-6.1-sol",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
Expand All @@ -17,6 +18,7 @@ export const CODEX_MAX_ALIAS_MODELS = new Set([
"gpt-6-luna",
]);
export const CODEX_ULTRA_ALIAS_MODELS = new Set([
"gpt-6.1-sol",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-6-astra",
Expand Down
7 changes: 7 additions & 0 deletions open-sse/services/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ export const CODEX_NATIVE_UNPREFIXED_MODELS = new Set([
"gpt-6-astra-high",
"gpt-6-astra-medium",
"gpt-6-astra-low",
"gpt-6.1-sol",
"gpt-6.1-sol-ultra",
"gpt-6.1-sol-max",
"gpt-6.1-sol-xhigh",
"gpt-6.1-sol-high",
"gpt-6.1-sol-medium",
"gpt-6.1-sol-low",
"gpt-5.6-sol",
"gpt-5.6-sol-ultra",
"gpt-5.6-sol-max",
Expand Down
2 changes: 1 addition & 1 deletion open-sse/translator/request/openai-responses/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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-(?:astra|sol|luna)|gpt-6\.1-sol)(?:-(?: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))?$/;

Expand Down
1 change: 1 addition & 0 deletions src/lib/providers/codexFastTier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type CodexFastTierValue = CodexServiceTier;
export type CodexGlobalServiceMode = "none" | CodexServiceTier;

export const CODEX_FAST_TIER_DEFAULT_SUPPORTED_MODELS: readonly string[] = [
"gpt-6.1-sol",
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
Expand Down
8 changes: 4 additions & 4 deletions src/lib/usage/costCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ export function getCodexFastCostMultiplier(

const modelKey = stripCodexEffortSuffix(normalizeModelName(String(model || "")).toLowerCase());
const compactModelKey = modelKey.replace(/-/g, "");
// GPT-6.1 Sol purchased-credit/USD Fast rate is 2x, not the 2.5x
// included-subscription consumption multiplier (codex/pricing and codex/speed).
if (compactModelKey === "gpt6.1sol") return 2;
// 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)
) {
if (/^gpt6(?:astra|sol|luna)$/.test(compactModelKey)) {
return 2.5;
}
if (
Expand Down
3 changes: 2 additions & 1 deletion src/lib/vscode/reasoningMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type VscodeCatalogModel = {

const STANDARD_EFFORT_SUFFIX_PATTERN = /-(xhigh|high|medium|low|none)$/i;
const CODEX_EXTENDED_EFFORT_SUFFIX_PATTERN =
/^(.*gpt-(?:5\.6-(?:sol|terra|luna)|6-(?:astra|sol|luna)))-(max|ultra)$/i;
/^(.*gpt-(?:5\.6-(?:sol|terra|luna)|6-(?:astra|sol|luna)|6\.1-sol))-(max|ultra)$/i;
const DEFAULT_REASONING_EFFORT = "none";
const KNOWN_REASONING_EFFORTS = new Set(["none", "low", "medium", "high", "xhigh", "max", "ultra"]);

Expand Down Expand Up @@ -190,6 +190,7 @@ function getCodexGpt56DefaultReasoningEffort(model: VscodeCatalogModel) {
const providerModelId = (parsed.model || model.root || modelId.split("/").pop() || modelId)
.trim()
.toLowerCase();
if (/^gpt-6\.1-sol(?:-(?:low|medium|high|xhigh|max|ultra))?$/.test(providerModelId)) return "low";
const match = providerModelId.match(
/^gpt-(?:5\.6-(sol|terra|luna)|6-(?:astra|sol|luna))(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/
);
Expand Down
2 changes: 1 addition & 1 deletion src/shared/constants/codexClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions src/shared/constants/modelSpecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ const GEMINI_36_FLASH_MODEL_SPEC = {
} satisfies ModelSpec;

export const MODEL_SPECS: Record<string, ModelSpec> = {
// Public API limits; Codex's smaller window lives in its provider registry.
"gpt-6.1-sol": {
...GPT_5_6_MODEL_SPEC,
aliases: ["openai/gpt-6.1-sol"],
},
// Public model limits; the Codex registry supplies its smaller OAuth window.
// https://developers.openai.com/api/docs/models/gpt-6-astra
"gpt-6-astra": {
Expand Down
3 changes: 3 additions & 0 deletions src/shared/constants/pricing/frontier-labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {

export const DEFAULT_PRICING_FRONTIER = {
openai: {
// Standard short-context USD/MTok. Long-context and other processing tiers
// are not represented by this static row. See the GPT-6.1 Sol API model page.
"gpt-6.1-sol": { input: 2, output: 10, cached: 0.1, reasoning: 10, cache_creation: 2.5 },
"gpt-6-astra": GPT_6_ASTRA_PRICING,
"gpt-5.6": GPT_5_6_SOL_PRICING,
"gpt-5.6-sol": GPT_5_6_SOL_PRICING,
Expand Down
10 changes: 10 additions & 0 deletions src/shared/constants/pricing/oauth-subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const ANTIGRAVITY_GEMINI_3_7_PRICING = {
// Codex Standard: 250 / 25 / 1250 credits per MTok, at 25 credits per USD.
// https://developers.openai.com/codex/pricing
const GPT_6_ASTRA_CODEX_PRICING = GPT_6_ASTRA_PRICING;
// GPT-6.1 Sol: 50 / 2.5 / 250 Standard credits per MTok, at 25 credits/USD.
// Codex's credit card does not specify a separate cache-write rate.
const GPT_6_1_SOL_CODEX_PRICING = { input: 2, output: 10, cached: 0.1, reasoning: 10 };
// Codex Standard: Sol 50 / 5 / 250 and Luna 2.5 / 0.25 / 12.5 credits per MTok.
const GPT_6_SOL_CODEX_PRICING = {
input: 2.0,
Expand Down Expand Up @@ -108,6 +111,13 @@ export const DEFAULT_PRICING_OAUTH = {
},
},
cx: {
"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-astra": GPT_6_ASTRA_CODEX_PRICING,
"gpt-6-astra-ultra": GPT_6_ASTRA_CODEX_PRICING,
"gpt-6-astra-max": GPT_6_ASTRA_CODEX_PRICING,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/reasoning/effortStandardization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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-(astra|sol|luna)|6\.1-sol)(?:-(?:none|low|medium|high|xhigh|max|ultra))?$/
);
if (!match) return values;

Expand Down
12 changes: 6 additions & 6 deletions tests/snapshots/provider/translate-path.json
Original file line number Diff line number Diff line change
Expand Up @@ -1334,23 +1334,23 @@
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses_websockets=2026-02-06",
"User-Agent": "codex-cli/0.156.1 (<OS>; <ARCH>)",
"Version": "0.156.1"
"User-Agent": "codex-cli/0.159.2 (<OS>; <ARCH>)",
"Version": "0.159.2"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses_websockets=2026-02-06",
"User-Agent": "codex-cli/0.156.1 (<OS>; <ARCH>)",
"Version": "0.156.1"
"User-Agent": "codex-cli/0.159.2 (<OS>; <ARCH>)",
"Version": "0.159.2"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"Openai-Beta": "responses_websockets=2026-02-06",
"User-Agent": "codex-cli/0.156.1 (<OS>; <ARCH>)",
"Version": "0.156.1"
"User-Agent": "codex-cli/0.159.2 (<OS>; <ARCH>)",
"Version": "0.159.2"
}
},
"url": {
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/codex-astra.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ test("Codex exposes Astra and its effort variants with live OAuth limits", () =>
assert.equal(model.supportsVision, true);
assert.equal(model.supportsXHighEffort, true);
}
const solIds = ["gpt-6.1-sol", ...EFFORTS.map((effort) => `gpt-6.1-sol-${effort}`)];
assert.deepEqual(
models.slice(0, ids.length).map((model) => model.id),
ids
models.slice(0, solIds.length + ids.length).map((model) => model.id),
[...solIds, ...ids]
);
}
});
Expand Down
1 change: 1 addition & 0 deletions tests/unit/codex-fast-tier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ test("Codex global service mode distinguishes no setting from explicit tiers", (
enabled: true,
tier: "default",
supportedModels: [
"gpt-6.1-sol",
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
Expand Down
111 changes: 111 additions & 0 deletions tests/unit/codex-gpt61-sol-bare-id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

// GPT-6.1 Sol is a Codex-native model like gpt-6-astra and the gpt-5.6 tiers.
// Its ids must be in CODEX_NATIVE_UNPREFIXED_MODELS, otherwise with both a Codex
// and an OpenAI connection active the bare `gpt-6.1-sol` id is ambiguous (or goes
// to OpenAI) and /v1/models never lists the bare Codex rows.

const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-gpt61-bare-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "codex-gpt61-bare-test-secret";

const core = await import("../../src/lib/db/core.ts");
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const { CODEX_NATIVE_UNPREFIXED_MODELS, getModelInfoCore } =
await import("../../open-sse/services/model.ts");
const { getProviderModels } = await import("../../open-sse/config/providerModels.ts");
const { getPricingForModel } = await import("../../src/shared/constants/pricing.ts");
const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts");

const MODEL = "gpt-6.1-sol";
const EFFORTS = ["ultra", "max", "xhigh", "high", "medium", "low"];
const EXPECTED_IDS = [MODEL, ...EFFORTS.map((effort) => `${MODEL}-${effort}`)];

// The base id and its effort tiers, as registered for the codex provider.
const SOL_IDS = getProviderModels("codex")
.map((model) => model.id)
.filter((id) => id === MODEL || id.startsWith(`${MODEL}-`));

async function seedConnection(provider: "codex" | "openai") {
await providersDb.createProviderConnection({
provider,
authType: provider === "codex" ? "oauth" : "apikey",
name: `${provider}-gpt61-bare`,
email: provider === "codex" ? "codex@example.com" : undefined,
apiKey: provider === "openai" ? "sk-openai-gpt61-bare" : undefined,
accessToken: provider === "codex" ? "codex-gpt61-bare-access" : undefined,
isActive: true,
testStatus: "active",
providerSpecificData: provider === "codex" ? { workspaceId: "ws-gpt61-bare" } : {},
});
}

test.beforeEach(() => {
core.resetDbInstance();
apiKeysDb.resetApiKeyState();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
});

test.after(() => {
core.resetDbInstance();
apiKeysDb.resetApiKeyState();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});

test("the Codex catalog registers exactly the base GPT-6.1 Sol id and its six effort tiers", () => {
assert.deepEqual([...SOL_IDS].sort(), [...EXPECTED_IDS].sort());
});

test("every GPT-6.1 Sol id in the Codex catalog is Codex-native when unprefixed", () => {
for (const id of SOL_IDS) {
assert.equal(CODEX_NATIVE_UNPREFIXED_MODELS.has(id), true, id);
}
});

test("every GPT-6.1 Sol Codex id resolves a non-zero pricing row", () => {
for (const id of SOL_IDS) {
const price = getPricingForModel("cx", id);
assert.ok(price, `cx/${id}`);
assert.ok(price.input > 0 && price.output > 0, `cx/${id} must not resolve to $0`);
}
});

test("bare gpt-6.1-sol routes to Codex when Codex and OpenAI are both active", async () => {
await seedConnection("codex");
await seedConnection("openai");

for (const id of [MODEL, `${MODEL}-ultra`, `${MODEL}-low`]) {
const info = await getModelInfoCore(id, null);
assert.equal(info.provider, "codex", id);
assert.equal(info.model, id);
}
});

test("bare gpt-6.1-sol stays on OpenAI when no Codex connection is active", async () => {
await seedConnection("openai");

const info = await getModelInfoCore(MODEL, null);
assert.equal(info.provider, "openai");
assert.equal(info.model, MODEL);
});

test("/v1/models lists the bare GPT-6.1 Sol ids under their codex/ rows", async () => {
await seedConnection("codex");

v1ModelsCatalog.__resetCatalogBuilderRunsForTest();
const response = await v1ModelsCatalog.getUnifiedModelsResponse(
new Request("http://localhost/api/v1/models")
);
const body = (await response.json()) as { data: Array<{ id: string; parent?: string | null }> };
const parentById = new Map(body.data.map((row) => [row.id, row.parent ?? null]));

for (const id of SOL_IDS) {
assert.equal(parentById.get(id), `codex/${id}`, id);
}
});
6 changes: 3 additions & 3 deletions tests/unit/executor-codex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

Expand All @@ -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");
}
);
Expand Down
Loading
Loading