From 3053324110f55d25bdb9d63fd787ccd9a9f7c5cf Mon Sep 17 00:00:00 2001 From: Kros Dai Date: Sun, 8 Mar 2026 09:09:09 -0400 Subject: [PATCH] fix: add implicit openai-codex provider snapshot --- ...dels-config.providers.openai-codex.test.ts | 107 ++++++++++++++++++ src/agents/models-config.providers.ts | 15 +++ src/agents/models-config.ts | 2 + 3 files changed, 124 insertions(+) create mode 100644 src/agents/models-config.providers.openai-codex.test.ts diff --git a/src/agents/models-config.providers.openai-codex.test.ts b/src/agents/models-config.providers.openai-codex.test.ts new file mode 100644 index 00000000000..20df80e1885 --- /dev/null +++ b/src/agents/models-config.providers.openai-codex.test.ts @@ -0,0 +1,107 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { resolveOpenClawAgentDir } from "./agent-paths.js"; +import { + installModelsConfigTestHooks, + MODELS_CONFIG_IMPLICIT_ENV_VARS, + unsetEnv, + withModelsTempHome, + withTempEnv, +} from "./models-config.e2e-harness.js"; +import { ensureOpenClawModelsJson } from "./models-config.js"; +import { resolveImplicitProviders } from "./models-config.providers.js"; +import { readGeneratedModelsJson } from "./models-config.test-utils.js"; + +installModelsConfigTestHooks(); + +async function writeCodexOauthProfile(agentDir: string) { + await fs.mkdir(agentDir, { recursive: true }); + await fs.writeFile( + path.join(agentDir, "auth-profiles.json"), + JSON.stringify( + { + version: 1, + profiles: { + "openai-codex:default": { + type: "oauth", + provider: "openai-codex", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + order: { + "openai-codex": ["openai-codex:default"], + }, + }, + null, + 2, + ), + "utf8", + ); +} + +describe("openai-codex implicit provider", () => { + it("injects an implicit provider when Codex OAuth exists", async () => { + await withModelsTempHome(async () => { + await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => { + unsetEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS); + const agentDir = resolveOpenClawAgentDir(); + await writeCodexOauthProfile(agentDir); + + const providers = await resolveImplicitProviders({ agentDir }); + expect(providers?.["openai-codex"]).toMatchObject({ + baseUrl: "https://chatgpt.com/backend-api", + api: "openai-codex-responses", + models: [], + }); + }); + }); + }); + + it("replaces stale openai-codex baseUrl in generated models.json", async () => { + await withModelsTempHome(async () => { + await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => { + unsetEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS); + const agentDir = resolveOpenClawAgentDir(); + await writeCodexOauthProfile(agentDir); + await fs.writeFile( + path.join(agentDir, "models.json"), + JSON.stringify( + { + providers: { + "openai-codex": { + baseUrl: "https://api.openai.com/v1", + api: "openai-responses", + models: [ + { + id: "gpt-5.4", + name: "GPT-5.4", + api: "openai-responses", + contextWindow: 1_000_000, + maxTokens: 100_000, + }, + ], + }, + }, + }, + null, + 2, + ), + "utf8", + ); + + await ensureOpenClawModelsJson({}); + + const parsed = await readGeneratedModelsJson<{ + providers: Record; + }>(); + expect(parsed.providers["openai-codex"]).toMatchObject({ + baseUrl: "https://chatgpt.com/backend-api", + api: "openai-codex-responses", + }); + }); + }); + }); +}); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a98f7bc0446..eac89af3501 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -207,6 +207,8 @@ const NVIDIA_DEFAULT_COST = { cacheWrite: 0, }; +const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api"; + const log = createSubsystemLogger("agents/model-providers"); interface OllamaModel { @@ -994,6 +996,14 @@ function buildOpenrouterProvider(): ProviderConfig { }; } +function buildOpenAICodexProvider(): ProviderConfig { + return { + baseUrl: OPENAI_CODEX_BASE_URL, + api: "openai-codex-responses", + models: [], + }; +} + async function buildVllmProvider(params?: { baseUrl?: string; apiKey?: string; @@ -1302,6 +1312,11 @@ export async function resolveImplicitProviders(params: { providers.openrouter = { ...buildOpenrouterProvider(), apiKey: openrouterKey }; } + const openaiCodexProfiles = listProfilesForProvider(authStore, "openai-codex"); + if (openaiCodexProfiles.length > 0) { + providers["openai-codex"] = buildOpenAICodexProvider(); + } + const nvidiaKey = resolveProviderApiKey("nvidia").apiKey; if (nvidiaKey) { providers.nvidia = { ...buildNvidiaProvider(), apiKey: nvidiaKey }; diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index cb4c76cfe56..c67c00549d3 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -22,6 +22,7 @@ type ModelsConfig = NonNullable; const DEFAULT_MODE: NonNullable = "merge"; const MODELS_JSON_WRITE_LOCKS = new Map>(); +const AUTHORITATIVE_IMPLICIT_BASEURL_PROVIDERS = new Set(["openai-codex"]); function isPositiveFiniteTokenLimit(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value > 0; @@ -198,6 +199,7 @@ function mergeWithExistingProviderSecrets(params: { preserved.apiKey = existing.apiKey; } if ( + !AUTHORITATIVE_IMPLICIT_BASEURL_PROVIDERS.has(key) && !explicitBaseUrlProviders.has(key) && typeof existing.baseUrl === "string" && existing.baseUrl