fix: preserve external auth hook compatibility

This commit is contained in:
Shakker
2026-04-23 06:35:15 +01:00
committed by Shakker
parent 47ae15c059
commit 3ec5558f53
2 changed files with 73 additions and 2 deletions

View File

@@ -22,12 +22,20 @@ type IsPluginProvidersLoadInFlight =
typeof import("./providers.runtime.js").isPluginProvidersLoadInFlight;
type ResolveCatalogHookProviderPluginIds =
typeof import("./providers.js").resolveCatalogHookProviderPluginIds;
type ResolveExternalAuthProfileCompatFallbackPluginIds =
typeof import("./providers.js").resolveExternalAuthProfileCompatFallbackPluginIds;
type ResolveExternalAuthProfileProviderPluginIds =
typeof import("./providers.js").resolveExternalAuthProfileProviderPluginIds;
const resolvePluginProvidersMock = vi.fn<ResolvePluginProviders>((_) => [] as ProviderPlugin[]);
const isPluginProvidersLoadInFlightMock = vi.fn<IsPluginProvidersLoadInFlight>((_) => false);
const resolveCatalogHookProviderPluginIdsMock = vi.fn<ResolveCatalogHookProviderPluginIds>(
(_) => [] as string[],
);
const resolveExternalAuthProfileCompatFallbackPluginIdsMock =
vi.fn<ResolveExternalAuthProfileCompatFallbackPluginIds>((_) => [] as string[]);
const resolveExternalAuthProfileProviderPluginIdsMock =
vi.fn<ResolveExternalAuthProfileProviderPluginIds>((_) => [] as string[]);
let augmentModelCatalogWithProviderPlugins: typeof import("./provider-runtime.js").augmentModelCatalogWithProviderPlugins;
let buildProviderAuthDoctorHintWithPlugin: typeof import("./provider-runtime.js").buildProviderAuthDoctorHintWithPlugin;
@@ -238,6 +246,10 @@ describe("provider-runtime", () => {
vi.doMock("./providers.js", () => ({
resolveCatalogHookProviderPluginIds: (params: unknown) =>
resolveCatalogHookProviderPluginIdsMock(params as never),
resolveExternalAuthProfileCompatFallbackPluginIds: (params: unknown) =>
resolveExternalAuthProfileCompatFallbackPluginIdsMock(params as never),
resolveExternalAuthProfileProviderPluginIds: (params: unknown) =>
resolveExternalAuthProfileProviderPluginIdsMock(params as never),
}));
vi.doMock("./providers.runtime.js", () => ({
resolvePluginProviders: (params: unknown) => resolvePluginProvidersMock(params as never),
@@ -301,6 +313,10 @@ describe("provider-runtime", () => {
isPluginProvidersLoadInFlightMock.mockReturnValue(false);
resolveCatalogHookProviderPluginIdsMock.mockReset();
resolveCatalogHookProviderPluginIdsMock.mockReturnValue([]);
resolveExternalAuthProfileCompatFallbackPluginIdsMock.mockReset();
resolveExternalAuthProfileCompatFallbackPluginIdsMock.mockReturnValue([]);
resolveExternalAuthProfileProviderPluginIdsMock.mockReset();
resolveExternalAuthProfileProviderPluginIdsMock.mockReturnValue([]);
});
it("matches providers by alias for runtime hook lookup", () => {
@@ -355,6 +371,19 @@ describe("provider-runtime", () => {
);
});
it("skips provider runtime loading when no plugin declares external auth hooks", () => {
expect(
resolveExternalAuthProfilesWithPlugins({
env: process.env,
context: {
env: process.env,
store: { version: 1, profiles: {} },
},
}),
).toEqual([]);
expect(resolvePluginProvidersMock).not.toHaveBeenCalled();
});
it("returns provider-prepared runtime auth for the matched provider", async () => {
const prepareRuntimeAuth = vi.fn(async () => ({
apiKey: "runtime-token",
@@ -679,6 +708,7 @@ describe("provider-runtime", () => {
it("dispatches runtime hooks for the matched provider", async () => {
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
resolveExternalAuthProfileProviderPluginIdsMock.mockReturnValue(["demo"]);
const prepareDynamicModel = vi.fn(async () => undefined);
const createStreamFn = vi.fn(() => vi.fn());
const createEmbeddingProvider = vi.fn(async () => ({

View File

@@ -7,6 +7,7 @@ import {
import type { ProviderSystemPromptContribution } from "../agents/system-prompt-contribution.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import {
__testing as providerHookRuntimeTesting,
@@ -21,7 +22,11 @@ import {
import { resolveBundledProviderPolicySurface } from "./provider-public-artifacts.js";
import type { ProviderRuntimeModel } from "./provider-runtime-model.types.js";
import type { ProviderThinkingProfile } from "./provider-thinking.types.js";
import { resolveCatalogHookProviderPluginIds } from "./providers.js";
import {
resolveCatalogHookProviderPluginIds,
resolveExternalAuthProfileCompatFallbackPluginIds,
resolveExternalAuthProfileProviderPluginIds,
} from "./providers.js";
import { getActivePluginRegistryWorkspaceDirFromState } from "./runtime-state.js";
import { resolveRuntimeTextTransforms } from "./text-transforms.runtime.js";
import type {
@@ -70,6 +75,9 @@ import type {
ProviderWebSocketSessionPolicy,
PluginTextTransforms,
} from "./types.js";
const log = createSubsystemLogger("plugins/provider-runtime");
const warnedExternalAuthFallbackPluginIds = new Set<string>();
export {
clearProviderRuntimeHookCache,
prepareProviderExtraParams,
@@ -755,14 +763,47 @@ export function resolveExternalAuthProfilesWithPlugins(params: {
env?: NodeJS.ProcessEnv;
context: ProviderResolveExternalAuthProfilesContext;
}): ProviderExternalAuthProfile[] {
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
const env = params.env ?? process.env;
const externalAuthPluginIds = resolveExternalAuthProfileProviderPluginIds({
config: params.config,
workspaceDir,
env,
});
const fallbackPluginIds = resolveExternalAuthProfileCompatFallbackPluginIds({
config: params.config,
workspaceDir,
env,
});
const pluginIds = [...new Set([...externalAuthPluginIds, ...fallbackPluginIds])].toSorted(
(left, right) => left.localeCompare(right),
);
if (pluginIds.length === 0) {
return [];
}
const declaredPluginIds = new Set(externalAuthPluginIds);
const matches: ProviderExternalAuthProfile[] = [];
for (const plugin of resolveProviderPluginsForHooks(params)) {
for (const plugin of resolveProviderPluginsForHooks({
...params,
workspaceDir,
env,
onlyPluginIds: pluginIds,
})) {
const profiles =
plugin.resolveExternalAuthProfiles?.(params.context) ??
plugin.resolveExternalOAuthProfiles?.(params.context);
if (!profiles || profiles.length === 0) {
continue;
}
if (!declaredPluginIds.has(plugin.id) && !warnedExternalAuthFallbackPluginIds.has(plugin.id)) {
warnedExternalAuthFallbackPluginIds.add(plugin.id);
// Deprecated compatibility path for plugins that predate the manifest
// contract. Remove this warning with the fallback resolver after the
// externalAuthProviders migration window closes.
log.warn(
`Provider plugin "${plugin.id}" uses external auth hooks without declaring contracts.externalAuthProviders. This compatibility fallback is deprecated and will be removed in a future release.`,
);
}
matches.push(...profiles);
}
return matches;