Models: scope implicit codex baseUrl override

This commit is contained in:
Kros Dai
2026-03-08 09:32:21 -04:00
committed by Peter Steinberger
parent 374001c4a0
commit ec75643a09
3 changed files with 72 additions and 6 deletions

View File

@@ -56,6 +56,7 @@ describe("openai-codex implicit provider", () => {
api: "openai-codex-responses",
models: [],
});
expect(providers?.["openai-codex"]).not.toHaveProperty("apiKey");
});
});
});
@@ -104,4 +105,51 @@ describe("openai-codex implicit provider", () => {
});
});
});
it("preserves an existing baseUrl for explicit openai-codex config without oauth synthesis", async () => {
await withModelsTempHome(async () => {
await withTempEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS, async () => {
unsetEnv(MODELS_CONFIG_IMPLICIT_ENV_VARS);
const agentDir = resolveOpenClawAgentDir();
await fs.mkdir(agentDir, { recursive: true });
await fs.writeFile(
path.join(agentDir, "models.json"),
JSON.stringify(
{
providers: {
"openai-codex": {
baseUrl: "https://chatgpt.com/backend-api",
api: "openai-codex-responses",
models: [],
},
},
},
null,
2,
),
"utf8",
);
await ensureOpenClawModelsJson({
models: {
mode: "merge",
providers: {
"openai-codex": {
api: "openai-codex-responses",
models: [],
},
},
},
});
const parsed = await readGeneratedModelsJson<{
providers: Record<string, { baseUrl?: string; api?: string }>;
}>();
expect(parsed.providers["openai-codex"]).toMatchObject({
baseUrl: "https://chatgpt.com/backend-api",
api: "openai-codex-responses",
});
});
});
});
});

View File

@@ -1000,6 +1000,8 @@ function buildOpenAICodexProvider(): ProviderConfig {
return {
baseUrl: OPENAI_CODEX_BASE_URL,
api: "openai-codex-responses",
// Like Copilot, Codex resolves OAuth credentials from auth-profiles at
// runtime, so the snapshot only needs the canonical API surface.
models: [],
};
}

View File

@@ -142,10 +142,16 @@ async function readJson(pathname: string): Promise<unknown> {
async function resolveProvidersForModelsJson(params: {
cfg: OpenClawConfig;
agentDir: string;
}): Promise<Record<string, ProviderConfig>> {
}): Promise<{
providers: Record<string, ProviderConfig>;
authoritativeImplicitBaseUrlProviders: ReadonlySet<string>;
}> {
const { cfg, agentDir } = params;
const explicitProviders = cfg.models?.providers ?? {};
const implicitProviders = await resolveImplicitProviders({ agentDir, explicitProviders });
const authoritativeImplicitBaseUrlProviders = new Set<string>(
[...AUTHORITATIVE_IMPLICIT_BASEURL_PROVIDERS].filter((key) => Boolean(implicitProviders[key])),
);
const providers: Record<string, ProviderConfig> = mergeProviders({
implicit: implicitProviders,
explicit: explicitProviders,
@@ -163,7 +169,7 @@ async function resolveProvidersForModelsJson(params: {
if (implicitCopilot && !providers["github-copilot"]) {
providers["github-copilot"] = implicitCopilot;
}
return providers;
return { providers, authoritativeImplicitBaseUrlProviders };
}
function mergeWithExistingProviderSecrets(params: {
@@ -171,9 +177,15 @@ function mergeWithExistingProviderSecrets(params: {
existingProviders: Record<string, NonNullable<ModelsConfig["providers"]>[string]>;
secretRefManagedProviders: ReadonlySet<string>;
explicitBaseUrlProviders: ReadonlySet<string>;
authoritativeImplicitBaseUrlProviders: ReadonlySet<string>;
}): Record<string, ProviderConfig> {
const { nextProviders, existingProviders, secretRefManagedProviders, explicitBaseUrlProviders } =
params;
const {
nextProviders,
existingProviders,
secretRefManagedProviders,
explicitBaseUrlProviders,
authoritativeImplicitBaseUrlProviders,
} = params;
const mergedProviders: Record<string, ProviderConfig> = {};
for (const [key, entry] of Object.entries(existingProviders)) {
mergedProviders[key] = entry;
@@ -199,7 +211,7 @@ function mergeWithExistingProviderSecrets(params: {
preserved.apiKey = existing.apiKey;
}
if (
!AUTHORITATIVE_IMPLICIT_BASEURL_PROVIDERS.has(key) &&
!authoritativeImplicitBaseUrlProviders.has(key) &&
!explicitBaseUrlProviders.has(key) &&
typeof existing.baseUrl === "string" &&
existing.baseUrl
@@ -217,6 +229,7 @@ async function resolveProvidersForMode(params: {
providers: Record<string, ProviderConfig>;
secretRefManagedProviders: ReadonlySet<string>;
explicitBaseUrlProviders: ReadonlySet<string>;
authoritativeImplicitBaseUrlProviders: ReadonlySet<string>;
}): Promise<Record<string, ProviderConfig>> {
if (params.mode !== "merge") {
return params.providers;
@@ -234,6 +247,7 @@ async function resolveProvidersForMode(params: {
existingProviders,
secretRefManagedProviders: params.secretRefManagedProviders,
explicitBaseUrlProviders: params.explicitBaseUrlProviders,
authoritativeImplicitBaseUrlProviders: params.authoritativeImplicitBaseUrlProviders,
});
}
@@ -300,7 +314,8 @@ export async function ensureOpenClawModelsJson(
// through the full loadConfig() pipeline which applies these.
applyConfigEnvVars(cfg);
const providers = await resolveProvidersForModelsJson({ cfg, agentDir });
const { providers, authoritativeImplicitBaseUrlProviders } =
await resolveProvidersForModelsJson({ cfg, agentDir });
if (Object.keys(providers).length === 0) {
return { agentDir, wrote: false };
@@ -331,6 +346,7 @@ export async function ensureOpenClawModelsJson(
providers: normalizedProviders,
secretRefManagedProviders,
explicitBaseUrlProviders,
authoritativeImplicitBaseUrlProviders,
});
const next = `${JSON.stringify({ providers: mergedProviders }, null, 2)}\n`;
const existingRaw = await readRawFile(targetPath);