diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts index 21464291ec5..9b618a6dbc8 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -27,6 +27,7 @@ import { cloneFirstTemplateModel, findCatalogTemplate, isOpenAIApiBaseUrl, + isOpenAICodexBaseUrl, matchesExactOrPrefix, } from "./shared.js"; import { @@ -83,14 +84,6 @@ const OPENAI_CODEX_MODERN_MODEL_IDS = [ ] as const; const OPENAI_RESPONSES_STREAM_HOOKS = buildProviderStreamFamilyHooks("openai-responses-defaults"); -function isOpenAICodexBaseUrl(baseUrl?: string): boolean { - const trimmed = baseUrl?.trim(); - if (!trimmed) { - return false; - } - return /^https?:\/\/chatgpt\.com\/backend-api\/?$/i.test(trimmed); -} - function normalizeCodexTransport(model: ProviderRuntimeModel): ProviderRuntimeModel { const useCodexTransport = !model.baseUrl || isOpenAIApiBaseUrl(model.baseUrl) || isOpenAICodexBaseUrl(model.baseUrl); diff --git a/extensions/openai/shared.ts b/extensions/openai/shared.ts index f31371a2e22..a01ac442252 100644 --- a/extensions/openai/shared.ts +++ b/extensions/openai/shared.ts @@ -23,4 +23,12 @@ export function isOpenAIApiBaseUrl(baseUrl?: string): boolean { return /^https?:\/\/api\.openai\.com(?:\/v1)?\/?$/i.test(trimmed); } +export function isOpenAICodexBaseUrl(baseUrl?: string): boolean { + const trimmed = baseUrl?.trim(); + if (!trimmed) { + return false; + } + return /^https?:\/\/chatgpt\.com\/backend-api\/?$/i.test(trimmed); +} + export { cloneFirstTemplateModel, findCatalogTemplate, matchesExactOrPrefix }; diff --git a/extensions/openai/transport-policy.test.ts b/extensions/openai/transport-policy.test.ts index 71eaf312359..65c05c9b371 100644 --- a/extensions/openai/transport-policy.test.ts +++ b/extensions/openai/transport-policy.test.ts @@ -104,4 +104,26 @@ describe("openai transport policy", () => { degradeCooldownMs: 60_000, }); }); + + it("treats ChatGPT Codex backend routes as native OpenAI-family transports", () => { + expect( + resolveOpenAIWebSocketSessionPolicy({ + provider: "openai-codex", + modelId: "gpt-5.4", + model: { + ...nativeModel, + provider: "openai-codex", + api: "openai-codex-responses", + baseUrl: "https://chatgpt.com/backend-api", + }, + sessionId: "session-123", + }), + ).toMatchObject({ + headers: { + "x-client-request-id": "session-123", + "x-openclaw-session-id": "session-123", + }, + degradeCooldownMs: 60_000, + }); + }); }); diff --git a/extensions/openai/transport-policy.ts b/extensions/openai/transport-policy.ts index a87aaf9daa3..60be75e9ded 100644 --- a/extensions/openai/transport-policy.ts +++ b/extensions/openai/transport-policy.ts @@ -5,20 +5,12 @@ import type { ProviderWebSocketSessionPolicy, } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared"; -import { isOpenAIApiBaseUrl } from "./shared.js"; +import { isOpenAIApiBaseUrl, isOpenAICodexBaseUrl } from "./shared.js"; const DEFAULT_OPENAI_WS_DEGRADE_COOLDOWN_MS = 60_000; const AZURE_PROVIDER_IDS = new Set(["azure-openai", "azure-openai-responses"]); const OPENAI_CODEX_PROVIDER_ID = "openai-codex"; -function isOpenAICodexBaseUrl(baseUrl?: string): boolean { - const trimmed = baseUrl?.trim(); - if (!trimmed) { - return false; - } - return /^https?:\/\/chatgpt\.com\/backend-api\/?$/i.test(trimmed); -} - function isAzureOpenAIBaseUrl(baseUrl?: string): boolean { const trimmed = baseUrl?.trim(); if (!trimmed) {