mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-21 23:11:01 +00:00
fix(onboarding): hide image-only auth providers
This commit is contained in:
@@ -35,6 +35,7 @@ export default definePluginEntry({
|
||||
groupId: "fal",
|
||||
groupLabel: "fal",
|
||||
groupHint: "Image generation",
|
||||
onboardingScopes: ["image-generation"],
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"groupId": "fal",
|
||||
"groupLabel": "fal",
|
||||
"groupHint": "Image generation",
|
||||
"onboardingScopes": ["image-generation"],
|
||||
"optionKey": "falApiKey",
|
||||
"cliFlag": "--fal-api-key",
|
||||
"cliOption": "--fal-api-key <key>",
|
||||
|
||||
@@ -354,4 +354,50 @@ describe("buildAuthChoiceOptions", () => {
|
||||
expect(ollamaGroup).toBeDefined();
|
||||
expect(ollamaGroup?.options.some((opt) => opt.value === "ollama")).toBe(true);
|
||||
});
|
||||
|
||||
it("hides image-generation-only providers from the interactive auth picker", () => {
|
||||
resolveManifestProviderAuthChoices.mockReturnValue([
|
||||
{
|
||||
pluginId: "fal",
|
||||
providerId: "fal",
|
||||
methodId: "api-key",
|
||||
choiceId: "fal-api-key",
|
||||
choiceLabel: "fal API key",
|
||||
groupId: "fal",
|
||||
groupLabel: "fal",
|
||||
onboardingScopes: ["image-generation"],
|
||||
},
|
||||
{
|
||||
pluginId: "openai",
|
||||
providerId: "openai",
|
||||
methodId: "api-key",
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
},
|
||||
]);
|
||||
resolveProviderWizardOptions.mockReturnValue([
|
||||
{
|
||||
value: "local-image-runtime",
|
||||
label: "Local image runtime",
|
||||
groupId: "local-image-runtime",
|
||||
groupLabel: "Local image runtime",
|
||||
onboardingScopes: ["image-generation"],
|
||||
},
|
||||
{
|
||||
value: "ollama",
|
||||
label: "Ollama",
|
||||
groupId: "ollama",
|
||||
groupLabel: "Ollama",
|
||||
},
|
||||
]);
|
||||
|
||||
const options = getOptions();
|
||||
|
||||
expect(options.some((option) => option.value === "openai-api-key")).toBe(true);
|
||||
expect(options.some((option) => option.value === "ollama")).toBe(true);
|
||||
expect(options.some((option) => option.value === "fal-api-key")).toBe(false);
|
||||
expect(options.some((option) => option.value === "local-image-runtime")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,17 @@ import {
|
||||
} from "./auth-choice-options.static.js";
|
||||
import type { AuthChoice, AuthChoiceGroupId } from "./onboard-types.js";
|
||||
|
||||
const DEFAULT_AUTH_CHOICE_ONBOARDING_SCOPE = "text-inference" as const;
|
||||
|
||||
function includesOnboardingScope(
|
||||
onboardingScopes: readonly ("text-inference" | "image-generation")[] | undefined,
|
||||
scope: "text-inference" | "image-generation",
|
||||
): boolean {
|
||||
return onboardingScopes
|
||||
? onboardingScopes.includes(scope)
|
||||
: scope === DEFAULT_AUTH_CHOICE_ONBOARDING_SCOPE;
|
||||
}
|
||||
|
||||
function compareOptionLabels(a: AuthChoiceOption, b: AuthChoiceOption): number {
|
||||
return a.label.localeCompare(b.label);
|
||||
}
|
||||
@@ -23,14 +34,18 @@ function resolveManifestProviderChoiceOptions(params?: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): AuthChoiceOption[] {
|
||||
return resolveManifestProviderAuthChoices(params ?? {}).map((choice) => ({
|
||||
value: choice.choiceId as AuthChoice,
|
||||
label: choice.choiceLabel,
|
||||
...(choice.choiceHint ? { hint: choice.choiceHint } : {}),
|
||||
...(choice.groupId ? { groupId: choice.groupId as AuthChoiceGroupId } : {}),
|
||||
...(choice.groupLabel ? { groupLabel: choice.groupLabel } : {}),
|
||||
...(choice.groupHint ? { groupHint: choice.groupHint } : {}),
|
||||
}));
|
||||
return resolveManifestProviderAuthChoices(params ?? {})
|
||||
.filter((choice) =>
|
||||
includesOnboardingScope(choice.onboardingScopes, DEFAULT_AUTH_CHOICE_ONBOARDING_SCOPE),
|
||||
)
|
||||
.map((choice) => ({
|
||||
value: choice.choiceId as AuthChoice,
|
||||
label: choice.choiceLabel,
|
||||
...(choice.choiceHint ? { hint: choice.choiceHint } : {}),
|
||||
...(choice.groupId ? { groupId: choice.groupId as AuthChoiceGroupId } : {}),
|
||||
...(choice.groupLabel ? { groupLabel: choice.groupLabel } : {}),
|
||||
...(choice.groupHint ? { groupHint: choice.groupHint } : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
function resolveRuntimeFallbackProviderChoiceOptions(params?: {
|
||||
@@ -38,14 +53,18 @@ function resolveRuntimeFallbackProviderChoiceOptions(params?: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): AuthChoiceOption[] {
|
||||
return resolveProviderWizardOptions(params ?? {}).map((option) => ({
|
||||
value: option.value as AuthChoice,
|
||||
label: option.label,
|
||||
...(option.hint ? { hint: option.hint } : {}),
|
||||
groupId: option.groupId as AuthChoiceGroupId,
|
||||
groupLabel: option.groupLabel,
|
||||
...(option.groupHint ? { groupHint: option.groupHint } : {}),
|
||||
}));
|
||||
return resolveProviderWizardOptions(params ?? {})
|
||||
.filter((option) =>
|
||||
includesOnboardingScope(option.onboardingScopes, DEFAULT_AUTH_CHOICE_ONBOARDING_SCOPE),
|
||||
)
|
||||
.map((option) => ({
|
||||
value: option.value as AuthChoice,
|
||||
label: option.label,
|
||||
...(option.hint ? { hint: option.hint } : {}),
|
||||
groupId: option.groupId as AuthChoiceGroupId,
|
||||
groupLabel: option.groupLabel,
|
||||
...(option.groupHint ? { groupHint: option.groupHint } : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
export function formatAuthChoiceChoicesForCli(params?: {
|
||||
|
||||
@@ -48,8 +48,15 @@ export type PluginManifestProviderAuthChoice = {
|
||||
cliFlag?: string;
|
||||
cliOption?: string;
|
||||
cliDescription?: string;
|
||||
/**
|
||||
* Interactive onboarding surfaces where this auth choice should appear.
|
||||
* Defaults to `["text-inference"]` when omitted.
|
||||
*/
|
||||
onboardingScopes?: PluginManifestOnboardingScope[];
|
||||
};
|
||||
|
||||
export type PluginManifestOnboardingScope = "text-inference" | "image-generation";
|
||||
|
||||
export type PluginManifestLoadResult =
|
||||
| { ok: true; manifest: PluginManifest; manifestPath: string }
|
||||
| { ok: false; error: string; manifestPath: string };
|
||||
@@ -107,6 +114,10 @@ function normalizeProviderAuthChoices(
|
||||
const cliOption = typeof entry.cliOption === "string" ? entry.cliOption.trim() : "";
|
||||
const cliDescription =
|
||||
typeof entry.cliDescription === "string" ? entry.cliDescription.trim() : "";
|
||||
const onboardingScopes = normalizeStringList(entry.onboardingScopes).filter(
|
||||
(scope): scope is PluginManifestOnboardingScope =>
|
||||
scope === "text-inference" || scope === "image-generation",
|
||||
);
|
||||
normalized.push({
|
||||
provider,
|
||||
method,
|
||||
@@ -120,6 +131,7 @@ function normalizeProviderAuthChoices(
|
||||
...(cliFlag ? { cliFlag } : {}),
|
||||
...(cliOption ? { cliOption } : {}),
|
||||
...(cliDescription ? { cliDescription } : {}),
|
||||
...(onboardingScopes.length > 0 ? { onboardingScopes } : {}),
|
||||
});
|
||||
}
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
|
||||
@@ -24,6 +24,7 @@ describe("provider auth choice manifest helpers", () => {
|
||||
method: "api-key",
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
onboardingScopes: ["text-inference"],
|
||||
optionKey: "openaiApiKey",
|
||||
cliFlag: "--openai-api-key",
|
||||
cliOption: "--openai-api-key <key>",
|
||||
@@ -40,6 +41,7 @@ describe("provider auth choice manifest helpers", () => {
|
||||
methodId: "api-key",
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
onboardingScopes: ["text-inference"],
|
||||
optionKey: "openaiApiKey",
|
||||
cliFlag: "--openai-api-key",
|
||||
cliOption: "--openai-api-key <key>",
|
||||
|
||||
@@ -16,6 +16,7 @@ export type ProviderAuthChoiceMetadata = {
|
||||
cliFlag?: string;
|
||||
cliOption?: string;
|
||||
cliDescription?: string;
|
||||
onboardingScopes?: ("text-inference" | "image-generation")[];
|
||||
};
|
||||
|
||||
export type ProviderOnboardAuthFlag = {
|
||||
@@ -52,6 +53,7 @@ export function resolveManifestProviderAuthChoices(params?: {
|
||||
...(choice.cliFlag ? { cliFlag: choice.cliFlag } : {}),
|
||||
...(choice.cliOption ? { cliOption: choice.cliOption } : {}),
|
||||
...(choice.cliDescription ? { cliDescription: choice.cliDescription } : {}),
|
||||
...(choice.onboardingScopes ? { onboardingScopes: choice.onboardingScopes } : {}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,20 @@ function normalizeTextList(values: string[] | undefined): string[] | undefined {
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeOnboardingScopes(
|
||||
values: Array<"text-inference" | "image-generation"> | undefined,
|
||||
): Array<"text-inference" | "image-generation"> | undefined {
|
||||
const normalized = Array.from(
|
||||
new Set(
|
||||
(values ?? []).filter(
|
||||
(value): value is "text-inference" | "image-generation" =>
|
||||
value === "text-inference" || value === "image-generation",
|
||||
),
|
||||
),
|
||||
);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeProviderWizardSetup(params: {
|
||||
providerId: string;
|
||||
pluginId: string;
|
||||
@@ -79,6 +93,9 @@ function normalizeProviderWizardSetup(params: {
|
||||
? { groupHint: normalizeText(params.setup.groupHint) }
|
||||
: {}),
|
||||
...(methodId && params.auth.some((method) => method.id === methodId) ? { methodId } : {}),
|
||||
...(normalizeOnboardingScopes(params.setup.onboardingScopes)
|
||||
? { onboardingScopes: normalizeOnboardingScopes(params.setup.onboardingScopes) }
|
||||
: {}),
|
||||
...(params.setup.modelAllowlist
|
||||
? {
|
||||
modelAllowlist: {
|
||||
|
||||
@@ -79,6 +79,7 @@ describe("provider wizard boundaries", () => {
|
||||
choiceLabel: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
onboardingScopes: ["text-inference"],
|
||||
},
|
||||
run: vi.fn(),
|
||||
},
|
||||
@@ -92,6 +93,7 @@ describe("provider wizard boundaries", () => {
|
||||
label: "OpenAI API key",
|
||||
groupId: "openai",
|
||||
groupLabel: "OpenAI",
|
||||
onboardingScopes: ["text-inference"],
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
@@ -106,6 +108,39 @@ describe("provider wizard boundaries", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves onboarding scopes on wizard options", () => {
|
||||
const provider = makeProvider({
|
||||
id: "fal",
|
||||
label: "fal",
|
||||
auth: [
|
||||
{
|
||||
id: "api-key",
|
||||
label: "fal API key",
|
||||
kind: "api_key",
|
||||
wizard: {
|
||||
choiceId: "fal-api-key",
|
||||
choiceLabel: "fal API key",
|
||||
groupId: "fal",
|
||||
groupLabel: "fal",
|
||||
onboardingScopes: ["image-generation"],
|
||||
},
|
||||
run: vi.fn(),
|
||||
},
|
||||
],
|
||||
});
|
||||
resolvePluginProviders.mockReturnValue([provider]);
|
||||
|
||||
expect(resolveProviderWizardOptions({})).toEqual([
|
||||
{
|
||||
value: "fal-api-key",
|
||||
label: "fal API key",
|
||||
groupId: "fal",
|
||||
groupLabel: "fal",
|
||||
onboardingScopes: ["image-generation"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns method wizard metadata for canonical choices", () => {
|
||||
const provider = makeProvider({
|
||||
id: "anthropic",
|
||||
|
||||
@@ -20,6 +20,7 @@ export type ProviderWizardOption = {
|
||||
groupId: string;
|
||||
groupLabel: string;
|
||||
groupHint?: string;
|
||||
onboardingScopes?: Array<"text-inference" | "image-generation">;
|
||||
};
|
||||
|
||||
export type ProviderModelPickerEntry = {
|
||||
@@ -88,6 +89,7 @@ function buildSetupOptionForMethod(params: {
|
||||
groupId: normalizedGroupId,
|
||||
groupLabel: params.wizard.groupLabel?.trim() || params.provider.label,
|
||||
groupHint: params.wizard.groupHint?.trim(),
|
||||
...(params.wizard.onboardingScopes ? { onboardingScopes: params.wizard.onboardingScopes } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -593,6 +593,11 @@ export type ProviderPluginWizardSetup = {
|
||||
groupLabel?: string;
|
||||
groupHint?: string;
|
||||
methodId?: string;
|
||||
/**
|
||||
* Interactive onboarding surfaces where this auth choice should appear.
|
||||
* Defaults to `["text-inference"]` when omitted.
|
||||
*/
|
||||
onboardingScopes?: Array<"text-inference" | "image-generation">;
|
||||
/**
|
||||
* Optional model-allowlist prompt policy applied after this auth choice is
|
||||
* selected in configure/onboarding flows.
|
||||
|
||||
Reference in New Issue
Block a user