mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 16:12:13 +00:00
feat(plugins): support provider auth aliases
This commit is contained in:
@@ -59,6 +59,7 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
modelSupport: undefined,
|
||||
cliBackends: [],
|
||||
channelEnvVars: undefined,
|
||||
providerAuthAliases: undefined,
|
||||
providerAuthChoices: undefined,
|
||||
skills: [],
|
||||
settingsFiles: undefined,
|
||||
|
||||
@@ -382,6 +382,9 @@ describe("loadPluginManifestRegistry", () => {
|
||||
providerAuthEnvVars: {
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
},
|
||||
providerAuthAliases: {
|
||||
"openai-codex": "openai",
|
||||
},
|
||||
providerAuthChoices: [
|
||||
{
|
||||
provider: "openai",
|
||||
@@ -404,6 +407,9 @@ describe("loadPluginManifestRegistry", () => {
|
||||
expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({
|
||||
openai: ["OPENAI_API_KEY"],
|
||||
});
|
||||
expect(registry.plugins[0]?.providerAuthAliases).toEqual({
|
||||
"openai-codex": "openai",
|
||||
});
|
||||
expect(registry.plugins[0]?.enabledByDefault).toBe(true);
|
||||
expect(registry.plugins[0]?.providerAuthChoices).toEqual([
|
||||
{
|
||||
|
||||
@@ -78,6 +78,7 @@ export type PluginManifestRecord = {
|
||||
modelSupport?: PluginManifestModelSupport;
|
||||
cliBackends: string[];
|
||||
providerAuthEnvVars?: Record<string, string[]>;
|
||||
providerAuthAliases?: Record<string, string>;
|
||||
channelEnvVars?: Record<string, string[]>;
|
||||
providerAuthChoices?: PluginManifest["providerAuthChoices"];
|
||||
skills: string[];
|
||||
@@ -311,6 +312,7 @@ function buildRecord(params: {
|
||||
modelSupport: params.manifest.modelSupport,
|
||||
cliBackends: params.manifest.cliBackends ?? [],
|
||||
providerAuthEnvVars: params.manifest.providerAuthEnvVars,
|
||||
providerAuthAliases: params.manifest.providerAuthAliases,
|
||||
channelEnvVars: params.manifest.channelEnvVars,
|
||||
providerAuthChoices: params.manifest.providerAuthChoices,
|
||||
skills: params.manifest.skills ?? [],
|
||||
|
||||
@@ -105,6 +105,8 @@ export type PluginManifest = {
|
||||
cliBackends?: string[];
|
||||
/** Cheap provider-auth env lookup without booting plugin runtime. */
|
||||
providerAuthEnvVars?: Record<string, string[]>;
|
||||
/** Provider ids that should reuse another provider id for auth lookup. */
|
||||
providerAuthAliases?: Record<string, string>;
|
||||
/** Cheap channel env lookup without booting plugin runtime. */
|
||||
channelEnvVars?: Record<string, string[]>;
|
||||
/**
|
||||
@@ -198,6 +200,22 @@ function normalizeStringListRecord(value: unknown): Record<string, string[]> | u
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringRecord(value: unknown): Record<string, string> | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized: Record<string, string> = {};
|
||||
for (const [rawKey, rawValue] of Object.entries(value)) {
|
||||
const key = normalizeOptionalString(rawKey) ?? "";
|
||||
const value = normalizeOptionalString(rawValue) ?? "";
|
||||
if (!key || !value) {
|
||||
continue;
|
||||
}
|
||||
normalized[key] = value;
|
||||
}
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeManifestContracts(value: unknown): PluginManifestContracts | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
@@ -516,6 +534,7 @@ export function loadPluginManifest(
|
||||
const modelSupport = normalizeManifestModelSupport(raw.modelSupport);
|
||||
const cliBackends = normalizeTrimmedStringList(raw.cliBackends);
|
||||
const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars);
|
||||
const providerAuthAliases = normalizeStringRecord(raw.providerAuthAliases);
|
||||
const channelEnvVars = normalizeStringListRecord(raw.channelEnvVars);
|
||||
const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices);
|
||||
const skills = normalizeTrimmedStringList(raw.skills);
|
||||
@@ -544,6 +563,7 @@ export function loadPluginManifest(
|
||||
modelSupport,
|
||||
cliBackends,
|
||||
providerAuthEnvVars,
|
||||
providerAuthAliases,
|
||||
channelEnvVars,
|
||||
providerAuthChoices,
|
||||
skills,
|
||||
|
||||
@@ -8,6 +8,7 @@ vi.mock("./manifest-registry.js", () => ({
|
||||
|
||||
import {
|
||||
resolveManifestDeprecatedProviderAuthChoice,
|
||||
resolveManifestProviderApiKeyChoice,
|
||||
resolveManifestProviderAuthChoice,
|
||||
resolveManifestProviderAuthChoices,
|
||||
resolveManifestProviderOnboardAuthFlags,
|
||||
@@ -338,4 +339,33 @@ describe("provider auth choice manifest helpers", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves api-key choices through manifest-owned provider auth aliases", () => {
|
||||
setManifestPlugins([
|
||||
{
|
||||
id: "fixture-provider",
|
||||
origin: "bundled",
|
||||
providerAuthAliases: {
|
||||
"fixture-provider-plan": "fixture-provider",
|
||||
},
|
||||
providerAuthChoices: [
|
||||
{
|
||||
provider: "fixture-provider",
|
||||
method: "api-key",
|
||||
choiceId: "fixture-provider-api-key",
|
||||
choiceLabel: "Fixture Provider API key",
|
||||
optionKey: "fixtureProviderApiKey",
|
||||
cliFlag: "--fixture-provider-api-key",
|
||||
cliOption: "--fixture-provider-api-key <key>",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveManifestProviderApiKeyChoice({
|
||||
providerId: "fixture-provider-plan",
|
||||
})?.choiceId,
|
||||
).toBe("fixture-provider-api-key");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { normalizeProviderIdForAuth } from "../agents/model-selection.js";
|
||||
import { resolveProviderIdForAuth } from "../agents/provider-auth-aliases.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
|
||||
import { loadPluginManifestRegistry, type PluginManifestRecord } from "./manifest-registry.js";
|
||||
@@ -203,7 +203,7 @@ export function resolveManifestProviderApiKeyChoice(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
}): ProviderAuthChoiceMetadata | undefined {
|
||||
const normalizedProviderId = normalizeProviderIdForAuth(params.providerId);
|
||||
const normalizedProviderId = resolveProviderIdForAuth(params.providerId, params);
|
||||
if (!normalizedProviderId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export function resolveManifestProviderApiKeyChoice(params: {
|
||||
if (!choice.optionKey) {
|
||||
return false;
|
||||
}
|
||||
return normalizeProviderIdForAuth(choice.providerId) === normalizedProviderId;
|
||||
return resolveProviderIdForAuth(choice.providerId, params) === normalizedProviderId;
|
||||
});
|
||||
const preferred = pickPreferredManifestAuthChoice(candidates);
|
||||
return preferred ? stripChoiceOrigin(preferred) : undefined;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
||||
import { resolveOpenClawAgentDir } from "../agents/agent-paths.js";
|
||||
import { buildAuthProfileId } from "../agents/auth-profiles/identity.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles/profiles.js";
|
||||
import { normalizeProviderIdForAuth } from "../agents/provider-id.js";
|
||||
import { resolveProviderIdForAuth } from "../agents/provider-auth-aliases.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import {
|
||||
@@ -136,7 +136,7 @@ export function applyAuthProfileConfig(
|
||||
preferProfileFirst?: boolean;
|
||||
},
|
||||
): OpenClawConfig {
|
||||
const normalizedProvider = normalizeProviderIdForAuth(params.provider);
|
||||
const normalizedProvider = resolveProviderIdForAuth(params.provider, { config: cfg });
|
||||
const profiles = {
|
||||
...cfg.auth?.profiles,
|
||||
[params.profileId]: {
|
||||
@@ -148,13 +148,16 @@ export function applyAuthProfileConfig(
|
||||
};
|
||||
|
||||
const configuredProviderProfiles = Object.entries(cfg.auth?.profiles ?? {})
|
||||
.filter(([, profile]) => normalizeProviderIdForAuth(profile.provider) === normalizedProvider)
|
||||
.filter(
|
||||
([, profile]) =>
|
||||
resolveProviderIdForAuth(profile.provider, { config: cfg }) === normalizedProvider,
|
||||
)
|
||||
.map(([profileId, profile]) => ({ profileId, mode: profile.mode }));
|
||||
|
||||
// Maintain `auth.order` when it already exists. Additionally, if we detect
|
||||
// mixed auth modes for the same provider, keep the newly selected profile first.
|
||||
const matchingProviderOrderEntries = Object.entries(cfg.auth?.order ?? {}).filter(
|
||||
([providerId]) => normalizeProviderIdForAuth(providerId) === normalizedProvider,
|
||||
([providerId]) => resolveProviderIdForAuth(providerId, { config: cfg }) === normalizedProvider,
|
||||
);
|
||||
const existingProviderOrder =
|
||||
matchingProviderOrderEntries.length > 0
|
||||
@@ -184,7 +187,8 @@ export function applyAuthProfileConfig(
|
||||
matchingProviderOrderEntries.length > 0
|
||||
? Object.fromEntries(
|
||||
Object.entries(cfg.auth?.order ?? {}).filter(
|
||||
([providerId]) => normalizeProviderIdForAuth(providerId) !== normalizedProvider,
|
||||
([providerId]) =>
|
||||
resolveProviderIdForAuth(providerId, { config: cfg }) !== normalizedProvider,
|
||||
),
|
||||
)
|
||||
: cfg.auth?.order;
|
||||
|
||||
Reference in New Issue
Block a user