tool-registry.js 3.85 KB
/**
 * Tool Registry — Master Agent 工具注册中心
 *
 * 为 Master Agent(Claude CLI 会话)提供可调用的工具集。
 * 工具以 JSON Schema 格式定义,注入到 Master 的 system prompt 中。
 * Master 通过 [TOOL_CALL:] 格式调用工具,系统解析并执行。
 */

class ToolRegistry {
    constructor() {
        /** @type {Map<string, ToolDefinition>} */
        this.tools = new Map();
    }

    /**
     * 注册一个工具
     * @param {ToolDefinition} tool
     */
    register(tool) {
        if (!tool.name || !tool.execute) {
            throw new Error(`Tool must have name and execute: ${JSON.stringify(tool)}`);
        }
        this.tools.set(tool.name, tool);
    }

    /**
     * 批量注册工具
     * @param {ToolDefinition[]} tools
     */
    registerAll(tools) {
        for (const tool of tools) {
            this.register(tool);
        }
    }

    /**
     * 获取工具
     * @param {string} name
     * @returns {ToolDefinition|undefined}
     */
    get(name) {
        return this.tools.get(name);
    }

    /**
     * 执行工具调用
     * @param {string} name - 工具名称
     * @param {object} params - 调用参数
     * @param {object} context - 执行上下文 { agentManager, workDir, ... }
     * @returns {Promise<object>} 工具执行结果
     */
    async execute(name, params, context) {
        const tool = this.tools.get(name);
        if (!tool) {
            return { error: `Unknown tool: ${name}`, status: 'error' };
        }

        try {
            const result = await tool.execute(params, context);
            return { result, status: 'success' };
        } catch (err) {
            return { error: err.message, status: 'error' };
        }
    }

    /**
     * 获取所有工具的 schema(用于注入 system prompt)
     * @returns {object[]}
     */
    getSchemas() {
        return Array.from(this.tools.values()).map(t => ({
            name: t.name,
            description: t.description,
            parameters: t.parameters || {},
        }));
    }

    /**
     * 生成工具描述文本(注入 Master system prompt)
     * @returns {string}
     */
    generateToolPrompt() {
        const schemas = this.getSchemas();
        if (schemas.length === 0) return '';

        const toolDescriptions = schemas.map(s => {
            const params = Object.entries(s.parameters.properties || {})
                .map(([k, v]) => {
                    const required = (s.parameters.required || []).includes(k);
                    return `    - ${k} (${v.type}${required ? ', 必需' : ', 可选'}): ${v.description || ''}`;
                })
                .join('\n');

            return `### ${s.name}\n${s.description}\n\n**参数:**\n${params}`;
        }).join('\n\n---\n\n');

        return `\
## 可用工具

你可以通过以下格式调用工具:

\`\`\`
[TOOL_CALL: tool_name]
{
  "param1": "value1",
  "param2": "value2"
}
[/TOOL_CALL]
\`\`\`

${toolDescriptions}`;
    }

    /**
     * 解析 Master 输出中的工具调用
     * @param {string} output - Master 输出文本
     * @returns {Array<{tool: string, params: object}>}
     */
    static parseToolCalls(output) {
        const regex = /\[TOOL_CALL:\s*([^\]]+)\]\s*([\s\S]*?)\[\/TOOL_CALL\]/g;
        const calls = [];
        let match;

        while ((match = regex.exec(output)) !== null) {
            const toolName = match[1].trim();
            const rawParams = match[2].trim();

            let params = {};
            if (rawParams) {
                try {
                    params = JSON.parse(rawParams);
                } catch {
                    // 如果不是 JSON,尝试作为纯文本 task
                    params = { task: rawParams };
                }
            }

            calls.push({ tool: toolName, params });
        }

        return calls;
    }
}

module.exports = { ToolRegistry };