From eea8b022965b627e8a0e125c6ce05f6e753e00d1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 21 Apr 2026 06:56:31 -0700 Subject: [PATCH] fix(openai): hide invalid codex imports in setup --- .../openai/openai-codex-provider.test.ts | 52 ++++++++++++++++--- extensions/openai/openai-codex-provider.ts | 37 +++++++------ extensions/openai/openai-provider.ts | 8 +-- extensions/openai/openclaw.plugin.json | 12 ++--- extensions/openai/provider-contract-api.ts | 22 ++++---- src/commands/auth-choice-options.test.ts | 6 +-- 6 files changed, 90 insertions(+), 47 deletions(-) diff --git a/extensions/openai/openai-codex-provider.test.ts b/extensions/openai/openai-codex-provider.test.ts index 27fc6f652f6..b95b0f07c49 100644 --- a/extensions/openai/openai-codex-provider.test.ts +++ b/extensions/openai/openai-codex-provider.test.ts @@ -158,11 +158,11 @@ describe("openai codex provider", () => { "import-codex-cli", ]); expect(provider.auth?.find((method) => method.id === "oauth")).toMatchObject({ - label: "OpenAI Codex Login", - hint: "Browser sign-in", + label: "OpenAI Codex Browser Login", + hint: "Sign in with OpenAI in your browser", wizard: { choiceId: "openai-codex", - choiceLabel: "OpenAI Codex Login", + choiceLabel: "OpenAI Codex Browser Login", assistantPriority: -30, }, }); @@ -177,13 +177,14 @@ describe("openai codex provider", () => { }, }); expect(provider.auth?.find((method) => method.id === "import-codex-cli")).toMatchObject({ - label: "OpenAI Codex", - hint: "Import existing ~/.codex login once", + label: "Import Existing Codex Login", + hint: "Import an existing ~/.codex login", kind: "oauth", wizard: { choiceId: "openai-codex-import", - choiceLabel: "OpenAI Codex", + choiceLabel: "Import Existing Codex Login", assistantPriority: -20, + assistantVisibility: "manual-only", }, }); }); @@ -194,13 +195,48 @@ describe("openai codex provider", () => { const provider = buildOpenAICodexProviderPlugin(); expect(provider.auth?.find((method) => method.id === "import-codex-cli")).toMatchObject({ - label: "OpenAI Codex (~/.codex existing key detected)", + label: "Import Existing Codex Login (~/.codex detected)", wizard: { - choiceLabel: "OpenAI Codex (~/.codex existing key detected)", + choiceLabel: "Import Existing Codex Login (~/.codex detected)", + assistantVisibility: "visible", }, }); }); + it("soft-fails import when no compatible ~/.codex login exists", async () => { + const provider = buildOpenAICodexProviderPlugin(); + const importMethod = provider.auth?.find((method) => method.id === "import-codex-cli"); + const note = vi.fn(async () => {}); + const runtime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), + }; + readOpenAICodexCliOAuthProfileMock.mockReturnValueOnce(null); + + const result = await importMethod?.run({ + config: {}, + env: process.env, + prompter: { + note, + progress: vi.fn(), + } as never, + runtime: runtime as never, + isRemote: false, + openUrl: async () => {}, + oauth: { createVpsAwareHandlers: (() => ({})) as never }, + }); + + expect(result).toEqual({ profiles: [] }); + expect(runtime.error).toHaveBeenCalledWith( + "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead.", + ); + expect(note).toHaveBeenCalledWith( + "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead.", + "Import Existing Codex Login", + ); + }); + it("stores device-code logins as OpenAI Codex oauth profiles", async () => { const provider = buildOpenAICodexProviderPlugin(); const deviceCodeMethod = provider.auth?.find((method) => method.id === "device-code"); diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts index 47f342a7b29..263264a2cff 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -49,10 +49,13 @@ const OPENAI_WIZARD_GROUP = { const OPENAI_CODEX_LOGIN_ASSISTANT_PRIORITY = -30; const OPENAI_CODEX_IMPORT_ASSISTANT_PRIORITY = -20; const OPENAI_CODEX_DEVICE_PAIRING_ASSISTANT_PRIORITY = -10; -const OPENAI_CODEX_LOGIN_LABEL = "OpenAI Codex Login"; -const OPENAI_CODEX_IMPORT_LABEL = "OpenAI Codex"; -const OPENAI_CODEX_IMPORT_DETECTED_SUFFIX = "~/.codex existing key detected"; +const OPENAI_CODEX_LOGIN_LABEL = "OpenAI Codex Browser Login"; +const OPENAI_CODEX_LOGIN_HINT = "Sign in with OpenAI in your browser"; +const OPENAI_CODEX_IMPORT_LABEL = "Import Existing Codex Login"; +const OPENAI_CODEX_IMPORT_HINT = "Import an existing ~/.codex login"; +const OPENAI_CODEX_IMPORT_DETECTED_SUFFIX = "~/.codex detected"; const OPENAI_CODEX_DEVICE_PAIRING_LABEL = "OpenAI Codex Device Pairing"; +const OPENAI_CODEX_DEVICE_PAIRING_HINT = "Pair in browser with a device code"; const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4"; const OPENAI_CODEX_GPT_54_LEGACY_MODEL_ID = "gpt-5.4-codex"; const OPENAI_CODEX_GPT_54_PRO_MODEL_ID = "gpt-5.4-pro"; @@ -378,9 +381,11 @@ async function runImportOpenAICodexCliAuth(ctx: ProviderAuthContext) { store: ensureAuthProfileStoreForLocalUpdate(ctx.agentDir), }); if (!profile) { - throw new Error( - "No compatible Codex CLI OAuth login found. Sign in with `codex` first or use ChatGPT OAuth instead.", - ); + const message = + "No compatible ~/.codex ChatGPT login found. Use Browser Login or Device Pairing instead."; + ctx.runtime.error(message); + await ctx.prompter.note(message, OPENAI_CODEX_IMPORT_LABEL); + return { profiles: [] }; } return { @@ -424,15 +429,16 @@ function buildOpenAICodexAuthDoctorHint(ctx: { profileId?: string }) { return "Deprecated profile. Run `openclaw models auth login --provider openai-codex` or `openclaw configure`."; } -function buildOpenAICodexImportWizardLabel() { - if (!hasOpenAICodexCliOAuthCredential()) { +function buildOpenAICodexImportWizardLabel(hasCodexCliCredential: boolean) { + if (!hasCodexCliCredential) { return OPENAI_CODEX_IMPORT_LABEL; } return `${OPENAI_CODEX_IMPORT_LABEL} (${OPENAI_CODEX_IMPORT_DETECTED_SUFFIX})`; } export function buildOpenAICodexProviderPlugin(): ProviderPlugin { - const importWizardLabel = buildOpenAICodexImportWizardLabel(); + const hasCodexCliCredential = hasOpenAICodexCliOAuthCredential(); + const importWizardLabel = buildOpenAICodexImportWizardLabel(hasCodexCliCredential); return { id: PROVIDER_ID, label: "OpenAI Codex", @@ -441,12 +447,12 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { { id: "oauth", label: OPENAI_CODEX_LOGIN_LABEL, - hint: "Browser sign-in", + hint: OPENAI_CODEX_LOGIN_HINT, kind: "oauth", wizard: { choiceId: "openai-codex", choiceLabel: OPENAI_CODEX_LOGIN_LABEL, - choiceHint: "Browser sign-in", + choiceHint: OPENAI_CODEX_LOGIN_HINT, assistantPriority: OPENAI_CODEX_LOGIN_ASSISTANT_PRIORITY, ...OPENAI_WIZARD_GROUP, }, @@ -455,12 +461,12 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { { id: "device-code", label: OPENAI_CODEX_DEVICE_PAIRING_LABEL, - hint: "Pair in browser with a device code", + hint: OPENAI_CODEX_DEVICE_PAIRING_HINT, kind: "device_code", wizard: { choiceId: "openai-codex-device-code", choiceLabel: OPENAI_CODEX_DEVICE_PAIRING_LABEL, - choiceHint: "Pair in browser with a device code", + choiceHint: OPENAI_CODEX_DEVICE_PAIRING_HINT, assistantPriority: OPENAI_CODEX_DEVICE_PAIRING_ASSISTANT_PRIORITY, ...OPENAI_WIZARD_GROUP, }, @@ -475,13 +481,14 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { { id: "import-codex-cli", label: importWizardLabel, - hint: "Import existing ~/.codex login once", + hint: OPENAI_CODEX_IMPORT_HINT, kind: "oauth", wizard: { choiceId: "openai-codex-import", choiceLabel: importWizardLabel, - choiceHint: "Import existing ~/.codex login once", + choiceHint: OPENAI_CODEX_IMPORT_HINT, assistantPriority: OPENAI_CODEX_IMPORT_ASSISTANT_PRIORITY, + assistantVisibility: hasCodexCliCredential ? "visible" : "manual-only", ...OPENAI_WIZARD_GROUP, }, run: async (ctx) => await runImportOpenAICodexCliAuth(ctx), diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index 13c496dbc46..6e4b5405cd7 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -185,8 +185,8 @@ export function buildOpenAIProvider(): ProviderPlugin { createProviderApiKeyAuthMethod({ providerId: PROVIDER_ID, methodId: "api-key", - label: "OpenAI API key", - hint: "Direct OpenAI API key", + label: "OpenAI API Key", + hint: "Use your OpenAI API key directly", optionKey: "openaiApiKey", flagName: "--openai-api-key", envVar: "OPENAI_API_KEY", @@ -196,10 +196,10 @@ export function buildOpenAIProvider(): ProviderPlugin { applyConfig: (cfg) => applyOpenAIConfig(cfg), wizard: { choiceId: "openai-api-key", - choiceLabel: "OpenAI API key", + choiceLabel: "OpenAI API Key", groupId: "openai", groupLabel: "OpenAI", - groupHint: "Codex OAuth + API key", + groupHint: "API key + Codex auth", }, }), ], diff --git a/extensions/openai/openclaw.plugin.json b/extensions/openai/openclaw.plugin.json index 5def6f714c3..cf049d43267 100644 --- a/extensions/openai/openclaw.plugin.json +++ b/extensions/openai/openclaw.plugin.json @@ -15,8 +15,8 @@ "method": "oauth", "choiceId": "openai-codex", "deprecatedChoiceIds": ["codex-cli"], - "choiceLabel": "OpenAI Codex Login", - "choiceHint": "Browser sign-in", + "choiceLabel": "OpenAI Codex Browser Login", + "choiceHint": "Sign in with OpenAI in your browser", "assistantPriority": -30, "groupId": "openai", "groupLabel": "OpenAI", @@ -37,8 +37,8 @@ "provider": "openai-codex", "method": "import-codex-cli", "choiceId": "openai-codex-import", - "choiceLabel": "OpenAI Codex", - "choiceHint": "Import existing ~/.codex login once", + "choiceLabel": "Import Existing Codex Login", + "choiceHint": "Import an existing ~/.codex login", "assistantPriority": -20, "groupId": "openai", "groupLabel": "OpenAI", @@ -48,7 +48,7 @@ "provider": "openai", "method": "api-key", "choiceId": "openai-api-key", - "choiceLabel": "OpenAI API key", + "choiceLabel": "OpenAI API Key", "assistantPriority": -40, "groupId": "openai", "groupLabel": "OpenAI", @@ -56,7 +56,7 @@ "optionKey": "openaiApiKey", "cliFlag": "--openai-api-key", "cliOption": "--openai-api-key ", - "cliDescription": "OpenAI API key" + "cliDescription": "OpenAI API Key" } ], "contracts": { diff --git a/extensions/openai/provider-contract-api.ts b/extensions/openai/provider-contract-api.ts index f208fd42571..ec492daf5af 100644 --- a/extensions/openai/provider-contract-api.ts +++ b/extensions/openai/provider-contract-api.ts @@ -16,13 +16,13 @@ export function createOpenAICodexProvider(): ProviderPlugin { { id: "oauth", kind: "oauth", - label: "OpenAI Codex Login", - hint: "Browser sign-in", + label: "OpenAI Codex Browser Login", + hint: "Sign in with OpenAI in your browser", run: noopAuth, wizard: { choiceId: "openai-codex", - choiceLabel: "OpenAI Codex Login", - choiceHint: "Browser sign-in", + choiceLabel: "OpenAI Codex Browser Login", + choiceHint: "Sign in with OpenAI in your browser", assistantPriority: -30, ...OPENAI_WIZARD_GROUP, }, @@ -44,13 +44,13 @@ export function createOpenAICodexProvider(): ProviderPlugin { { id: "import-codex-cli", kind: "oauth", - label: "OpenAI Codex", - hint: "Import existing ~/.codex login once", + label: "Import Existing Codex Login", + hint: "Import an existing ~/.codex login", run: noopAuth, wizard: { choiceId: "openai-codex-import", - choiceLabel: "OpenAI Codex", - choiceHint: "Import existing ~/.codex login once", + choiceLabel: "Import Existing Codex Login", + choiceHint: "Import an existing ~/.codex login", assistantPriority: -20, ...OPENAI_WIZARD_GROUP, }, @@ -70,12 +70,12 @@ export function createOpenAIProvider(): ProviderPlugin { { id: "api-key", kind: "api_key", - label: "OpenAI API key", - hint: "Direct OpenAI API key", + label: "OpenAI API Key", + hint: "Use your OpenAI API key directly", run: noopAuth, wizard: { choiceId: "openai-api-key", - choiceLabel: "OpenAI API key", + choiceLabel: "OpenAI API Key", assistantPriority: -40, ...OPENAI_WIZARD_GROUP, }, diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index 648d6dcad8e..88734f3e9b4 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -451,21 +451,21 @@ describe("buildAuthChoiceOptions", () => { resolveProviderWizardOptions.mockReturnValue([ { value: "openai-api-key", - label: "OpenAI API key", + label: "OpenAI API Key", groupId: "openai", groupLabel: "OpenAI", assistantPriority: -40, }, { value: "openai-codex", - label: "OpenAI Codex Login", + label: "OpenAI Codex Browser Login", groupId: "openai", groupLabel: "OpenAI", assistantPriority: -30, }, { value: "openai-codex-import", - label: "OpenAI Codex (~/.codex existing key detected)", + label: "Import Existing Codex Login (~/.codex detected)", groupId: "openai", groupLabel: "OpenAI", assistantPriority: -20,