feat(codex): expose 1M context variants for GPT-6 and GPT-5.6

Add [1m] extended-context variants for gpt-6-astra/sol/luna and
gpt-5.6-sol/terra/luna mapping to their base upstream model IDs with
an 872k-token context window, share the codex capability table with
the cx alias, align model discovery with the inference client version,
respect per-connection enabledModels during account selection, and
fall back to another account when Codex reports a model unsupported
for a ChatGPT account.
This commit is contained in:
Tasa authored and decolua committed 2026-09-28 19:09:39 +07:00
1 parent 65826d95f2
commit 9f41ee754b
8 files changed
+46 -11

No files matched your search

+1
View File
@@ -58,6 +58,7 @@ const COOLDOWN = {
*/ */
export const ERROR_RULES = [ export const ERROR_RULES = [
// --- Text-based rules (checked first, order = priority) --- // --- Text-based rules (checked first, order = priority) ---
{ provider: "codex", text: "model is not supported when using codex with a chatgpt account", cooldownMs: MAX_RATE_LIMIT_COOLDOWN_MS },
{ text: "no credentials", cooldownMs: COOLDOWN.long }, { text: "no credentials", cooldownMs: COOLDOWN.long },
{ text: "request not allowed", cooldownMs: COOLDOWN.short }, { text: "request not allowed", cooldownMs: COOLDOWN.short },
{ text: "improperly formed request", cooldownMs: COOLDOWN.long }, { text: "improperly formed request", cooldownMs: COOLDOWN.long },
+8
View File
@@ -161,6 +161,7 @@ const KIRO_GPT_5_6_CAPABILITIES = { vision: true, reasoning: true, search: true,
// (lower than OpenAI API's 1.05M). Sol differs from Terra/Luna. #2720 // (lower than OpenAI API's 1.05M). Sol differs from Terra/Luna. #2720
const CODEX_GPT_56_SOL_CAPS = { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 372000, maxOutput: 128000 }; const CODEX_GPT_56_SOL_CAPS = { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 372000, maxOutput: 128000 };
const CODEX_GPT_56_DEFAULT_CAPS = { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 }; const CODEX_GPT_56_DEFAULT_CAPS = { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 };
const CODEX_EXTENDED_CAPS = { ...CODEX_GPT_56_DEFAULT_CAPS, contextWindow: 872000 };
/** /**
* Provider-specific capability overrides. Keyed by provider alias/id. * Provider-specific capability overrides. Keyed by provider alias/id.
@@ -185,6 +186,12 @@ export const PROVIDER_CAPABILITIES = {
"gpt-6-astra": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 }, "gpt-6-astra": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 },
"gpt-6-sol": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 }, "gpt-6-sol": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 },
"gpt-6-luna": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 }, "gpt-6-luna": { vision: true, reasoning: true, search: true, thinkingFormat: "openai", contextWindow: 272000, maxOutput: 128000 },
"gpt-6-astra[1m]": CODEX_EXTENDED_CAPS,
"gpt-6-sol[1m]": CODEX_EXTENDED_CAPS,
"gpt-6-luna[1m]": CODEX_EXTENDED_CAPS,
"gpt-5.6-sol[1m]": CODEX_EXTENDED_CAPS,
"gpt-5.6-terra[1m]": CODEX_EXTENDED_CAPS,
"gpt-5.6-luna[1m]": CODEX_EXTENDED_CAPS,
"gpt-5.6-sol": CODEX_GPT_56_SOL_CAPS, "gpt-5.6-sol": CODEX_GPT_56_SOL_CAPS,
"gpt-5.6-sol-review": CODEX_GPT_56_SOL_CAPS, "gpt-5.6-sol-review": CODEX_GPT_56_SOL_CAPS,
"gpt-5.6-terra": CODEX_GPT_56_DEFAULT_CAPS, "gpt-5.6-terra": CODEX_GPT_56_DEFAULT_CAPS,
@@ -268,6 +275,7 @@ export const PROVIDER_CAPABILITIES = {
// Qoder CN serves the identical model catalog from the CN gateway, so it shares // Qoder CN serves the identical model catalog from the CN gateway, so it shares
// the intl Qoder capability table verbatim (vision/reasoning/contextWindow). // the intl Qoder capability table verbatim (vision/reasoning/contextWindow).
PROVIDER_CAPABILITIES["qoder-cn"] = PROVIDER_CAPABILITIES["qoder"]; PROVIDER_CAPABILITIES["qoder-cn"] = PROVIDER_CAPABILITIES["qoder"];
PROVIDER_CAPABILITIES.cx = PROVIDER_CAPABILITIES.codex;
/** /**
* Pattern fallback — glob (* = wildcard), matched case-insensitively and * Pattern fallback — glob (* = wildcard), matched case-insensitively and
+6
View File
@@ -53,13 +53,19 @@ export default {
}, },
models: [ models: [
{ id: "gpt-6-astra", name: "GPT 6.0 Astra" }, { id: "gpt-6-astra", name: "GPT 6.0 Astra" },
{ id: "gpt-6-astra[1m]", name: "GPT 6.0 Astra (extended context)", upstreamModelId: "gpt-6-astra" },
{ id: "gpt-6-sol", name: "GPT 6.0 Sol", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS }, { id: "gpt-6-sol", name: "GPT 6.0 Sol", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS },
{ id: "gpt-6-sol[1m]", name: "GPT 6.0 Sol (extended context)", upstreamModelId: "gpt-6-sol", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS },
{ id: "gpt-6-luna", name: "GPT 6.0 Luna", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS }, { id: "gpt-6-luna", name: "GPT 6.0 Luna", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS },
{ id: "gpt-6-luna[1m]", name: "GPT 6.0 Luna (extended context)", upstreamModelId: "gpt-6-luna", responsesLite: true, thinkingLevels: GPT_6_LITE_THINKING_LEVELS },
{ id: "gpt-5.6-sol", name: "GPT 5.6 Sol" }, { id: "gpt-5.6-sol", name: "GPT 5.6 Sol" },
{ id: "gpt-5.6-sol[1m]", name: "GPT 5.6 Sol (extended context)", upstreamModelId: "gpt-5.6-sol" },
{ id: "gpt-5.6-sol-review", name: "GPT 5.6 Sol Review", upstreamModelId: "gpt-5.6-sol", quotaFamily: "review" }, { id: "gpt-5.6-sol-review", name: "GPT 5.6 Sol Review", upstreamModelId: "gpt-5.6-sol", quotaFamily: "review" },
{ id: "gpt-5.6-terra", name: "GPT 5.6 Terra" }, { id: "gpt-5.6-terra", name: "GPT 5.6 Terra" },
{ id: "gpt-5.6-terra[1m]", name: "GPT 5.6 Terra (extended context)", upstreamModelId: "gpt-5.6-terra" },
{ id: "gpt-5.6-terra-review", name: "GPT 5.6 Terra Review", upstreamModelId: "gpt-5.6-terra", quotaFamily: "review" }, { id: "gpt-5.6-terra-review", name: "GPT 5.6 Terra Review", upstreamModelId: "gpt-5.6-terra", quotaFamily: "review" },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna" }, { id: "gpt-5.6-luna", name: "GPT 5.6 Luna" },
{ id: "gpt-5.6-luna[1m]", name: "GPT 5.6 Luna (extended context)", upstreamModelId: "gpt-5.6-luna" },
{ id: "gpt-5.6-luna-review", name: "GPT 5.6 Luna Review", upstreamModelId: "gpt-5.6-luna", quotaFamily: "review" }, { id: "gpt-5.6-luna-review", name: "GPT 5.6 Luna Review", upstreamModelId: "gpt-5.6-luna", quotaFamily: "review" },
{ id: "gpt-5.5", name: "GPT 5.5" }, { id: "gpt-5.5", name: "GPT 5.5" },
{ id: "gpt-5.5-review", name: "GPT 5.5 Review", upstreamModelId: "gpt-5.5", quotaFamily: "review" }, { id: "gpt-5.5-review", name: "GPT 5.5 Review", upstreamModelId: "gpt-5.5", quotaFamily: "review" },
+2 -1
View File
@@ -20,12 +20,13 @@ export function getQuotaCooldown(backoffLevel = 0) {
* @param {number} backoffLevel - Current backoff level for exponential backoff * @param {number} backoffLevel - Current backoff level for exponential backoff
* @returns {{ shouldFallback: boolean, cooldownMs: number, newBackoffLevel?: number }} * @returns {{ shouldFallback: boolean, cooldownMs: number, newBackoffLevel?: number }}
*/ */
export function checkFallbackError(status, errorText, backoffLevel = 0) { export function checkFallbackError(status, errorText, backoffLevel = 0, provider = null) {
const lowerError = errorText const lowerError = errorText
? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase() ? (typeof errorText === "string" ? errorText : JSON.stringify(errorText)).toLowerCase()
: ""; : "";
for (const rule of ERROR_RULES) { for (const rule of ERROR_RULES) {
if (rule.provider && rule.provider !== provider) continue;
// Text-based rule: match substring in error message // Text-based rule: match substring in error message
if (rule.text && lowerError && lowerError.includes(rule.text)) { if (rule.text && lowerError && lowerError.includes(rule.text)) {
if (rule.backoff) { if (rule.backoff) {
+3 -6
View File
@@ -13,15 +13,12 @@ import { resolveConnectionProxyConfig } from "@/lib/network/connectionProxy";
import { resolveCursorModels } from "open-sse/services/cursorModels.js"; import { resolveCursorModels } from "open-sse/services/cursorModels.js";
import { resolveZedModels } from "open-sse/shared/zedAuth.js"; import { resolveZedModels } from "open-sse/shared/zedAuth.js";
import { resolveClineModels, resolveClinepassModels } from "open-sse/services/clinepassModels.js"; import { resolveClineModels, resolveClinepassModels } from "open-sse/services/clinepassModels.js";
import codexProvider from "open-sse/providers/registry/codex.js";
const GEMINI_CLI_MODELS_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels"; const GEMINI_CLI_MODELS_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
// The /codex/models endpoint gates each entry by minimal_client_version against this // Model discovery must identify as the same Codex CLI version as inference.
// value, and codex CLI's own manifest (openai/codex codex-rs/models-manager/models.json) const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${codexProvider.transport.cliVersion}`;
// already requires 0.144.0 for its newest models, so a stale client_version here comes
// back 200 with those entries quietly missing instead of erroring.
const CODEX_CLIENT_VERSION = "0.144.6";
const CODEX_MODELS_URL = `https://chatgpt.com/backend-api/codex/models?client_version=${CODEX_CLIENT_VERSION}`;
const parseOpenAIStyleModels = (data) => { const parseOpenAIStyleModels = (data) => {
if (Array.isArray(data)) return data; if (Array.isArray(data)) return data;
+3 -3
View File
@@ -158,13 +158,13 @@ export async function handleChat(request, clientRawRequest = null) {
}); });
} }
return handleSingleModelChat(body, modelStr, clientRawRequest, request, apiKey); return handleSingleModelChat(body, modelStr, clientRawRequest, request, apiKey, contextMarker ? `${modelStr.slice(modelStr.indexOf("/") + 1)}[${contextMarker}]` : null);
} }
/** /**
* Handle single model chat request * Handle single model chat request
*/ */
async function handleSingleModelChat(body, modelStr, clientRawRequest = null, request = null, apiKey = null) { async function handleSingleModelChat(body, modelStr, clientRawRequest = null, request = null, apiKey = null, requestedModel = null) {
const modelInfo = await getModelInfo(modelStr); const modelInfo = await getModelInfo(modelStr);
// If provider is null, this might be a combo name - check and handle // If provider is null, this might be a combo name - check and handle
@@ -233,7 +233,7 @@ async function handleSingleModelChat(body, modelStr, clientRawRequest = null, re
let lastHeaders = null; let lastHeaders = null;
while (true) { while (true) {
const credentials = await getProviderCredentials(provider, excludeConnectionIds, model); const credentials = await getProviderCredentials(provider, excludeConnectionIds, model, { requestedModel: requestedModel || model });
// All accounts unavailable // All accounts unavailable
if (!credentials || credentials.allRateLimited) { if (!credentials || credentials.allRateLimited) {
+4 -1
View File
@@ -31,6 +31,7 @@ export async function getProviderCredentials(provider, excludeConnectionIds = nu
? excludeConnectionIds ? excludeConnectionIds
: (excludeConnectionIds ? new Set([excludeConnectionIds]) : new Set()); : (excludeConnectionIds ? new Set([excludeConnectionIds]) : new Set());
const preferredConnectionId = options?.preferredConnectionId || null; const preferredConnectionId = options?.preferredConnectionId || null;
const requestedModel = options?.requestedModel || model;
// Acquire mutex to prevent race conditions // Acquire mutex to prevent race conditions
const currentMutex = selectionMutex; const currentMutex = selectionMutex;
let resolveMutex; let resolveMutex;
@@ -85,6 +86,8 @@ export async function getProviderCredentials(provider, excludeConnectionIds = nu
const availableConnections = connections.filter(c => { const availableConnections = connections.filter(c => {
if (excludeSet.has(c.id)) return false; if (excludeSet.has(c.id)) return false;
if (isModelLockActive(c, model)) return false; if (isModelLockActive(c, model)) return false;
const enabled = c.providerSpecificData?.enabledModels;
if (providerId === "codex" && Array.isArray(enabled) && enabled.length && requestedModel && !enabled.includes(requestedModel)) return false;
// Antigravity: skip if live quota exhausted for this model // Antigravity: skip if live quota exhausted for this model
if (isAntigravity && model && antigravityQuotaCache) { if (isAntigravity && model && antigravityQuotaCache) {
const quota = antigravityQuotaCache.get(c.id)?.[model]; const quota = antigravityQuotaCache.get(c.id)?.[model];
@@ -259,7 +262,7 @@ export async function markAccountUnavailable(connectionId, status, errorText, pr
: Math.min(resetsAtMs - Date.now(), MAX_RATE_LIMIT_COOLDOWN_MS); : Math.min(resetsAtMs - Date.now(), MAX_RATE_LIMIT_COOLDOWN_MS);
newBackoffLevel = 0; newBackoffLevel = 0;
} else { } else {
({ shouldFallback, cooldownMs, newBackoffLevel } = checkFallbackError(status, errorText, backoffLevel)); ({ shouldFallback, cooldownMs, newBackoffLevel } = checkFallbackError(status, errorText, backoffLevel, resolveProviderId(provider)));
} }
if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 }; if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 };
+19
View File
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import codex from "../../open-sse/providers/registry/codex.js";
import { getModelUpstreamId } from "../../open-sse/config/providerModels.js";
import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js";
import { stripModelContextMarker } from "../../open-sse/utils/modelMarkers.js";
import { checkFallbackError } from "../../open-sse/services/accountFallback.js";
for (const id of ["gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]) {
const extended = `${id}[1m]`;
assert.equal(codex.models.find((model) => model.id === extended)?.upstreamModelId, id);
assert.equal(getModelUpstreamId("cx", extended), id);
assert.equal(getCapabilitiesForModel("codex", extended).contextWindow, 872000);
assert.equal(getCapabilitiesForModel("cx", extended).contextWindow, 872000);
assert.deepEqual(stripModelContextMarker(`cx/${extended}`), { model: `cx/${id}`, contextMarker: "1m" });
}
const unsupported = "The 'gpt-6-sol' model is not supported when using Codex with a ChatGPT account.";
assert.equal(checkFallbackError(400, unsupported, 0, "codex").shouldFallback, true);
assert.equal(checkFallbackError(400, "Invalid JSON body", 0, "codex").shouldFallback, false);
console.log("Codex extended models and account fallback OK");