refactor(plugins): derive compat provider ids from manifests

This commit is contained in:
Peter Steinberger 2026-03-16 00:40:58 -07:00
parent 74d0c39b32
commit 8fe08df2eb
No known key found for this signature in database
8 changed files with 88 additions and 57 deletions

View File

@ -36,11 +36,6 @@ import {
type OpenClawConfig,
type ResolvedDiscordAccount,
} from "openclaw/plugin-sdk/discord";
import {
buildAgentSessionKey,
resolveThreadSessionKeys,
type RoutePeer,
} from "openclaw/plugin-sdk/routing";
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
import { normalizeMessageChannel } from "../../../src/utils/message-channel.js";
import {

View File

@ -26,7 +26,6 @@ import {
type ChannelPlugin,
type ResolvedIMessageAccount,
} from "openclaw/plugin-sdk/imessage";
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js";
import { getIMessageRuntime } from "./runtime.js";

View File

@ -4,7 +4,7 @@ import {
createScopedAccountConfigAccessors,
collectAllowlistProviderRestrictSendersWarnings,
} from "openclaw/plugin-sdk/compat";
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/core";
import {
buildBaseAccountStatusSnapshot,
buildBaseChannelStatusSummary,

View File

@ -11,7 +11,7 @@ import {
buildAgentSessionKey,
resolveThreadSessionKeys,
type RoutePeer,
} from "openclaw/plugin-sdk/routing";
} from "openclaw/plugin-sdk/core";
import {
buildComputedAccountStatusSnapshot,
buildChannelConfigSchema,

View File

@ -11,7 +11,7 @@ import {
buildAgentSessionKey,
resolveThreadSessionKeys,
type RoutePeer,
} from "openclaw/plugin-sdk/routing";
} from "openclaw/plugin-sdk/core";
import {
buildChannelConfigSchema,
buildTokenChannelStatusSummary,

View File

@ -5,11 +5,16 @@ import { discoverOpenClawPlugins } from "../src/plugins/discovery.js";
// Match exact monolithic-root specifier in any code path:
// imports/exports, require/dynamic import, and test mocks (vi.mock/jest.mock).
const ROOT_IMPORT_PATTERN = /["']openclaw\/plugin-sdk["']/;
const LEGACY_ROUTING_IMPORT_PATTERN = /["']openclaw\/plugin-sdk\/routing["']/;
function hasMonolithicRootImport(content: string): boolean {
return ROOT_IMPORT_PATTERN.test(content);
}
function hasLegacyRoutingImport(content: string): boolean {
return LEGACY_ROUTING_IMPORT_PATTERN.test(content);
}
function isSourceFile(filePath: string): boolean {
if (filePath.endsWith(".d.ts")) {
return false;
@ -77,7 +82,8 @@ function main() {
filesToCheck.add(sharedFile);
}
const offenders: string[] = [];
const monolithicOffenders: string[] = [];
const legacyRoutingOffenders: string[] = [];
for (const entryFile of filesToCheck) {
let content = "";
try {
@ -86,19 +92,35 @@ function main() {
continue;
}
if (hasMonolithicRootImport(content)) {
offenders.push(entryFile);
monolithicOffenders.push(entryFile);
}
if (hasLegacyRoutingImport(content)) {
legacyRoutingOffenders.push(entryFile);
}
}
if (offenders.length > 0) {
console.error("Bundled plugin source files must not import monolithic openclaw/plugin-sdk.");
for (const file of offenders.toSorted()) {
const relative = path.relative(process.cwd(), file) || file;
console.error(`- ${relative}`);
if (monolithicOffenders.length > 0 || legacyRoutingOffenders.length > 0) {
if (monolithicOffenders.length > 0) {
console.error("Bundled plugin source files must not import monolithic openclaw/plugin-sdk.");
for (const file of monolithicOffenders.toSorted()) {
const relative = path.relative(process.cwd(), file) || file;
console.error(`- ${relative}`);
}
}
if (legacyRoutingOffenders.length > 0) {
console.error(
"Bundled plugin source files must not import legacy openclaw/plugin-sdk/routing.",
);
for (const file of legacyRoutingOffenders.toSorted()) {
const relative = path.relative(process.cwd(), file) || file;
console.error(`- ${relative}`);
}
}
if (monolithicOffenders.length > 0 || legacyRoutingOffenders.length > 0) {
console.error(
"Use openclaw/plugin-sdk/<channel> for channel plugins, /core for shared routing and startup surfaces, or /compat for broader internals.",
);
}
console.error(
"Use openclaw/plugin-sdk/<channel> for channel plugins, /core for startup surfaces, or /compat for broader internals.",
);
process.exit(1);
}

View File

@ -20,7 +20,13 @@ describe("resolvePluginProviders", () => {
});
loadPluginManifestRegistryMock.mockReset();
loadPluginManifestRegistryMock.mockReturnValue({
plugins: [],
plugins: [
{ id: "google", providers: ["google"], origin: "bundled" },
{ id: "kilocode", providers: ["kilocode"], origin: "bundled" },
{ id: "moonshot", providers: ["moonshot"], origin: "bundled" },
{ id: "google-gemini-cli-auth", providers: [], origin: "bundled" },
{ id: "workspace-provider", providers: ["workspace-provider"], origin: "workspace" },
],
diagnostics: [],
});
});
@ -77,7 +83,7 @@ describe("resolvePluginProviders", () => {
config: expect.objectContaining({
plugins: expect.objectContaining({
enabled: true,
allow: expect.arrayContaining(["openai", "moonshot", "zai"]),
allow: expect.arrayContaining(["google", "moonshot"]),
}),
}),
cache: false,
@ -103,6 +109,22 @@ describe("resolvePluginProviders", () => {
expect(allow).not.toContain("google-gemini-cli-auth");
});
it("does not inject non-bundled provider plugin ids into compat allowlists", () => {
resolvePluginProviders({
config: {
plugins: {
allow: ["openrouter"],
},
},
bundledProviderAllowlistCompat: true,
});
const call = loadOpenClawPluginsMock.mock.calls.at(-1)?.[0];
const allow = call?.config?.plugins?.allow;
expect(allow).not.toContain("workspace-provider");
});
it("maps provider ids to owning plugin ids via manifests", () => {
loadPluginManifestRegistryMock.mockReturnValue({
plugins: [

View File

@ -7,39 +7,6 @@ import { loadPluginManifestRegistry } from "./manifest-registry.js";
import type { ProviderPlugin } from "./types.js";
const log = createSubsystemLogger("plugins");
const BUNDLED_PROVIDER_ALLOWLIST_COMPAT_PLUGIN_IDS = [
"anthropic",
"byteplus",
"cloudflare-ai-gateway",
"copilot-proxy",
"github-copilot",
"google",
"huggingface",
"kilocode",
"kimi-coding",
"minimax",
"mistral",
"modelstudio",
"moonshot",
"nvidia",
"ollama",
"openai",
"opencode",
"opencode-go",
"openrouter",
"qianfan",
"qwen-portal-auth",
"sglang",
"synthetic",
"together",
"venice",
"vercel-ai-gateway",
"volcengine",
"xai",
"vllm",
"xiaomi",
"zai",
] as const;
function hasExplicitPluginConfig(config: PluginLoadOptions["config"]): boolean {
const plugins = config?.plugins;
@ -69,10 +36,11 @@ function hasExplicitPluginConfig(config: PluginLoadOptions["config"]): boolean {
function withBundledProviderVitestCompat(params: {
config: PluginLoadOptions["config"];
pluginIds: readonly string[];
env?: PluginLoadOptions["env"];
}): PluginLoadOptions["config"] {
const env = params.env ?? process.env;
if (!env.VITEST || hasExplicitPluginConfig(params.config)) {
if (!env.VITEST || hasExplicitPluginConfig(params.config) || params.pluginIds.length === 0) {
return params.config;
}
@ -81,7 +49,7 @@ function withBundledProviderVitestCompat(params: {
plugins: {
...params.config?.plugins,
enabled: true,
allow: [...BUNDLED_PROVIDER_ALLOWLIST_COMPAT_PLUGIN_IDS],
allow: [...params.pluginIds],
slots: {
...params.config?.plugins?.slots,
memory: "none",
@ -90,6 +58,22 @@ function withBundledProviderVitestCompat(params: {
};
}
function resolveBundledProviderCompatPluginIds(params: {
config?: PluginLoadOptions["config"];
workspaceDir?: string;
env?: PluginLoadOptions["env"];
}): string[] {
const registry = loadPluginManifestRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
});
return registry.plugins
.filter((plugin) => plugin.origin === "bundled" && plugin.providers.length > 0)
.map((plugin) => plugin.id)
.toSorted((left, right) => left.localeCompare(right));
}
export function resolveOwningPluginIdsForProvider(params: {
provider: string;
config?: PluginLoadOptions["config"];
@ -126,15 +110,24 @@ export function resolvePluginProviders(params: {
activate?: boolean;
cache?: boolean;
}): ProviderPlugin[] {
const bundledProviderCompatPluginIds =
params.bundledProviderAllowlistCompat || params.bundledProviderVitestCompat
? resolveBundledProviderCompatPluginIds({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
})
: [];
const maybeAllowlistCompat = params.bundledProviderAllowlistCompat
? withBundledPluginAllowlistCompat({
config: params.config,
pluginIds: BUNDLED_PROVIDER_ALLOWLIST_COMPAT_PLUGIN_IDS,
pluginIds: bundledProviderCompatPluginIds,
})
: params.config;
const config = params.bundledProviderVitestCompat
? withBundledProviderVitestCompat({
config: maybeAllowlistCompat,
pluginIds: bundledProviderCompatPluginIds,
env: params.env,
})
: maybeAllowlistCompat;