mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:40:42 +00:00
feat: add external auth provider contracts
This commit is contained in:
@@ -47,6 +47,7 @@ import { resolvePluginCacheInputs } from "./roots.js";
|
||||
|
||||
type PluginManifestContractListKey =
|
||||
| "speechProviders"
|
||||
| "externalAuthProviders"
|
||||
| "mediaUnderstandingProviders"
|
||||
| "realtimeVoiceProviders"
|
||||
| "realtimeTranscriptionProviders"
|
||||
|
||||
@@ -233,6 +233,12 @@ export type PluginManifest = {
|
||||
|
||||
export type PluginManifestContracts = {
|
||||
embeddedExtensionFactories?: string[];
|
||||
/**
|
||||
* Provider ids whose external auth profile hook can contribute runtime-only
|
||||
* credentials. Declaring this lets auth-store overlays load only the owning
|
||||
* plugin instead of every provider plugin.
|
||||
*/
|
||||
externalAuthProviders?: string[];
|
||||
memoryEmbeddingProviders?: string[];
|
||||
speechProviders?: string[];
|
||||
realtimeTranscriptionProviders?: string[];
|
||||
@@ -420,6 +426,7 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
||||
}
|
||||
|
||||
const embeddedExtensionFactories = normalizeTrimmedStringList(value.embeddedExtensionFactories);
|
||||
const externalAuthProviders = normalizeTrimmedStringList(value.externalAuthProviders);
|
||||
const memoryEmbeddingProviders = normalizeTrimmedStringList(value.memoryEmbeddingProviders);
|
||||
const speechProviders = normalizeTrimmedStringList(value.speechProviders);
|
||||
const realtimeTranscriptionProviders = normalizeTrimmedStringList(
|
||||
@@ -435,6 +442,7 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
||||
const tools = normalizeTrimmedStringList(value.tools);
|
||||
const contracts = {
|
||||
...(embeddedExtensionFactories.length > 0 ? { embeddedExtensionFactories } : {}),
|
||||
...(externalAuthProviders.length > 0 ? { externalAuthProviders } : {}),
|
||||
...(memoryEmbeddingProviders.length > 0 ? { memoryEmbeddingProviders } : {}),
|
||||
...(speechProviders.length > 0 ? { speechProviders } : {}),
|
||||
...(realtimeTranscriptionProviders.length > 0 ? { realtimeTranscriptionProviders } : {}),
|
||||
|
||||
@@ -10,6 +10,8 @@ type LoadOpenClawPlugins = typeof import("./loader.js").loadOpenClawPlugins;
|
||||
type IsPluginRegistryLoadInFlight = typeof import("./loader.js").isPluginRegistryLoadInFlight;
|
||||
type LoadPluginManifestRegistry =
|
||||
typeof import("./manifest-registry.js").loadPluginManifestRegistry;
|
||||
type ResolveManifestContractPluginIds =
|
||||
typeof import("./manifest-registry.js").resolveManifestContractPluginIds;
|
||||
type ApplyPluginAutoEnable = typeof import("../config/plugin-auto-enable.js").applyPluginAutoEnable;
|
||||
type SetActivePluginRegistry = typeof import("./runtime.js").setActivePluginRegistry;
|
||||
|
||||
@@ -17,12 +19,30 @@ const resolveRuntimePluginRegistryMock = vi.fn<ResolveRuntimePluginRegistry>();
|
||||
const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
|
||||
const isPluginRegistryLoadInFlightMock = vi.fn<IsPluginRegistryLoadInFlight>((_) => false);
|
||||
const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
|
||||
const resolveManifestContractPluginIdsMock = vi.fn<ResolveManifestContractPluginIds>((params) => {
|
||||
const onlyPluginIds =
|
||||
params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null;
|
||||
return loadPluginManifestRegistryMock({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
.plugins.filter(
|
||||
(plugin) =>
|
||||
(!params.origin || plugin.origin === params.origin) &&
|
||||
(!onlyPluginIds || onlyPluginIds.has(plugin.id)) &&
|
||||
(plugin.contracts?.[params.contract] ?? []).length > 0,
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
});
|
||||
const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
|
||||
|
||||
let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
||||
let resolveOwningPluginIdsForModelRef: typeof import("./providers.js").resolveOwningPluginIdsForModelRef;
|
||||
let resolveActivatableProviderOwnerPluginIds: typeof import("./providers.js").resolveActivatableProviderOwnerPluginIds;
|
||||
let resolveEnabledProviderPluginIds: typeof import("./providers.js").resolveEnabledProviderPluginIds;
|
||||
let resolveExternalAuthProfileProviderPluginIds: typeof import("./providers.js").resolveExternalAuthProfileProviderPluginIds;
|
||||
let resolveDiscoveredProviderPluginIds: typeof import("./providers.js").resolveDiscoveredProviderPluginIds;
|
||||
let resolveDiscoverableProviderOwnerPluginIds: typeof import("./providers.js").resolveDiscoverableProviderOwnerPluginIds;
|
||||
let resolvePluginProviders: typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
@@ -37,6 +57,7 @@ function createManifestProviderPlugin(params: {
|
||||
modelSupport?: { modelPrefixes?: string[]; modelPatterns?: string[] };
|
||||
activation?: PluginManifestRecord["activation"];
|
||||
setup?: PluginManifestRecord["setup"];
|
||||
contracts?: PluginManifestRecord["contracts"];
|
||||
}): PluginManifestRecord {
|
||||
return {
|
||||
id: params.id,
|
||||
@@ -47,6 +68,7 @@ function createManifestProviderPlugin(params: {
|
||||
modelSupport: params.modelSupport,
|
||||
activation: params.activation,
|
||||
setup: params.setup,
|
||||
contracts: params.contracts,
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: params.origin ?? "bundled",
|
||||
@@ -285,12 +307,15 @@ describe("resolvePluginProviders", () => {
|
||||
vi.doMock("./manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: (...args: Parameters<LoadPluginManifestRegistry>) =>
|
||||
loadPluginManifestRegistryMock(...args),
|
||||
resolveManifestContractPluginIds: (...args: Parameters<ResolveManifestContractPluginIds>) =>
|
||||
resolveManifestContractPluginIdsMock(...args),
|
||||
}));
|
||||
({
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveOwningPluginIdsForProvider,
|
||||
resolveOwningPluginIdsForModelRef,
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveExternalAuthProfileProviderPluginIds,
|
||||
resolveDiscoveredProviderPluginIds,
|
||||
resolveDiscoverableProviderOwnerPluginIds,
|
||||
} = await import("./providers.js"));
|
||||
@@ -321,6 +346,7 @@ describe("resolvePluginProviders", () => {
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
|
||||
loadOpenClawPluginsMock.mockReturnValue(registry);
|
||||
loadPluginManifestRegistryMock.mockReset();
|
||||
resolveManifestContractPluginIdsMock.mockClear();
|
||||
applyPluginAutoEnableMock.mockReset();
|
||||
applyPluginAutoEnableMock.mockImplementation(
|
||||
(params): PluginAutoEnableResult => ({
|
||||
@@ -387,6 +413,28 @@ describe("resolvePluginProviders", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves external auth hook plugin ids from manifest contracts without runtime loading", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "external-auth-owner",
|
||||
providerIds: ["demo"],
|
||||
contracts: { externalAuthProviders: ["demo"] },
|
||||
}),
|
||||
createManifestProviderPlugin({
|
||||
id: "regular-provider",
|
||||
providerIds: ["regular"],
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveExternalAuthProfileProviderPluginIds({
|
||||
config: {},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).toEqual(["external-auth-owner"]);
|
||||
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats explicit empty provider scopes as scoped-empty in provider helpers", () => {
|
||||
expect(
|
||||
resolveEnabledProviderPluginIds({
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "./manifest-owner-policy.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
resolveManifestContractPluginIds,
|
||||
type PluginManifestRecord,
|
||||
type PluginManifestRegistry,
|
||||
} from "./manifest-registry.js";
|
||||
@@ -118,6 +119,44 @@ export function resolveEnabledProviderPluginIds(params: {
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveExternalAuthProfileProviderPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
return resolveManifestContractPluginIds({
|
||||
contract: "externalAuthProviders",
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveExternalAuthProfileCompatFallbackPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
// Compatibility fallback for provider plugins that implemented the public
|
||||
// external auth hook before contracts.externalAuthProviders existed. Remove
|
||||
// this with the warning path in provider-runtime after the deprecation window.
|
||||
const declaredPluginIds = new Set(resolveExternalAuthProfileProviderPluginIds(params));
|
||||
const registry = loadProviderManifestRegistry(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.origin !== "bundled" &&
|
||||
plugin.providers.length > 0 &&
|
||||
!declaredPluginIds.has(plugin.id) &&
|
||||
isProviderPluginEligibleForRuntimeOwnerActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveDiscoveredProviderPluginIds(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
@@ -229,6 +268,8 @@ export function resolveActivatableProviderOwnerPluginIds(params: {
|
||||
export const __testing = {
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveExternalAuthProfileCompatFallbackPluginIds,
|
||||
resolveExternalAuthProfileProviderPluginIds,
|
||||
resolveDiscoveredProviderPluginIds,
|
||||
resolveDiscoverableProviderOwnerPluginIds,
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
|
||||
Reference in New Issue
Block a user