mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 15:04:46 +00:00
fix(configure): unify OpenAI auth provider picker (#82324)
This commit is contained in:
committed by
GitHub
parent
8d3b5c2d19
commit
49e9382cc0
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers: reject malformed successful Runway, BytePlus, and Ollama embedding responses with provider-owned errors instead of raw parser/type failures, silent bad vectors, or long bogus polling.
|
||||
- Trajectory export: skip and report malformed session/runtime JSONL rows in `manifest.json` instead of letting wrong-shaped session rows crash support bundle export.
|
||||
- Config/doctor: copy fallback-enabled channel `allowFrom` entries into explicit `groupAllowFrom` allowlists during `openclaw doctor --fix`, preserving current group access without adding runtime fallback-transition flags.
|
||||
- Configure: show one OpenAI provider entry with ChatGPT/Codex sign-in and API key choices, and keep browsed Codex models in the saved `/model` picker allowlist.
|
||||
- Hooks: raise bounded gateway lifecycle hook wait budgets to 5 seconds for shutdown and 10 seconds for pre-restart, giving short restart notification handlers time to finish before shutdown continues. (#82273) Thanks @bryanbaer.
|
||||
- Plugin releases: require external package compatibility metadata in the npm plugin publish plan, matching the ClawHub package contract before packages ship.
|
||||
- Agents/OpenAI-compatible: honor per-model `max_completion_tokens`/`max_tokens` params in embedded OpenAI-completions runs so high-token Kimi-style routes keep their configured completion cap. Fixes #82230. Thanks @albert-zen.
|
||||
|
||||
@@ -7,11 +7,13 @@ export const OPENAI_CHATGPT_DEVICE_PAIRING_HINT =
|
||||
export const OPENAI_CODEX_API_KEY_BACKUP_LABEL = "OpenAI API Key Backup";
|
||||
export const OPENAI_CODEX_API_KEY_BACKUP_HINT =
|
||||
"Use an OpenAI API key when your Codex subscription is unavailable";
|
||||
export const OPENAI_CODEX_LOGIN_LABEL = "OpenAI Codex Browser Login";
|
||||
export const OPENAI_CODEX_LOGIN_LABEL = "ChatGPT/Codex Browser Login";
|
||||
export const OPENAI_CODEX_LOGIN_HINT = "Sign in with OpenAI in your browser";
|
||||
export const OPENAI_CODEX_DEVICE_PAIRING_LABEL = "OpenAI Codex Device Pairing";
|
||||
export const OPENAI_CODEX_DEVICE_PAIRING_LABEL = "ChatGPT/Codex Device Pairing";
|
||||
export const OPENAI_CODEX_DEVICE_PAIRING_HINT = "Pair in browser with a device code";
|
||||
|
||||
const OPENAI_UNIFIED_GROUP_HINT = "ChatGPT/Codex sign-in or API key";
|
||||
|
||||
export const OPENAI_API_KEY_WIZARD_GROUP = {
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
@@ -21,11 +23,11 @@ export const OPENAI_API_KEY_WIZARD_GROUP = {
|
||||
export const OPENAI_ACCOUNT_WIZARD_GROUP = {
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: "ChatGPT subscription or API key",
|
||||
groupHint: OPENAI_UNIFIED_GROUP_HINT,
|
||||
} as const;
|
||||
|
||||
export const OPENAI_CODEX_WIZARD_GROUP = {
|
||||
groupId: "openai-codex",
|
||||
groupLabel: "OpenAI Codex",
|
||||
groupHint: "ChatGPT/Codex sign-in",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: OPENAI_UNIFIED_GROUP_HINT,
|
||||
} as const;
|
||||
|
||||
@@ -170,23 +170,23 @@ describe("openai codex provider", () => {
|
||||
const apiKey = requireAuthMethod(provider, "api-key");
|
||||
|
||||
expectRecordFields(oauth.wizard, "oauth wizard", {
|
||||
choiceLabel: "OpenAI Codex Browser Login",
|
||||
groupId: "openai-codex",
|
||||
groupLabel: "OpenAI Codex",
|
||||
groupHint: "ChatGPT/Codex sign-in",
|
||||
choiceLabel: "ChatGPT/Codex Browser Login",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: "ChatGPT/Codex sign-in or API key",
|
||||
});
|
||||
expectRecordFields(deviceCode.wizard, "device-code wizard", {
|
||||
choiceLabel: "OpenAI Codex Device Pairing",
|
||||
groupId: "openai-codex",
|
||||
groupLabel: "OpenAI Codex",
|
||||
groupHint: "ChatGPT/Codex sign-in",
|
||||
choiceLabel: "ChatGPT/Codex Device Pairing",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: "ChatGPT/Codex sign-in or API key",
|
||||
});
|
||||
expectRecordFields(apiKey.wizard, "api-key wizard", {
|
||||
choiceLabel: "OpenAI API Key Backup",
|
||||
choiceHint: "Use an OpenAI API key when your Codex subscription is unavailable",
|
||||
groupId: "openai-codex",
|
||||
groupLabel: "OpenAI Codex",
|
||||
groupHint: "ChatGPT/Codex sign-in",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: "ChatGPT/Codex sign-in or API key",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,19 +222,20 @@ describe("openai codex provider", () => {
|
||||
const deviceCode = requireAuthMethod(provider, "device-code");
|
||||
|
||||
expect(provider.auth?.map((method) => method.id)).toEqual(["oauth", "device-code", "api-key"]);
|
||||
expect(oauth.label).toBe("OpenAI Codex Browser Login");
|
||||
expect(oauth.label).toBe("ChatGPT/Codex Browser Login");
|
||||
expect(oauth.hint).toBe("Sign in with OpenAI in your browser");
|
||||
expectRecordFields(oauth.wizard, "oauth wizard", {
|
||||
choiceId: "openai-codex",
|
||||
choiceLabel: "OpenAI Codex Browser Login",
|
||||
choiceLabel: "ChatGPT/Codex Browser Login",
|
||||
assistantPriority: -30,
|
||||
onboardingFeatured: true,
|
||||
});
|
||||
expect(deviceCode.label).toBe("OpenAI Codex Device Pairing");
|
||||
expect(deviceCode.label).toBe("ChatGPT/Codex Device Pairing");
|
||||
expect(deviceCode.hint).toBe("Pair in browser with a device code");
|
||||
expect(deviceCode.kind).toBe("device_code");
|
||||
expectRecordFields(deviceCode.wizard, "device-code wizard", {
|
||||
choiceId: "openai-codex-device-code",
|
||||
choiceLabel: "OpenAI Codex Device Pairing",
|
||||
choiceLabel: "ChatGPT/Codex Device Pairing",
|
||||
assistantPriority: -10,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -489,6 +489,7 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CODEX_LOGIN_LABEL,
|
||||
choiceHint: OPENAI_CODEX_LOGIN_HINT,
|
||||
assistantPriority: OPENAI_CODEX_LOGIN_ASSISTANT_PRIORITY,
|
||||
onboardingFeatured: true,
|
||||
...OPENAI_CODEX_WIZARD_GROUP,
|
||||
},
|
||||
run: async (ctx) => await runOpenAICodexOAuth(ctx),
|
||||
@@ -525,6 +526,7 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CODEX_API_KEY_BACKUP_LABEL,
|
||||
choiceHint: OPENAI_CODEX_API_KEY_BACKUP_HINT,
|
||||
assistantPriority: 5,
|
||||
assistantVisibility: "manual-only",
|
||||
...OPENAI_CODEX_WIZARD_GROUP,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -132,7 +132,7 @@ describe("buildOpenAIProvider", () => {
|
||||
choiceHint: "Use your OpenAI API key directly",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
groupHint: "ChatGPT subscription or API key",
|
||||
groupHint: "ChatGPT/Codex sign-in or API key",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -767,9 +767,10 @@
|
||||
"choiceLabel": "ChatGPT Login",
|
||||
"choiceHint": "Sign in with your ChatGPT or Codex subscription",
|
||||
"assistantPriority": -40,
|
||||
"assistantVisibility": "manual-only",
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT subscription or API key"
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key"
|
||||
},
|
||||
{
|
||||
"provider": "openai",
|
||||
@@ -778,32 +779,34 @@
|
||||
"choiceLabel": "ChatGPT Device Pairing",
|
||||
"choiceHint": "Pair your ChatGPT account in browser with a device code",
|
||||
"assistantPriority": -10,
|
||||
"assistantVisibility": "manual-only",
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT subscription or API key"
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key"
|
||||
},
|
||||
{
|
||||
"provider": "openai-codex",
|
||||
"method": "oauth",
|
||||
"choiceId": "openai-codex",
|
||||
"deprecatedChoiceIds": ["codex-cli", "openai-codex-import"],
|
||||
"choiceLabel": "OpenAI Codex Browser Login",
|
||||
"choiceLabel": "ChatGPT/Codex Browser Login",
|
||||
"choiceHint": "Sign in with OpenAI in your browser",
|
||||
"assistantPriority": -30,
|
||||
"groupId": "openai-codex",
|
||||
"groupLabel": "OpenAI Codex",
|
||||
"groupHint": "ChatGPT/Codex sign-in"
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key",
|
||||
"onboardingFeatured": true
|
||||
},
|
||||
{
|
||||
"provider": "openai-codex",
|
||||
"method": "device-code",
|
||||
"choiceId": "openai-codex-device-code",
|
||||
"choiceLabel": "OpenAI Codex Device Pairing",
|
||||
"choiceLabel": "ChatGPT/Codex Device Pairing",
|
||||
"choiceHint": "Pair in browser with a device code",
|
||||
"assistantPriority": -10,
|
||||
"groupId": "openai-codex",
|
||||
"groupLabel": "OpenAI Codex",
|
||||
"groupHint": "ChatGPT/Codex sign-in"
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key"
|
||||
},
|
||||
{
|
||||
"provider": "openai-codex",
|
||||
@@ -813,9 +816,9 @@
|
||||
"choiceHint": "Use an OpenAI API key when your Codex subscription is unavailable",
|
||||
"assistantPriority": 5,
|
||||
"assistantVisibility": "manual-only",
|
||||
"groupId": "openai-codex",
|
||||
"groupLabel": "OpenAI Codex",
|
||||
"groupHint": "ChatGPT/Codex sign-in",
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key",
|
||||
"optionKey": "openaiApiKey",
|
||||
"cliFlag": "--openai-api-key",
|
||||
"cliOption": "--openai-api-key <key>",
|
||||
@@ -830,7 +833,7 @@
|
||||
"assistantPriority": 5,
|
||||
"groupId": "openai",
|
||||
"groupLabel": "OpenAI",
|
||||
"groupHint": "ChatGPT subscription or API key",
|
||||
"groupHint": "ChatGPT/Codex sign-in or API key",
|
||||
"onboardingFeatured": true,
|
||||
"optionKey": "openaiApiKey",
|
||||
"cliFlag": "--openai-api-key",
|
||||
|
||||
@@ -22,6 +22,7 @@ const manifest = JSON.parse(
|
||||
choiceHint?: string;
|
||||
choiceId?: string;
|
||||
deprecatedChoiceIds?: string[];
|
||||
assistantVisibility?: string;
|
||||
groupId?: string;
|
||||
groupLabel?: string;
|
||||
groupHint?: string;
|
||||
@@ -38,6 +39,7 @@ function manifestComparableWizardFields(choice: {
|
||||
choiceId?: string;
|
||||
choiceLabel?: string;
|
||||
choiceHint?: string;
|
||||
assistantVisibility?: string;
|
||||
groupId?: string;
|
||||
groupLabel?: string;
|
||||
groupHint?: string;
|
||||
@@ -47,6 +49,7 @@ function manifestComparableWizardFields(choice: {
|
||||
choiceId: choice.choiceId,
|
||||
choiceLabel: choice.choiceLabel,
|
||||
choiceHint: choice.choiceHint,
|
||||
assistantVisibility: choice.assistantVisibility,
|
||||
groupId: choice.groupId,
|
||||
groupLabel: choice.groupLabel,
|
||||
groupHint: choice.groupHint,
|
||||
@@ -125,38 +128,40 @@ describe("OpenAI plugin manifest", () => {
|
||||
|
||||
expect(openAiLogin?.choiceLabel).toBe("ChatGPT Login");
|
||||
expect(openAiLogin?.choiceHint).toBe("Sign in with your ChatGPT or Codex subscription");
|
||||
expect(openAiLogin?.assistantVisibility).toBe("manual-only");
|
||||
expect(openAiLogin?.groupId).toBe("openai");
|
||||
expect(openAiLogin?.groupLabel).toBe("OpenAI");
|
||||
expect(openAiLogin?.groupHint).toBe("ChatGPT subscription or API key");
|
||||
expect(openAiLogin?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(openAiDeviceCode?.choiceLabel).toBe("ChatGPT Device Pairing");
|
||||
expect(openAiDeviceCode?.choiceHint).toBe(
|
||||
"Pair your ChatGPT account in browser with a device code",
|
||||
);
|
||||
expect(openAiDeviceCode?.assistantVisibility).toBe("manual-only");
|
||||
expect(openAiDeviceCode?.groupId).toBe("openai");
|
||||
expect(openAiDeviceCode?.groupLabel).toBe("OpenAI");
|
||||
expect(openAiDeviceCode?.groupHint).toBe("ChatGPT subscription or API key");
|
||||
expect(codexBrowserLogin?.choiceLabel).toBe("OpenAI Codex Browser Login");
|
||||
expect(openAiDeviceCode?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(codexBrowserLogin?.choiceLabel).toBe("ChatGPT/Codex Browser Login");
|
||||
expect(codexBrowserLogin?.choiceHint).toBe("Sign in with OpenAI in your browser");
|
||||
expect(codexBrowserLogin?.groupId).toBe("openai-codex");
|
||||
expect(codexBrowserLogin?.groupLabel).toBe("OpenAI Codex");
|
||||
expect(codexBrowserLogin?.groupHint).toBe("ChatGPT/Codex sign-in");
|
||||
expect(codexDeviceCode?.choiceLabel).toBe("OpenAI Codex Device Pairing");
|
||||
expect(codexBrowserLogin?.groupId).toBe("openai");
|
||||
expect(codexBrowserLogin?.groupLabel).toBe("OpenAI");
|
||||
expect(codexBrowserLogin?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(codexDeviceCode?.choiceLabel).toBe("ChatGPT/Codex Device Pairing");
|
||||
expect(codexDeviceCode?.choiceHint).toBe("Pair in browser with a device code");
|
||||
expect(codexDeviceCode?.groupId).toBe("openai-codex");
|
||||
expect(codexDeviceCode?.groupLabel).toBe("OpenAI Codex");
|
||||
expect(codexDeviceCode?.groupHint).toBe("ChatGPT/Codex sign-in");
|
||||
expect(codexDeviceCode?.groupId).toBe("openai");
|
||||
expect(codexDeviceCode?.groupLabel).toBe("OpenAI");
|
||||
expect(codexDeviceCode?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(apiKey?.choiceLabel).toBe("OpenAI API Key");
|
||||
expect(apiKey?.choiceHint).toBe("Use your OpenAI API key directly");
|
||||
expect(apiKey?.groupId).toBe("openai");
|
||||
expect(apiKey?.groupLabel).toBe("OpenAI");
|
||||
expect(apiKey?.groupHint).toBe("ChatGPT subscription or API key");
|
||||
expect(apiKey?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(codexApiKey?.choiceLabel).toBe("OpenAI API Key Backup");
|
||||
expect(codexApiKey?.choiceHint).toBe(
|
||||
"Use an OpenAI API key when your Codex subscription is unavailable",
|
||||
);
|
||||
expect(codexApiKey?.groupId).toBe("openai-codex");
|
||||
expect(codexApiKey?.groupLabel).toBe("OpenAI Codex");
|
||||
expect(codexApiKey?.groupHint).toBe("ChatGPT/Codex sign-in");
|
||||
expect(codexApiKey?.groupId).toBe("openai");
|
||||
expect(codexApiKey?.groupLabel).toBe("OpenAI");
|
||||
expect(codexApiKey?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
|
||||
expect(choices.map((choice) => choice.choiceLabel)).not.toContain(
|
||||
"OpenAI Codex (ChatGPT OAuth)",
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ export function createOpenAICodexProvider(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CODEX_LOGIN_LABEL,
|
||||
choiceHint: OPENAI_CODEX_LOGIN_HINT,
|
||||
assistantPriority: -30,
|
||||
onboardingFeatured: true,
|
||||
...OPENAI_CODEX_WIZARD_GROUP,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ describe("OpenAI setup auth provider", () => {
|
||||
expect(authMethodIds(provider)).toEqual(["oauth", "device-code", "api-key"]);
|
||||
expect(oauth?.label).toBe("ChatGPT Login");
|
||||
expect(oauth?.wizard?.choiceId).toBe("openai");
|
||||
expect(oauth?.wizard?.assistantVisibility).toBe("manual-only");
|
||||
expect(apiKey?.label).toBe("OpenAI API Key");
|
||||
expect(apiKey?.wizard?.choiceId).toBe("openai-api-key");
|
||||
});
|
||||
|
||||
@@ -53,6 +53,7 @@ export function buildOpenAISetupProvider(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CHATGPT_LOGIN_LABEL,
|
||||
choiceHint: OPENAI_CHATGPT_LOGIN_HINT,
|
||||
assistantPriority: -40,
|
||||
assistantVisibility: "manual-only",
|
||||
...OPENAI_ACCOUNT_WIZARD_GROUP,
|
||||
},
|
||||
run: async (ctx) => runOpenAICodexProviderAuthMethod("oauth", ctx),
|
||||
@@ -68,6 +69,7 @@ export function buildOpenAISetupProvider(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CHATGPT_DEVICE_PAIRING_LABEL,
|
||||
choiceHint: OPENAI_CHATGPT_DEVICE_PAIRING_HINT,
|
||||
assistantPriority: -10,
|
||||
assistantVisibility: "manual-only",
|
||||
...OPENAI_ACCOUNT_WIZARD_GROUP,
|
||||
},
|
||||
run: async (ctx) => runOpenAICodexProviderAuthMethod("device-code", ctx),
|
||||
@@ -108,6 +110,7 @@ export function buildOpenAICodexSetupProvider(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CODEX_LOGIN_LABEL,
|
||||
choiceHint: OPENAI_CODEX_LOGIN_HINT,
|
||||
assistantPriority: -30,
|
||||
onboardingFeatured: true,
|
||||
...OPENAI_CODEX_WIZARD_GROUP,
|
||||
},
|
||||
run: async (ctx) => runOpenAICodexProviderAuthMethod("oauth", ctx),
|
||||
@@ -138,6 +141,7 @@ export function buildOpenAICodexSetupProvider(): ProviderPlugin {
|
||||
choiceLabel: OPENAI_CODEX_API_KEY_BACKUP_LABEL,
|
||||
choiceHint: OPENAI_CODEX_API_KEY_BACKUP_HINT,
|
||||
assistantPriority: 5,
|
||||
assistantVisibility: "manual-only",
|
||||
...OPENAI_CODEX_WIZARD_GROUP,
|
||||
},
|
||||
run: async (ctx) => runOpenAICodexProviderAuthMethod("api-key", ctx),
|
||||
|
||||
@@ -55,6 +55,7 @@ vi.mock("../flows/provider-flow.js", () => ({
|
||||
...(choice.assistantVisibility
|
||||
? { assistantVisibility: choice.assistantVisibility }
|
||||
: {}),
|
||||
...(choice.onboardingFeatured ? { onboardingFeatured: true } : {}),
|
||||
},
|
||||
})),
|
||||
...resolveProviderWizardOptions()
|
||||
@@ -75,6 +76,7 @@ vi.mock("../flows/provider-flow.js", () => ({
|
||||
...(option.assistantVisibility
|
||||
? { assistantVisibility: option.assistantVisibility }
|
||||
: {}),
|
||||
...(option.onboardingFeatured ? { onboardingFeatured: true } : {}),
|
||||
},
|
||||
})),
|
||||
];
|
||||
@@ -455,25 +457,42 @@ describe("buildAuthChoiceOptions", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("orders OpenAI auth methods as api key, browser login, then device pairing", () => {
|
||||
it("groups OpenAI auth methods under one provider entry", () => {
|
||||
resolveProviderWizardOptions.mockReturnValue([
|
||||
{
|
||||
value: "openai",
|
||||
label: "ChatGPT Login",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
assistantPriority: -40,
|
||||
assistantVisibility: "manual-only",
|
||||
},
|
||||
{
|
||||
value: "openai-device-code",
|
||||
label: "ChatGPT Device Pairing",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
assistantPriority: -10,
|
||||
assistantVisibility: "manual-only",
|
||||
},
|
||||
{
|
||||
value: "openai-api-key",
|
||||
label: "OpenAI API Key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
assistantPriority: -40,
|
||||
assistantPriority: 5,
|
||||
},
|
||||
{
|
||||
value: "openai-codex",
|
||||
label: "OpenAI Codex Browser Login",
|
||||
label: "ChatGPT/Codex Browser Login",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
assistantPriority: -30,
|
||||
onboardingFeatured: true,
|
||||
},
|
||||
{
|
||||
value: "openai-codex-device-code",
|
||||
label: "OpenAI Codex Device Pairing",
|
||||
label: "ChatGPT/Codex Device Pairing",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
assistantPriority: -10,
|
||||
@@ -487,10 +506,11 @@ describe("buildAuthChoiceOptions", () => {
|
||||
const openAIGroup = requireChoiceGroup(groups, "openai");
|
||||
|
||||
expect(openAIGroup.options.map((option) => option.value)).toEqual([
|
||||
"openai-api-key",
|
||||
"openai-codex",
|
||||
"openai-codex-device-code",
|
||||
"openai-api-key",
|
||||
]);
|
||||
expect(openAIGroup.options[0]?.onboardingFeatured).toBe(true);
|
||||
});
|
||||
|
||||
it("groups OpenCode Zen and Go under one OpenCode entry", () => {
|
||||
|
||||
@@ -222,6 +222,16 @@ function promptModelAllowlistOptions(index = 0) {
|
||||
| undefined;
|
||||
}
|
||||
|
||||
function promptDefaultModelOptions(index = 0) {
|
||||
return mocks.promptDefaultModel.mock.calls[index]?.[0] as
|
||||
| {
|
||||
browseCatalogOnDemand?: boolean;
|
||||
loadCatalog?: boolean;
|
||||
preferredProvider?: string;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
|
||||
const noopPrompter = {} as WizardPrompter;
|
||||
|
||||
function createKilocodeProvider() {
|
||||
@@ -631,6 +641,35 @@ describe("promptAuthConfig", () => {
|
||||
expect(call?.loadCatalog).toBe(true);
|
||||
});
|
||||
|
||||
it("lets skip-auth model browsing scope the allowlist to the selected model provider", async () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.promptAuthChoiceGrouped.mockResolvedValue("skip");
|
||||
mocks.promptDefaultModel.mockResolvedValue({ model: "openai-codex/gpt-5.5" });
|
||||
mocks.promptModelAllowlist.mockResolvedValue({
|
||||
models: ["openai-codex/gpt-5.5"],
|
||||
scopeKeys: ["openai-codex/gpt-5.5", "openai-codex/gpt-5.5-pro"],
|
||||
});
|
||||
mocks.resolveProviderPluginChoice.mockReturnValue(null);
|
||||
|
||||
const result = await promptAuthConfig(
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "fleet-router/qwen3.6:latest" },
|
||||
},
|
||||
},
|
||||
},
|
||||
makeRuntime(),
|
||||
noopPrompter,
|
||||
);
|
||||
|
||||
expect(promptDefaultModelOptions()?.loadCatalog).toBe(true);
|
||||
expect(promptDefaultModelOptions()?.browseCatalogOnDemand).toBe(true);
|
||||
expect(promptModelAllowlistOptions()?.preferredProvider).toBe("openai-codex");
|
||||
expect(result.agents?.defaults?.model).toEqual({ primary: "openai-codex/gpt-5.5" });
|
||||
expect(Object.keys(result.agents?.defaults?.models ?? {})).toEqual(["openai-codex/gpt-5.5"]);
|
||||
});
|
||||
|
||||
it("returns to auth selection when plugin install onboarding asks for a retry", async () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.promptAuthChoiceGrouped
|
||||
|
||||
@@ -104,6 +104,12 @@ function resolveSingleConfiguredProvider(cfg: OpenClawConfig): string | undefine
|
||||
return configuredProviders.length === 1 ? configuredProviders[0] : undefined;
|
||||
}
|
||||
|
||||
function resolveProviderFromModelRef(model: string | undefined): string | undefined {
|
||||
const trimmed = model?.trim();
|
||||
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
||||
return slashIndex > 0 ? trimmed?.slice(0, slashIndex) : undefined;
|
||||
}
|
||||
|
||||
function resolveConfiguredProviderFromAuthChange(params: {
|
||||
before: OpenClawConfig;
|
||||
after: OpenClawConfig;
|
||||
@@ -210,7 +216,8 @@ export async function promptAuthConfig(
|
||||
allowKeep: true,
|
||||
ignoreAllowlist: true,
|
||||
includeProviderPluginSetups: false,
|
||||
loadCatalog: false,
|
||||
loadCatalog: true,
|
||||
browseCatalogOnDemand: true,
|
||||
preferredProvider,
|
||||
workspaceDir: resolveDefaultAgentWorkspaceDir(),
|
||||
runtime,
|
||||
@@ -220,6 +227,7 @@ export async function promptAuthConfig(
|
||||
}
|
||||
if (modelSelection.model) {
|
||||
next = applyPrimaryModel(next, modelSelection.model);
|
||||
preferredProvider = resolveProviderFromModelRef(modelSelection.model) ?? preferredProvider;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -574,6 +574,39 @@ describe("promptDefaultModel", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the full catalog cold until browsing when no provider is preferred", async () => {
|
||||
const select = vi.fn(async (params) => params.initialValue as never);
|
||||
const prompter = makePrompter({ select });
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "fleet-router/qwen3.6:latest",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await promptDefaultModel({
|
||||
config,
|
||||
prompter,
|
||||
allowKeep: true,
|
||||
includeManual: true,
|
||||
ignoreAllowlist: true,
|
||||
browseCatalogOnDemand: true,
|
||||
loadCatalog: true,
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual({});
|
||||
expect(loadModelCatalog).not.toHaveBeenCalled();
|
||||
const params = pickerParams(select as MockCallSource);
|
||||
expect(params.searchable).toBe(false);
|
||||
expect(params.initialValue).toBe("__keep__");
|
||||
expect(optionValues(pickerOptions(select as MockCallSource))).toEqual([
|
||||
"__keep__",
|
||||
"__manual__",
|
||||
"__browse__",
|
||||
]);
|
||||
});
|
||||
|
||||
it("loads the full model catalog when the user chooses to browse", async () => {
|
||||
loadModelCatalog.mockResolvedValue([
|
||||
{
|
||||
|
||||
@@ -611,9 +611,8 @@ export async function promptDefaultModel(
|
||||
if (
|
||||
loadCatalog &&
|
||||
browseCatalogOnDemand &&
|
||||
preferredProvider &&
|
||||
allowKeep &&
|
||||
normalizeProviderId(resolved.provider) === preferredProvider
|
||||
(!preferredProvider || normalizeProviderId(resolved.provider) === preferredProvider)
|
||||
) {
|
||||
const configuredLabel = await resolveConfiguredDisplayLabel();
|
||||
const options: WizardSelectOption[] = [
|
||||
|
||||
@@ -101,6 +101,7 @@ describe("normalizeRegisteredProvider", () => {
|
||||
kind: "custom",
|
||||
wizard: {
|
||||
choiceId: " demo-primary ",
|
||||
onboardingFeatured: true,
|
||||
modelAllowlist: {
|
||||
allowedKeys: [" demo/model ", "demo/model"],
|
||||
initialSelections: [" demo/model "],
|
||||
@@ -121,6 +122,7 @@ describe("normalizeRegisteredProvider", () => {
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: " demo-choice ",
|
||||
onboardingFeatured: true,
|
||||
methodId: " missing ",
|
||||
},
|
||||
modelPicker: {
|
||||
@@ -142,6 +144,7 @@ describe("normalizeRegisteredProvider", () => {
|
||||
kind: "custom",
|
||||
wizard: {
|
||||
choiceId: "demo-primary",
|
||||
onboardingFeatured: true,
|
||||
modelAllowlist: {
|
||||
allowedKeys: ["demo/model"],
|
||||
initialSelections: ["demo/model"],
|
||||
@@ -155,6 +158,7 @@ describe("normalizeRegisteredProvider", () => {
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: "demo-choice",
|
||||
onboardingFeatured: true,
|
||||
},
|
||||
modelPicker: {
|
||||
label: "Demo models",
|
||||
|
||||
@@ -121,6 +121,7 @@ function buildNormalizedWizardSetup(params: {
|
||||
params.setup.assistantVisibility === "visible"
|
||||
? { assistantVisibility: params.setup.assistantVisibility }
|
||||
: {}),
|
||||
...(params.setup.onboardingFeatured === true ? { onboardingFeatured: true } : {}),
|
||||
...(groupId ? { groupId } : {}),
|
||||
...(groupLabel ? { groupLabel } : {}),
|
||||
...(groupHint ? { groupHint } : {}),
|
||||
|
||||
@@ -27,6 +27,7 @@ export type ProviderWizardOption = {
|
||||
onboardingScopes?: Array<"text-inference" | "image-generation">;
|
||||
assistantPriority?: number;
|
||||
assistantVisibility?: "visible" | "manual-only";
|
||||
onboardingFeatured?: boolean;
|
||||
};
|
||||
|
||||
export type ProviderModelPickerEntry = {
|
||||
@@ -119,6 +120,7 @@ function buildSetupOptionForMethod(params: {
|
||||
...(params.wizard.assistantVisibility
|
||||
? { assistantVisibility: params.wizard.assistantVisibility }
|
||||
: {}),
|
||||
...(params.wizard.onboardingFeatured ? { onboardingFeatured: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1120,6 +1120,7 @@ export type ProviderPluginWizardSetup = {
|
||||
choiceHint?: string;
|
||||
assistantPriority?: number;
|
||||
assistantVisibility?: "visible" | "manual-only";
|
||||
onboardingFeatured?: boolean;
|
||||
groupId?: string;
|
||||
groupLabel?: string;
|
||||
groupHint?: string;
|
||||
|
||||
@@ -1158,7 +1158,7 @@ describe("runSetupWizard", () => {
|
||||
providerId: "openai-codex",
|
||||
methodId: "oauth",
|
||||
choiceId: "openai-codex",
|
||||
choiceLabel: "OpenAI Codex Browser Login",
|
||||
choiceLabel: "ChatGPT/Codex Browser Login",
|
||||
});
|
||||
resolvePluginSetupProvider.mockReturnValue({
|
||||
id: "openai-codex",
|
||||
@@ -1166,7 +1166,7 @@ describe("runSetupWizard", () => {
|
||||
auth: [
|
||||
{
|
||||
id: "oauth",
|
||||
label: "OpenAI Codex Browser Login",
|
||||
label: "ChatGPT/Codex Browser Login",
|
||||
kind: "oauth",
|
||||
wizard: {
|
||||
modelSelection: {
|
||||
|
||||
Reference in New Issue
Block a user