fix(configure): unify OpenAI auth provider picker (#82324)

This commit is contained in:
Peter Steinberger
2026-05-16 01:02:33 +01:00
committed by GitHub
parent 8d3b5c2d19
commit 49e9382cc0
20 changed files with 187 additions and 60 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,
});
});

View File

@@ -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,
},
}),

View File

@@ -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",
});
});

View File

@@ -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",

View File

@@ -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)",
);

View File

@@ -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,
},
},

View File

@@ -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");
});

View File

@@ -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),

View File

@@ -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", () => {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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([
{

View File

@@ -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[] = [

View File

@@ -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",

View File

@@ -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 } : {}),

View File

@@ -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 } : {}),
};
}

View File

@@ -1120,6 +1120,7 @@ export type ProviderPluginWizardSetup = {
choiceHint?: string;
assistantPriority?: number;
assistantVisibility?: "visible" | "manual-only";
onboardingFeatured?: boolean;
groupId?: string;
groupLabel?: string;
groupHint?: string;

View File

@@ -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: {