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:
1 parent
65826d95f2
commit
9f41ee754b
8 files changed
+46
-11
No files matched your search
@@ -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 },
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
Reference in new issue
Block a user