Plugins: extract plugin api facade
This commit is contained in:
parent
cacd8898b2
commit
639178bd16
@ -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`. |
|
||||
|
||||
105
src/extension-host/plugin-api.test.ts
Normal file
105
src/extension-host/plugin-api.test.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
87
src/extension-host/plugin-api.ts
Normal file
87
src/extension-host/plugin-api.ts
Normal file
@ -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),
|
||||
};
|
||||
}
|
||||
@ -7,7 +7,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 { normalizePluginHttpPath } from "./http-path.js";
|
||||
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
||||
@ -641,13 +640,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: {
|
||||
@ -665,13 +657,11 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
rootDir: record.rootDir,
|
||||
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),
|
||||
@ -713,10 +703,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
});
|
||||
}
|
||||
},
|
||||
resolvePath: (input: string) => resolveUserPath(input),
|
||||
on: (hookName, handler, opts) =>
|
||||
registerTypedHook(record, hookName, handler, opts, params.hookPolicy),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user