Files
clawdbot/src/plugins/registry.ts
2026-01-12 01:16:39 +00:00

209 lines
5.2 KiB
TypeScript

import type { AnyAgentTool } from "../agents/tools/common.js";
import type {
GatewayRequestHandler,
GatewayRequestHandlers,
} from "../gateway/server-methods/types.js";
import { resolveUserPath } from "../utils.js";
import type {
ClawdbotPluginApi,
ClawdbotPluginCliRegistrar,
ClawdbotPluginService,
ClawdbotPluginToolContext,
ClawdbotPluginToolFactory,
PluginConfigUiHint,
PluginDiagnostic,
PluginLogger,
PluginOrigin,
} from "./types.js";
export type PluginToolRegistration = {
pluginId: string;
factory: ClawdbotPluginToolFactory;
names: string[];
source: string;
};
export type PluginCliRegistration = {
pluginId: string;
register: ClawdbotPluginCliRegistrar;
commands: string[];
source: string;
};
export type PluginServiceRegistration = {
pluginId: string;
service: ClawdbotPluginService;
source: string;
};
export type PluginRecord = {
id: string;
name: string;
version?: string;
description?: string;
source: string;
origin: PluginOrigin;
workspaceDir?: string;
enabled: boolean;
status: "loaded" | "disabled" | "error";
error?: string;
toolNames: string[];
gatewayMethods: string[];
cliCommands: string[];
services: string[];
configSchema: boolean;
configUiHints?: Record<string, PluginConfigUiHint>;
};
export type PluginRegistry = {
plugins: PluginRecord[];
tools: PluginToolRegistration[];
gatewayHandlers: GatewayRequestHandlers;
cliRegistrars: PluginCliRegistration[];
services: PluginServiceRegistration[];
diagnostics: PluginDiagnostic[];
};
export type PluginRegistryParams = {
logger: PluginLogger;
coreGatewayHandlers?: GatewayRequestHandlers;
};
export function createPluginRegistry(registryParams: PluginRegistryParams) {
const registry: PluginRegistry = {
plugins: [],
tools: [],
gatewayHandlers: {},
cliRegistrars: [],
services: [],
diagnostics: [],
};
const coreGatewayMethods = new Set(
Object.keys(registryParams.coreGatewayHandlers ?? {}),
);
const pushDiagnostic = (diag: PluginDiagnostic) => {
registry.diagnostics.push(diag);
};
const registerTool = (
record: PluginRecord,
tool: AnyAgentTool | ClawdbotPluginToolFactory,
opts?: { name?: string; names?: string[] },
) => {
const names = opts?.names ?? (opts?.name ? [opts.name] : []);
const factory: ClawdbotPluginToolFactory =
typeof tool === "function"
? tool
: (_ctx: ClawdbotPluginToolContext) => tool;
if (typeof tool !== "function") {
names.push(tool.name);
}
const normalized = names.map((name) => name.trim()).filter(Boolean);
if (normalized.length > 0) {
record.toolNames.push(...normalized);
}
registry.tools.push({
pluginId: record.id,
factory,
names: normalized,
source: record.source,
});
};
const registerGatewayMethod = (
record: PluginRecord,
method: string,
handler: GatewayRequestHandler,
) => {
const trimmed = method.trim();
if (!trimmed) return;
if (coreGatewayMethods.has(trimmed) || registry.gatewayHandlers[trimmed]) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `gateway method already registered: ${trimmed}`,
});
return;
}
registry.gatewayHandlers[trimmed] = handler;
record.gatewayMethods.push(trimmed);
};
const registerCli = (
record: PluginRecord,
registrar: ClawdbotPluginCliRegistrar,
opts?: { commands?: string[] },
) => {
const commands = (opts?.commands ?? [])
.map((cmd) => cmd.trim())
.filter(Boolean);
record.cliCommands.push(...commands);
registry.cliRegistrars.push({
pluginId: record.id,
register: registrar,
commands,
source: record.source,
});
};
const registerService = (
record: PluginRecord,
service: ClawdbotPluginService,
) => {
const id = service.id.trim();
if (!id) return;
record.services.push(id);
registry.services.push({
pluginId: record.id,
service,
source: record.source,
});
};
const normalizeLogger = (logger: PluginLogger): PluginLogger => ({
info: logger.info,
warn: logger.warn,
error: logger.error,
debug: logger.debug,
});
const createApi = (
record: PluginRecord,
params: {
config: ClawdbotPluginApi["config"];
pluginConfig?: Record<string, unknown>;
},
): ClawdbotPluginApi => {
return {
id: record.id,
name: record.name,
version: record.version,
description: record.description,
source: record.source,
config: params.config,
pluginConfig: params.pluginConfig,
logger: normalizeLogger(registryParams.logger),
registerTool: (tool, opts) => registerTool(record, tool, opts),
registerGatewayMethod: (method, handler) =>
registerGatewayMethod(record, method, handler),
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
registerService: (service) => registerService(record, service),
resolvePath: (input: string) => resolveUserPath(input),
};
};
return {
registry,
createApi,
pushDiagnostic,
registerTool,
registerGatewayMethod,
registerCli,
registerService,
};
}