marketplace-tools.js 6.66 KB
/**
 * marketplace-tools.js — 市场技能搜索 + 动态调用工具
 *
 * 为 Master Agent 提供搜索和动态调用 27,000+ 市场技能的能力。
 * 核心系统 agent 直接列在 prompt 中;市场技能通过 搜索 → 选择 → 调用 的方式使用。
 */

const fs = require('fs');
const path = require('path');

/**
 * search_marketplace_skills — 搜索市场技能
 */
function createSearchSkillsTool() {
    return {
        name: 'search_marketplace_skills',
        description: `在 ArenaSK 市场中搜索技能(27,000+ 可用)。返回匹配的技能列表(名称、描述、ELO 评分等)。
使用场景:
- 用户需要某个特定领域的技能时,先搜索找到最合适的
- 不确定是否存在某个技能时,先搜索确认
- 需要对比多个同类技能时

搜索后,如果找到合适的技能,可以用 call_marketplace_skill 工具动态调用它。`,

        parameters: {
            type: 'object',
            required: ['query'],
            properties: {
                query: {
                    type: 'string',
                    description: '搜索关键词(支持技能名、描述、标签等)',
                },
                limit: {
                    type: 'number',
                    description: '返回结果数量上限(默认 10,最大 20)',
                },
            },
        },

        async execute(params) {
            const { query, limit = 10 } = params;
            const skillDb = require('../../services/skill-db');

            const maxLimit = Math.min(limit || 10, 20);
            const results = skillDb.searchSkills(query, { limit: maxLimit });

            return {
                query,
                count: results.length,
                skills: results.map(s => ({
                    name: s.skillName,
                    description: (s.description || '').substring(0, 200),
                    domain: s.domain || '',
                    keywords: s.keywords || '',
                    downloadCount: s.downloadCount || 0,
                })),
                hint: results.length > 0
                    ? '找到匹配技能。使用 call_marketplace_skill 工具可以动态调用其中一个。'
                    : '未找到匹配技能。尝试不同的搜索关键词。',
            };
        },
    };
}

/**
 * call_marketplace_skill — 动态调用市场技能
 */
function createCallMarketplaceSkillTool() {
    return {
        name: 'call_marketplace_skill',
        description: `动态调用一个市场技能(先用 search_marketplace_skills 搜索找到技能名,再用此工具调用)。
系统会自动加载该技能的 SKILL.md,创建临时 Claude CLI session 执行任务。

使用流程:
1. 先用 search_marketplace_skills 搜索技能
2. 从搜索结果中选择合适的技能
3. 用此工具调用该技能,传入任务描述`,

        parameters: {
            type: 'object',
            required: ['skillName', 'task'],
            properties: {
                skillName: {
                    type: 'string',
                    description: '要调用的技能名称(从搜索结果中获取)',
                },
                task: {
                    type: 'string',
                    description: '给该技能的任务描述 / 用户输入',
                },
            },
        },

        async execute(params, context) {
            const { skillName, task } = params;
            const { agentManager, onSubAgentData } = context;
            const skillDb = require('../../services/skill-db');
            const { resolveSkillDir } = require('../../services/skill-dir-manager');

            // 1. 验证技能存在
            const skill = skillDb.getSkill(skillName);
            if (!skill) {
                throw new Error(`技能 "${skillName}" 不存在。请先用 search_marketplace_skills 搜索。`);
            }

            // 2. 查找 SKILL.md
            const skillDir = resolveSkillDir(skillName) || skill.directory;
            if (!skillDir) {
                throw new Error(`技能 "${skillName}" 的目录未找到。`);
            }

            const skillMdPath = path.join(skillDir, 'SKILL.md');
            if (!fs.existsSync(skillMdPath)) {
                throw new Error(`技能 "${skillName}" 的 SKILL.md 文件不存在: ${skillMdPath}`);
            }

            // 3. 读取 SKILL.md 作为 system prompt
            const skillMd = fs.readFileSync(skillMdPath, 'utf-8');

            // 限制 SKILL.md 大小(避免超大文件)
            const MAX_SKILL_SIZE = 100000; // 100KB
            if (skillMd.length > MAX_SKILL_SIZE) {
                throw new Error(`技能 "${skillName}" 的 SKILL.md 太大 (${skillMd.length} chars),超过 ${MAX_SKILL_SIZE} 限制。`);
            }

            // 4. 构建完整 prompt: SKILL.md + 用户任务
            const fullPrompt = `${skillMd}\n\n---\n\n## 用户任务\n\n${task}`;

            // 5. 检查 agentManager 中是否已有该 agent 配置
            //    如果没有,临时注册到 config 中
            if (!agentManager) {
                throw new Error('call_marketplace_skill 需要 agentManager');
            }

            let agentName = skillName;
            try {
                agentManager.getAgentConfig(agentName);
            } catch {
                // 临时注册该 agent 到 config
                const config = context.config;
                if (config && config.agents) {
                    config.agents[agentName] = {
                        type: 'executor',
                        protocol: 'claude-cli',
                        transport: 'pipe',
                        launcher: 'claude-local',
                        client: 'claude-local',
                        prompt: skillMdPath,
                        description: skill.description || '',
                    };
                }
            }

            // 6. 调用 agent
            try {
                const result = await agentManager.callAgent(agentName, fullPrompt, {
                    onData: (chunk) => {
                        if (onSubAgentData && chunk && chunk.trim()) {
                            onSubAgentData(agentName, chunk);
                        }
                    },
                });

                return {
                    skillName,
                    status: 'success',
                    output: result.output || '',
                };
            } catch (err) {
                return {
                    skillName,
                    status: 'error',
                    error: err.message,
                };
            }
        },
    };
}

module.exports = { createSearchSkillsTool, createCallMarketplaceSkillTool };