mirror of https://github.com/openclaw/openclaw.git
Plugins: extract plugin api facade
This commit is contained in:
parent
8a2f0be664
commit
a1e1dcc01a
|
|
@ -64,6 +64,7 @@ This is an implementation checklist, not a future-design spec.
|
|||
| Config validation indexing | `src/config/validation.ts`, `src/config/resolved-extension-validation.ts` | host-owned resolved registry | `moved` | Validation indexing now builds from resolved-extension records instead of flat manifest rows. |
|
||||
| Config doc baseline generation | `src/config/doc-baseline.ts` | host-owned resolved registry | `moved` | Bundled plugin and channel metadata now load through the resolved-extension registry. |
|
||||
| Plugin loader activation | `src/plugins/loader.ts` | extension host lifecycle + compatibility loader | `partial` | Activation now routes through `src/extension-host/activation.ts`, but discovery, enablement, provenance, module loading, and policy still live in the legacy plugin loader. |
|
||||
| Plugin API compatibility facade | `src/plugins/registry.ts` | `src/extension-host/plugin-api.ts` | `partial` | Compatibility `OpenClawPluginApi` composition and logger shaping now delegate through a host-owned helper; the legacy registry still supplies the concrete registration callbacks. |
|
||||
| Channel registration writes | `src/plugins/registry.ts` | host-owned channel registry | `partial` | Validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, and compatibility writes now route through `src/extension-host/registry-writes.ts`; the legacy plugin API still remains the call surface. |
|
||||
| Provider registration writes | `src/plugins/registry.ts` | host-owned provider registry | `partial` | Provider normalization still happens in plugin-era validation, duplicate detection and normalized registration shape now delegate to `src/extension-host/runtime-registrations.ts`, and compatibility writes now route through `src/extension-host/registry-writes.ts`. |
|
||||
| HTTP route registration writes | `src/plugins/registry.ts` | host-owned route registry | `partial` | Route validation and normalization now delegate to `src/extension-host/runtime-registrations.ts`, and compatibility append or replace writes now route through `src/extension-host/registry-writes.ts`. |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { PluginRecord } from "../plugins/registry.js";
|
||||
import { createExtensionHostPluginApi, normalizeExtensionHostPluginLogger } from "./plugin-api.js";
|
||||
|
||||
function createRecord(): PluginRecord {
|
||||
return {
|
||||
id: "demo",
|
||||
name: "Demo",
|
||||
source: "/plugins/demo.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
};
|
||||
}
|
||||
|
||||
describe("extension host plugin api", () => {
|
||||
it("normalizes plugin logger methods", () => {
|
||||
const logger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
};
|
||||
|
||||
const normalized = normalizeExtensionHostPluginLogger(logger);
|
||||
normalized.info("x");
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith("x");
|
||||
expect(normalized.debug).toBe(logger.debug);
|
||||
});
|
||||
|
||||
it("creates a compatibility plugin api that delegates all registration calls", () => {
|
||||
const callbacks = {
|
||||
registerTool: vi.fn(),
|
||||
registerHook: vi.fn(),
|
||||
registerHttpRoute: vi.fn(),
|
||||
registerChannel: vi.fn(),
|
||||
registerProvider: vi.fn(),
|
||||
registerGatewayMethod: vi.fn(),
|
||||
registerCli: vi.fn(),
|
||||
registerService: vi.fn(),
|
||||
registerCommand: vi.fn(),
|
||||
registerContextEngine: vi.fn(),
|
||||
on: vi.fn(),
|
||||
};
|
||||
|
||||
const api = createExtensionHostPluginApi({
|
||||
record: createRecord(),
|
||||
runtime: {} as never,
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
config: {},
|
||||
registerTool: callbacks.registerTool as never,
|
||||
registerHook: callbacks.registerHook as never,
|
||||
registerHttpRoute: callbacks.registerHttpRoute as never,
|
||||
registerChannel: callbacks.registerChannel as never,
|
||||
registerProvider: callbacks.registerProvider as never,
|
||||
registerGatewayMethod: callbacks.registerGatewayMethod as never,
|
||||
registerCli: callbacks.registerCli as never,
|
||||
registerService: callbacks.registerService as never,
|
||||
registerCommand: callbacks.registerCommand as never,
|
||||
registerContextEngine: callbacks.registerContextEngine as never,
|
||||
on: callbacks.on as never,
|
||||
});
|
||||
|
||||
api.registerTool({ name: "tool" } as never);
|
||||
api.registerHook("before_send", (() => {}) as never);
|
||||
api.registerHttpRoute({ path: "/x", handler: (() => {}) as never, auth: "gateway" });
|
||||
api.registerChannel({ id: "ch" } as never);
|
||||
api.registerProvider({} as never);
|
||||
api.registerGatewayMethod("ping", (() => {}) as never);
|
||||
api.registerCli((() => {}) as never);
|
||||
api.registerService({ id: "svc", start: async () => {}, stop: async () => {} } as never);
|
||||
api.registerCommand({ name: "cmd", description: "demo", handler: async () => ({}) } as never);
|
||||
api.registerContextEngine("engine", (() => ({}) as never) as never);
|
||||
api.on("before_send" as never, (() => {}) as never);
|
||||
|
||||
expect(callbacks.registerTool).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerHook).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerHttpRoute).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerChannel).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerProvider).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerGatewayMethod).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerCli).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerService).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerCommand).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.registerContextEngine).toHaveBeenCalledTimes(1);
|
||||
expect(callbacks.on).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import type { PluginRecord } from "../plugins/registry.js";
|
||||
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
import type {
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginChannelRegistration,
|
||||
OpenClawPluginCliRegistrar,
|
||||
OpenClawPluginCommandDefinition,
|
||||
OpenClawPluginHttpRouteParams,
|
||||
OpenClawPluginService,
|
||||
OpenClawPluginToolFactory,
|
||||
PluginLogger,
|
||||
PluginHookName,
|
||||
PluginHookHandlerMap,
|
||||
ProviderPlugin,
|
||||
} from "../plugins/types.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
export function normalizeExtensionHostPluginLogger(logger: PluginLogger): PluginLogger {
|
||||
return {
|
||||
info: logger.info,
|
||||
warn: logger.warn,
|
||||
error: logger.error,
|
||||
debug: logger.debug,
|
||||
};
|
||||
}
|
||||
|
||||
export function createExtensionHostPluginApi(params: {
|
||||
record: PluginRecord;
|
||||
runtime: PluginRuntime;
|
||||
logger: PluginLogger;
|
||||
config: OpenClawPluginApi["config"];
|
||||
pluginConfig?: Record<string, unknown>;
|
||||
registerTool: (
|
||||
tool: OpenClawPluginToolFactory | { name: string },
|
||||
opts?: { name?: string; names?: string[]; optional?: boolean },
|
||||
) => void;
|
||||
registerHook: (
|
||||
events: string | string[],
|
||||
handler: Parameters<OpenClawPluginApi["registerHook"]>[1],
|
||||
opts?: Parameters<OpenClawPluginApi["registerHook"]>[2],
|
||||
) => void;
|
||||
registerHttpRoute: (params: OpenClawPluginHttpRouteParams) => void;
|
||||
registerChannel: (registration: OpenClawPluginChannelRegistration | object) => void;
|
||||
registerProvider: (provider: ProviderPlugin) => void;
|
||||
registerGatewayMethod: (
|
||||
method: string,
|
||||
handler: OpenClawPluginApi["registerGatewayMethod"] extends (m: string, h: infer H) => void
|
||||
? H
|
||||
: never,
|
||||
) => void;
|
||||
registerCli: (registrar: OpenClawPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
||||
registerService: (service: OpenClawPluginService) => void;
|
||||
registerCommand: (command: OpenClawPluginCommandDefinition) => void;
|
||||
registerContextEngine: (
|
||||
id: string,
|
||||
factory: Parameters<OpenClawPluginApi["registerContextEngine"]>[1],
|
||||
) => void;
|
||||
on: <K extends PluginHookName>(
|
||||
hookName: K,
|
||||
handler: PluginHookHandlerMap[K],
|
||||
opts?: { priority?: number },
|
||||
) => void;
|
||||
}): OpenClawPluginApi {
|
||||
return {
|
||||
id: params.record.id,
|
||||
name: params.record.name,
|
||||
version: params.record.version,
|
||||
description: params.record.description,
|
||||
source: params.record.source,
|
||||
config: params.config,
|
||||
pluginConfig: params.pluginConfig,
|
||||
runtime: params.runtime,
|
||||
logger: normalizeExtensionHostPluginLogger(params.logger),
|
||||
registerTool: (tool, opts) => params.registerTool(tool as never, opts),
|
||||
registerHook: (events, handler, opts) => params.registerHook(events, handler, opts),
|
||||
registerHttpRoute: (routeParams) => params.registerHttpRoute(routeParams),
|
||||
registerChannel: (registration) => params.registerChannel(registration),
|
||||
registerProvider: (provider) => params.registerProvider(provider),
|
||||
registerGatewayMethod: (method, handler) => params.registerGatewayMethod(method, handler),
|
||||
registerCli: (registrar, opts) => params.registerCli(registrar, opts),
|
||||
registerService: (service) => params.registerService(service),
|
||||
registerCommand: (command) => params.registerCommand(command),
|
||||
registerContextEngine: (id, factory) => params.registerContextEngine(id, factory),
|
||||
resolvePath: (input) => resolveUserPath(input),
|
||||
on: (hookName, handler, opts) => params.on(hookName as never, handler as never, opts),
|
||||
};
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import {
|
|||
applyExtensionHostTypedHookPolicy,
|
||||
bridgeExtensionHostLegacyHooks,
|
||||
} from "../extension-host/hook-compat.js";
|
||||
import { createExtensionHostPluginApi } from "../extension-host/plugin-api.js";
|
||||
import {
|
||||
addExtensionChannelRegistration,
|
||||
addExtensionCliRegistration,
|
||||
|
|
@ -37,7 +38,6 @@ import type {
|
|||
GatewayRequestHandlers,
|
||||
} from "../gateway/server-methods/types.js";
|
||||
import { registerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { registerPluginCommand } from "./commands.js";
|
||||
import { normalizeRegisteredProvider } from "./provider-validation.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
|
|
@ -514,13 +514,6 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||
});
|
||||
};
|
||||
|
||||
const normalizeLogger = (logger: PluginLogger): PluginLogger => ({
|
||||
info: logger.info,
|
||||
warn: logger.warn,
|
||||
error: logger.error,
|
||||
debug: logger.debug,
|
||||
});
|
||||
|
||||
const createApi = (
|
||||
record: PluginRecord,
|
||||
params: {
|
||||
|
|
@ -529,21 +522,17 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||
hookPolicy?: PluginTypedHookPolicy;
|
||||
},
|
||||
): OpenClawPluginApi => {
|
||||
return {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
version: record.version,
|
||||
description: record.description,
|
||||
source: record.source,
|
||||
return createExtensionHostPluginApi({
|
||||
record,
|
||||
runtime: registryParams.runtime,
|
||||
logger: registryParams.logger,
|
||||
config: params.config,
|
||||
pluginConfig: params.pluginConfig,
|
||||
runtime: registryParams.runtime,
|
||||
logger: normalizeLogger(registryParams.logger),
|
||||
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
||||
registerHook: (events, handler, opts) =>
|
||||
registerHook(record, events, handler, opts, params.config),
|
||||
registerHttpRoute: (params) => registerHttpRoute(record, params),
|
||||
registerChannel: (registration) => registerChannel(record, registration),
|
||||
registerHttpRoute: (routeParams) => registerHttpRoute(record, routeParams),
|
||||
registerChannel: (registration) => registerChannel(record, registration as never),
|
||||
registerProvider: (provider) => registerProvider(record, provider),
|
||||
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
|
||||
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
|
||||
|
|
@ -568,10 +557,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||
registerEngine: registerContextEngine,
|
||||
});
|
||||
},
|
||||
resolvePath: (input: string) => resolveUserPath(input),
|
||||
on: (hookName, handler, opts) =>
|
||||
registerTypedHook(record, hookName, handler, opts, params.hookPolicy),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue