fix: set codex oauth model default

This commit is contained in:
Peter Steinberger
2026-01-06 02:48:53 +01:00
parent 17db03ad55
commit 20705d1b37
6 changed files with 179 additions and 1 deletions

View File

@@ -87,4 +87,76 @@ describe("getApiKeyForModel", () => {
await fs.rm(tempDir, { recursive: true, force: true });
}
});
it("suggests openai-codex when only Codex OAuth is configured", async () => {
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
const previousOpenAiKey = process.env.OPENAI_API_KEY;
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
try {
delete process.env.OPENAI_API_KEY;
process.env.CLAWDBOT_STATE_DIR = tempDir;
process.env.CLAWDBOT_AGENT_DIR = path.join(tempDir, "agent");
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
const authProfilesPath = path.join(
tempDir,
"agent",
"auth-profiles.json",
);
await fs.mkdir(path.dirname(authProfilesPath), {
recursive: true,
mode: 0o700,
});
await fs.writeFile(
authProfilesPath,
`${JSON.stringify(
{
version: 1,
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
...oauthFixture,
},
},
},
null,
2,
)}\n`,
"utf8",
);
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
await expect(
resolveApiKeyForProvider({ provider: "openai" }),
).rejects.toThrow(/openai-codex\/gpt-5\\.2/);
} finally {
if (previousOpenAiKey === undefined) {
delete process.env.OPENAI_API_KEY;
} else {
process.env.OPENAI_API_KEY = previousOpenAiKey;
}
if (previousStateDir === undefined) {
delete process.env.CLAWDBOT_STATE_DIR;
} else {
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
}
if (previousAgentDir === undefined) {
delete process.env.CLAWDBOT_AGENT_DIR;
} else {
process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
}
if (previousPiAgentDir === undefined) {
delete process.env.PI_CODING_AGENT_DIR;
} else {
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
}
await fs.rm(tempDir, { recursive: true, force: true });
}
});
});

View File

@@ -5,6 +5,7 @@ import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
import {
type AuthProfileStore,
ensureAuthProfileStore,
listProfilesForProvider,
resolveApiKeyForProfile,
resolveAuthProfileOrder,
} from "./auth-profiles.js";
@@ -83,6 +84,15 @@ export async function resolveApiKeyForProvider(params: {
return { apiKey: customKey, source: "models.json" };
}
if (provider === "openai") {
const hasCodex = listProfilesForProvider(store, "openai-codex").length > 0;
if (hasCodex) {
throw new Error(
'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.2 (ChatGPT OAuth) or set OPENAI_API_KEY for openai/gpt-5.2.',
);
}
}
throw new Error(`No API key found for provider "${provider}".`);
}

View File

@@ -6,6 +6,11 @@ import {
type OAuthCredentials,
type OAuthProvider,
} from "@mariozechner/pi-ai";
import { ensureAuthProfileStore, listProfilesForProvider } from "../agents/auth-profiles.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { getCustomProviderApiKey, resolveEnvApiKey } from "../agents/model-auth.js";
import { loadModelCatalog } from "../agents/model-catalog.js";
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
import {
isRemoteEnvironment,
loginAntigravityVpsAware,
@@ -58,6 +63,82 @@ import { defaultRuntime } from "../runtime.js";
import { resolveUserPath, sleep } from "../utils.js";
import type { WizardPrompter } from "./prompts.js";
const OPENAI_CODEX_DEFAULT_MODEL = "openai-codex/gpt-5.2";
function shouldSetOpenAICodexModel(model?: string): boolean {
const trimmed = model?.trim();
if (!trimmed) return true;
const normalized = trimmed.toLowerCase();
if (normalized.startsWith("openai-codex/")) return false;
if (normalized.startsWith("openai/")) return true;
return normalized === "gpt" || normalized === "gpt-mini";
}
function applyOpenAICodexModelDefault(
cfg: ClawdbotConfig,
): { next: ClawdbotConfig; changed: boolean } {
if (!shouldSetOpenAICodexModel(cfg.agent?.model)) {
return { next: cfg, changed: false };
}
return {
next: {
...cfg,
agent: {
...cfg.agent,
model: OPENAI_CODEX_DEFAULT_MODEL,
},
},
changed: true,
};
}
async function warnIfModelConfigLooksOff(
config: ClawdbotConfig,
prompter: WizardPrompter,
) {
const ref = resolveConfiguredModelRef({
cfg: config,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL,
});
const warnings: string[] = [];
const catalog = await loadModelCatalog({ config, useCache: false });
if (catalog.length > 0) {
const known = catalog.some(
(entry) => entry.provider === ref.provider && entry.id === ref.model,
);
if (!known) {
warnings.push(
`Model not found: ${ref.provider}/${ref.model}. Update agent.model or run /models list.`,
);
}
}
const store = ensureAuthProfileStore();
const hasProfile = listProfilesForProvider(store, ref.provider).length > 0;
const envKey = resolveEnvApiKey(ref.provider);
const customKey = getCustomProviderApiKey(config, ref.provider);
if (!hasProfile && !envKey && !customKey) {
warnings.push(
`No auth configured for provider "${ref.provider}". The agent may fail until credentials are added.`,
);
}
if (ref.provider === "openai") {
const hasCodex =
listProfilesForProvider(store, "openai-codex").length > 0;
if (hasCodex) {
warnings.push(
`Detected OpenAI Codex OAuth. Consider setting agent.model to ${OPENAI_CODEX_DEFAULT_MODEL}.`,
);
}
}
if (warnings.length > 0) {
await prompter.note(warnings.join("\n"), "Model check");
}
}
export async function runOnboardingWizard(
opts: OnboardOptions,
runtime: RuntimeEnv = defaultRuntime,
@@ -287,6 +368,14 @@ export async function runOnboardingWizard(
provider: "openai-codex",
mode: "oauth",
});
const applied = applyOpenAICodexModelDefault(nextConfig);
nextConfig = applied.next;
if (applied.changed) {
await prompter.note(
`Default model set to ${OPENAI_CODEX_DEFAULT_MODEL}`,
"Model configured",
);
}
}
} catch (err) {
spin.stop("OpenAI OAuth failed");
@@ -380,6 +469,8 @@ export async function runOnboardingWizard(
nextConfig = applyMinimaxConfig(nextConfig);
}
await warnIfModelConfigLooksOff(nextConfig, prompter);
const portRaw = await prompter.text({
message: "Gateway port",
initialValue: String(localPort),