From 70093018f8e23f986c0c905315cdd99ac7d3da44 Mon Sep 17 00:00:00 2001 From: sean Date: Sat, 28 Jun 2025 09:25:54 +0800 Subject: [PATCH] =?UTF-8?q?WIP:=20promptx=20tools=20=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 正在开发 promptx tools 功能 - 包含 bin、commands、DACP 相关修改 - 新增 tool 目录结构 🚧 这是开发中的代码,需要继续完善 --- src/bin/promptx.js | 5 +- src/lib/commands/MCPServerCommand.js | 76 +++-- src/lib/core/pouch/commands/DACPCommand.js | 128 +++++-- src/lib/tool/ToolExecutor.js | 292 ++++++++++++++++ src/lib/tool/ToolInterface.js | 183 ++++++++++ src/lib/tool/ToolUtils.js | 325 ++++++++++++++++++ src/lib/tool/ToolValidator.js | 374 +++++++++++++++++++++ src/lib/tool/index.js | 110 ++++++ 8 files changed, 1438 insertions(+), 55 deletions(-) create mode 100644 src/lib/tool/ToolExecutor.js create mode 100644 src/lib/tool/ToolInterface.js create mode 100644 src/lib/tool/ToolUtils.js create mode 100644 src/lib/tool/ToolValidator.js create mode 100644 src/lib/tool/index.js diff --git a/src/bin/promptx.js b/src/bin/promptx.js index 8fd89fa..2e68479 100755 --- a/src/bin/promptx.js +++ b/src/bin/promptx.js @@ -105,7 +105,7 @@ program .option('--host
', '绑定地址 (仅http/sse传输)', 'localhost') .option('--cors', '启用CORS (仅http/sse传输)', false) .option('--debug', '启用调试模式', false) - .option('--with-dacp', '同时启动DACP服务', false) + .option('--with-dacp', '(已废弃,静默忽略)', false) .action(async (options) => { try { // 设置调试模式 @@ -116,7 +116,8 @@ program // 根据传输类型选择命令 if (options.transport === 'stdio') { const mcpServer = new MCPServerCommand(); - await mcpServer.execute({ withDacp: options.withDacp }); + // 🔧 DACP现为Mock模式,静默忽略用户的withDacp配置 + await mcpServer.execute(); } else if (options.transport === 'http' || options.transport === 'sse') { const mcpHttpServer = new MCPStreamableHttpCommand(); const serverOptions = { diff --git a/src/lib/commands/MCPServerCommand.js b/src/lib/commands/MCPServerCommand.js index 36126cf..85f1005 100644 --- a/src/lib/commands/MCPServerCommand.js +++ b/src/lib/commands/MCPServerCommand.js @@ -84,10 +84,8 @@ class MCPServerCommand { // 设置进程清理处理器 this.setupProcessCleanup(); - // 如果需要启动DACP服务 - if (options.withDacp) { - await this.startDACPService(); - } + // 🔧 DACP现已改为Mock模式,无需启动独立服务 + // 静默忽略任何withDacp选项,保持向后兼容 this.log('🚀 启动MCP Server...'); const transport = new StdioServerTransport(); @@ -145,44 +143,27 @@ class MCPServerCommand { } /** - * 清理子进程 + * 清理子进程 (DACP现为Mock模式,此方法保留但无实际清理工作) + * @deprecated DACP已改为Mock模式,无需清理子进程 */ cleanup() { - if (this.dacpProcess && !this.dacpProcess.killed && this.dacpProcess.pid) { - this.log('🛑 正在终止DACP服务及其所有子进程...'); - - // 使用 tree-kill 终止整个进程树 - treeKill(this.dacpProcess.pid, 'SIGTERM', (err) => { - if (err) { - this.log(`⚠️ 优雅终止失败: ${err.message}`); - - // 3秒后强制终止 - setTimeout(() => { - if (this.dacpProcess && !this.dacpProcess.killed && this.dacpProcess.pid) { - this.log('⚠️ DACP服务未响应SIGTERM,强制终止整个进程树...'); - treeKill(this.dacpProcess.pid, 'SIGKILL', (killErr) => { - if (killErr) { - this.log(`❌ 强制终止失败: ${killErr.message}`); - } else { - this.log('✅ DACP服务进程树已强制终止'); - } - }); - } - }, 3000); - } else { - this.log('✅ DACP服务进程树已优雅终止'); - } - }); - } + // 🔧 DACP现已改为Mock模式,无需清理DACP子进程 + // HTTP模式的进程清理代码已保留作为参考实现 + this.log('🔧 Mock模式下无需清理DACP子进程'); } /** - * 检测DACP服务是否已经运行 + * 检测DACP服务是否已经运行 (HTTP模式 - 仅作参考实现保留) + * @deprecated DACP已改为Mock模式,此方法仅保留作为参考 * @param {string} host - 主机地址 * @param {number} port - 端口号 * @returns {Promise} 服务是否运行 */ async isDACPServiceRunning(host = 'localhost', port = 3002) { + // 🔧 Mock模式下始终返回false,因为不需要HTTP服务 + return false; + + /* HTTP模式参考实现(已禁用) const http = require('http'); return new Promise((resolve) => { @@ -224,15 +205,28 @@ class MCPServerCommand { req.end(); }); + */ } /** - * 获取DACP服务信息 + * 获取DACP服务信息 (HTTP模式 - 仅作参考实现保留) + * @deprecated DACP已改为Mock模式,此方法仅保留作为参考 * @param {string} host - 主机地址 * @param {number} port - 端口号 * @returns {Promise} 服务信息 */ async getDACPServiceInfo(host = 'localhost', port = 3002) { + // 🔧 Mock模式下返回模拟的服务信息 + return { + service: { + name: 'PromptX DACP Mock Service', + version: '1.0.0-mock' + }, + available_actions: ['calculate', 'send_email'], + mode: 'local_mock' + }; + + /* HTTP模式参考实现(已禁用) const http = require('http'); return new Promise((resolve) => { @@ -271,12 +265,25 @@ class MCPServerCommand { req.end(); }); + */ } /** - * 启动DACP服务 + * 启动DACP服务 (HTTP模式 - 仅作参考实现保留) + * @deprecated DACP已改为Mock模式,此方法仅保留作为参考 */ async startDACPService() { + // 🔧 Mock模式下输出提示信息即可 + console.error(''); + console.error('====================================='); + console.error('🔧 DACP Mock模式已启用'); + console.error('📦 本地函数调用模式:无需HTTP服务'); + console.error('🔧 支持的Actions: send_email, calculate'); + console.error('✅ Mock模式启动成功'); + console.error('====================================='); + console.error(''); + + /* HTTP模式参考实现(已禁用) const { spawn } = require('child_process'); const path = require('path'); @@ -395,6 +402,7 @@ class MCPServerCommand { this.log(`❌ DACP服务启动失败: ${error.message}`); throw error; } + */ } /** diff --git a/src/lib/core/pouch/commands/DACPCommand.js b/src/lib/core/pouch/commands/DACPCommand.js index ef03c9a..6636265 100644 --- a/src/lib/core/pouch/commands/DACPCommand.js +++ b/src/lib/core/pouch/commands/DACPCommand.js @@ -1,9 +1,14 @@ const BasePouchCommand = require('../BasePouchCommand'); const http = require('http'); +const fs = require('fs'); +const path = require('path'); /** * DACP服务调用命令 * 负责调用DACP服务,实现从AI建议到AI行动的转换 + * + * 🔧 当前实现:Mock模式(本地函数调用) + * 🌐 HTTP模式代码保留作为参考实现 */ class DACPCommand extends BasePouchCommand { constructor() { @@ -12,6 +17,10 @@ class DACPCommand extends BasePouchCommand { // 统一的DACP服务端点 // 所有service_id都路由到同一个服务 this.defaultEndpoint = 'http://localhost:3002/dacp'; + + // 🔧 永久使用Mock模式(本地函数调用) + // 不再支持HTTP模式,简化架构复杂度 + this.useMockMode = true; } /** @@ -37,7 +46,8 @@ class DACPCommand extends BasePouchCommand { } /** - * 获取服务端点 + * 获取服务端点(HTTP模式 - 仅作参考实现保留) + * @deprecated 当前使用Mock模式,此方法仅保留作为参考 * @param {string} serviceId - 服务ID * @returns {string} 服务端点URL */ @@ -59,20 +69,8 @@ class DACPCommand extends BasePouchCommand { const { service_id, action, parameters } = args; - // 获取服务端点(现在是统一的) - const endpoint = this.getServiceEndpoint(service_id); - - // 构造DACP请求 - const dacpRequest = { - service_id, - action, - parameters, - request_id: `req_${Date.now()}` - }; - - // 调用DACP服务 - const result = await this.makeHttpRequest(endpoint, dacpRequest); - return result; + // 🔧 直接使用本地Mock调用 + return await this.callLocalService(args); } catch (error) { // 统一错误处理 @@ -87,7 +85,99 @@ class DACPCommand extends BasePouchCommand { } /** - * 发送HTTP请求 + * 本地服务调用(Mock模式) + * @param {Object} args - 调用参数 + * @returns {Promise} DACP标准响应 + */ + async callLocalService(args) { + const startTime = Date.now(); + const { service_id, action, parameters } = args; + const request_id = `req_${Date.now()}`; + + try { + // 1. 读取DACP配置 + const configPath = path.join(__dirname, '../../../dacp/dacp-promptx-service/dacp.config.json'); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + + // 2. 验证service_id + if (service_id !== config.service.id) { + throw new Error(`Service ${service_id} not found. This is ${config.service.id}`); + } + + // 3. 动态加载actions + const actionsDir = path.join(__dirname, '../../../dacp/dacp-promptx-service/actions'); + const actions = {}; + + if (fs.existsSync(actionsDir)) { + fs.readdirSync(actionsDir).forEach(file => { + if (file.endsWith('.js')) { + const actionName = file.replace('.js', ''); + actions[actionName] = require(path.join(actionsDir, file)); + } + }); + } + + // 4. 查找action处理器 + let handler = null; + + // 先按模块名查找 + for (const [moduleName, module] of Object.entries(actions)) { + if (module[action] && typeof module[action] === 'function') { + handler = module[action]; + break; + } + } + + // 找不到则按精确匹配查找 + if (!handler && actions[action]) { + handler = actions[action]; + } + + if (!handler) { + throw new Error(`Action ${action} is not supported`); + } + + // 5. 执行action + const result = await handler(parameters); + + // 6. 返回DACP标准格式响应 + return { + request_id: request_id, + success: true, + data: { + execution_result: result, + evaluation: { + constraint_compliance: true, + rule_adherence: true, + guideline_alignment: true + }, + applied_guidelines: [ + 'DACP protocol standard', + 'Local mock execution' + ], + performance_metrics: { + execution_time: `${Date.now() - startTime}ms`, + resource_usage: 'minimal' + } + } + }; + + } catch (error) { + return { + request_id: request_id, + success: false, + error: { + code: error.message.includes('not found') ? 'INVALID_SERVICE' : + error.message.includes('not supported') ? 'UNKNOWN_ACTION' : 'EXECUTION_ERROR', + message: error.message + } + }; + } + } + + /** + * 发送HTTP请求(HTTP模式 - 仅作参考实现保留) + * @deprecated 当前使用Mock模式,此方法仅保留作为参考 * @param {string} url - 请求URL * @param {Object} data - 请求数据 * @returns {Promise} 响应数据 @@ -149,8 +239,7 @@ class DACPCommand extends BasePouchCommand { if (result.success) { const executionResult = result.data.execution_result; const metrics = result.data.performance_metrics; - - return `🚀 DACP服务调用成功 + return `🚀 DACP服务调用成功 (🔧 本地Mock模式) 📋 执行结果: ${JSON.stringify(executionResult, null, 2)} @@ -172,9 +261,10 @@ ${JSON.stringify(executionResult, null, 2)} return `❌ DACP服务调用异常 错误详情: ${error.message} +运行模式: 🔧 本地Mock模式 💡 请检查: -1. DACP服务是否运行 (http://localhost:3002/health) +1. DACP action模块是否存在 2. 服务ID是否正确 3. 操作名称是否有效 4. 参数格式是否正确`; diff --git a/src/lib/tool/ToolExecutor.js b/src/lib/tool/ToolExecutor.js new file mode 100644 index 0000000..e02f0ac --- /dev/null +++ b/src/lib/tool/ToolExecutor.js @@ -0,0 +1,292 @@ +const ToolValidator = require('./ToolValidator'); +const { TOOL_ERROR_CODES } = require('./ToolInterface'); + +/** + * ToolExecutor 工具执行器 + * 负责工具的加载、验证、执行和结果处理 + */ +class ToolExecutor { + constructor(options = {}) { + this.options = { + timeout: 30000, // 默认30秒超时 + maxConcurrency: 10, // 最大并发数 + enableCache: true, // 启用工具缓存 + ...options + }; + + this.toolCache = new Map(); // 工具实例缓存 + this.runningTasks = new Set(); // 正在执行的任务 + } + + /** + * 执行工具(从代码内容) + * @param {string} toolContent - 工具JavaScript代码内容 + * @param {Object} parameters - 工具参数 + * @param {Object} context - 执行上下文 + * @returns {Promise} 执行结果 + */ + async execute(toolContent, parameters = {}, context = {}) { + const executionId = this.generateExecutionId(); + const startTime = Date.now(); + + try { + // 1. 并发控制 + if (this.runningTasks.size >= this.options.maxConcurrency) { + throw new Error(`超出最大并发限制: ${this.options.maxConcurrency}`); + } + + this.runningTasks.add(executionId); + + // 2. 执行工具代码并创建实例 + const tool = this.executeToolContent(toolContent, context.toolName || 'unknown'); + + // 3. 参数验证 + const validation = this.validateParameters(tool, parameters); + if (!validation.valid) { + return this.formatError(TOOL_ERROR_CODES.VALIDATION_ERROR, '参数验证失败', { + errors: validation.errors, + parameters: parameters + }); + } + + // 4. 执行工具(带超时控制) + const result = await this.executeWithTimeout(tool, parameters); + const executionTime = Date.now() - startTime; + + // 5. 格式化成功结果 + return this.formatSuccess(result, { + executionId, + executionTime: `${executionTime}ms`, + tool: tool.getMetadata ? tool.getMetadata() : { name: context.toolName || 'unknown' } + }); + + } catch (error) { + const executionTime = Date.now() - startTime; + return this.formatError( + this.getErrorCode(error), + error.message, + { + executionId, + executionTime: `${executionTime}ms`, + stack: error.stack + } + ); + } finally { + this.runningTasks.delete(executionId); + } + } + + + /** + * 执行工具内容并返回实例 + * @param {string} toolContent - 工具代码内容 + * @param {string} toolName - 工具名称 + * @returns {Object} 工具实例 + */ + executeToolContent(toolContent, toolName) { + try { + // 创建安全的执行环境 + const sandbox = this.createSandbox(); + + // 执行工具代码 + const vm = require('vm'); + const script = new vm.Script(toolContent, { filename: `${toolName}.js` }); + const context = vm.createContext(sandbox); + + script.runInContext(context); + + // 获取导出的工具类 + const ToolClass = context.module.exports; + + if (!ToolClass || typeof ToolClass !== 'function') { + throw new Error(`工具未正确导出类: ${toolName}`); + } + + // 创建工具实例 + return new ToolClass(); + + } catch (error) { + throw new Error(`工具代码执行失败 ${toolName}: ${error.message}`); + } + } + + /** + * 创建安全的执行沙箱 + * @returns {Object} 沙箱环境 + */ + createSandbox() { + return { + require: require, + module: { exports: {} }, + exports: {}, + console: console, + Buffer: Buffer, + process: { + env: process.env, + hrtime: process.hrtime + }, + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + // 基础全局对象 + Object: Object, + Array: Array, + String: String, + Number: Number, + Boolean: Boolean, + Date: Date, + JSON: JSON, + Math: Math, + RegExp: RegExp, + Error: Error + }; + } + + + /** + * 带超时的工具执行 + * @param {BaseTool} tool - 工具实例 + * @param {Object} parameters - 参数 + * @returns {Promise<*>} 执行结果 + */ + async executeWithTimeout(tool, parameters) { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + reject(new Error(`工具执行超时: ${this.options.timeout}ms`)); + }, this.options.timeout); + + tool.execute(parameters) + .then(result => { + clearTimeout(timeoutId); + resolve(result); + }) + .catch(error => { + clearTimeout(timeoutId); + reject(error); + }); + }); + } + + /** + * 生成执行ID + * @returns {string} 唯一执行ID + */ + generateExecutionId() { + return `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + /** + * 参数验证 + * @param {Object} tool - 工具实例 + * @param {Object} parameters - 参数 + * @returns {Object} 验证结果 + */ + validateParameters(tool, parameters) { + // 如果工具有自定义validate方法,使用它 + if (typeof tool.validate === 'function') { + return tool.validate(parameters); + } + + // 否则使用默认验证 + return ToolValidator.defaultValidate(tool, parameters); + } + + /** + * 为工具增强默认实现 + * @param {Object} tool - 工具实例 + */ + enhanceToolWithDefaults(tool) { + // 如果没有validate方法,提供默认实现 + if (!tool.validate) { + tool.validate = (parameters) => ToolValidator.defaultValidate(tool, parameters); + } + + // 如果没有cleanup方法,提供空实现 + if (!tool.cleanup) { + tool.cleanup = () => {}; + } + + // 如果没有init方法,提供空实现 + if (!tool.init) { + tool.init = () => {}; + } + } + + /** + * 获取错误代码 + * @param {Error} error - 错误对象 + * @returns {string} 错误代码 + */ + getErrorCode(error) { + if (error.message.includes('超时')) return TOOL_ERROR_CODES.TIMEOUT_ERROR; + if (error.message.includes('不存在')) return 'TOOL_NOT_FOUND'; + if (error.message.includes('验证失败')) return TOOL_ERROR_CODES.VALIDATION_ERROR; + if (error.message.includes('并发限制')) return 'CONCURRENCY_ERROR'; + if (error.message.includes('接口不符合规范')) return 'INTERFACE_ERROR'; + return TOOL_ERROR_CODES.EXECUTION_ERROR; + } + + /** + * 格式化成功结果 + * @param {*} data - 结果数据 + * @param {Object} metadata - 元信息 + * @returns {Object} 标准化结果 + */ + formatSuccess(data, metadata = {}) { + return { + success: true, + data: data, + metadata: { + executor: 'ToolExecutor', + timestamp: new Date().toISOString(), + ...metadata + } + }; + } + + /** + * 格式化错误结果 + * @param {string} code - 错误代码 + * @param {string} message - 错误消息 + * @param {Object} details - 错误详情 + * @returns {Object} 标准化错误 + */ + formatError(code, message, details = {}) { + return { + success: false, + error: { + code: code, + message: message, + details: details + }, + metadata: { + executor: 'ToolExecutor', + timestamp: new Date().toISOString() + } + }; + } + + /** + * 获取执行统计信息 + * @returns {Object} 统计信息 + */ + getStats() { + return { + runningTasks: this.runningTasks.size, + cachedTools: this.toolCache.size, + maxConcurrency: this.options.maxConcurrency, + timeout: this.options.timeout + }; + } + + /** + * 清理资源 + */ + cleanup() { + this.toolCache.clear(); + this.runningTasks.clear(); + } +} + +module.exports = ToolExecutor; \ No newline at end of file diff --git a/src/lib/tool/ToolInterface.js b/src/lib/tool/ToolInterface.js new file mode 100644 index 0000000..ddf473d --- /dev/null +++ b/src/lib/tool/ToolInterface.js @@ -0,0 +1,183 @@ +/** + * ToolInterface - PromptX工具接口规范 + * 定义鸭子类型的工具接口,外部工具无需继承任何类 + */ + +/** + * Tool接口规范定义 + */ +const TOOL_INTERFACE = { + // 必须实现的方法 + required: [ + { + name: 'getMetadata', + signature: '() => Object', + description: '获取工具元信息', + returns: { + name: 'string - 工具名称', + description: 'string - 工具描述', + version: 'string - 版本号', + category: 'string - 分类(可选)', + author: 'string - 作者(可选)' + } + }, + { + name: 'getSchema', + signature: '() => Object', + description: '获取参数JSON Schema', + returns: { + type: 'string - 参数类型,通常为object', + properties: 'Object - 参数属性定义', + required: 'Array - 必需参数列表(可选)', + additionalProperties: 'boolean - 是否允许额外参数(可选)' + } + }, + { + name: 'execute', + signature: '(parameters: Object) => Promise', + description: '执行工具主逻辑', + parameters: { + parameters: 'Object - 工具参数,符合getSchema定义' + }, + returns: 'Promise - 工具执行结果' + } + ], + + // 可选实现的方法 + optional: [ + { + name: 'validate', + signature: '(parameters: Object) => Object', + description: '验证参数(可选,有默认实现)', + parameters: { + parameters: 'Object - 待验证参数' + }, + returns: { + valid: 'boolean - 验证是否通过', + errors: 'Array - 错误信息列表' + } + }, + { + name: 'cleanup', + signature: '() => void | Promise', + description: '清理资源(可选)', + returns: 'void | Promise' + }, + { + name: 'init', + signature: '(config?: Object) => void | Promise', + description: '初始化工具(可选)', + parameters: { + config: 'Object - 初始化配置(可选)' + }, + returns: 'void | Promise' + } + ] +}; + +/** + * 工具错误类型定义 + */ +const TOOL_ERROR_CODES = { + VALIDATION_ERROR: 'VALIDATION_ERROR', // 参数验证失败 + EXECUTION_ERROR: 'EXECUTION_ERROR', // 执行错误 + TIMEOUT_ERROR: 'TIMEOUT_ERROR', // 超时错误 + PERMISSION_ERROR: 'PERMISSION_ERROR', // 权限错误 + RESOURCE_ERROR: 'RESOURCE_ERROR', // 资源错误 + CONFIGURATION_ERROR: 'CONFIGURATION_ERROR' // 配置错误 +}; + +/** + * 标准结果格式定义 + */ +const TOOL_RESULT_FORMAT = { + success: { + success: true, + data: 'any - 工具返回的实际数据', + metadata: { + tool: 'string - 工具名称', + executionTime: 'string - 执行时间', + timestamp: 'string - 时间戳', + // ...其他元信息 + } + }, + + error: { + success: false, + error: { + code: 'string - 错误代码(见TOOL_ERROR_CODES)', + message: 'string - 错误消息', + details: 'Object - 错误详情(可选)' + }, + metadata: { + tool: 'string - 工具名称', + timestamp: 'string - 时间戳', + // ...其他元信息 + } + } +}; + +/** + * 示例工具实现 + */ +const EXAMPLE_TOOL = ` +class ExampleTool { + getMetadata() { + return { + name: 'example-tool', + description: '示例工具', + version: '1.0.0', + category: 'example', + author: 'PromptX Team' + }; + } + + getSchema() { + return { + type: 'object', + properties: { + input: { + type: 'string', + description: '输入参数' + } + }, + required: ['input'], + additionalProperties: false + }; + } + + async execute(parameters) { + const { input } = parameters; + + // 工具逻辑 + const result = \`处理结果: \${input}\`; + + return result; + } + + // 可选:自定义参数验证 + validate(parameters) { + const errors = []; + + if (!parameters.input || parameters.input.trim() === '') { + errors.push('input不能为空'); + } + + return { valid: errors.length === 0, errors }; + } + + // 可选:清理资源 + cleanup() { + console.log('清理资源'); + } +} + +module.exports = ExampleTool; +`; + +module.exports = { + TOOL_INTERFACE, + TOOL_ERROR_CODES, + TOOL_RESULT_FORMAT, + EXAMPLE_TOOL +}; \ No newline at end of file diff --git a/src/lib/tool/ToolUtils.js b/src/lib/tool/ToolUtils.js new file mode 100644 index 0000000..88af9fa --- /dev/null +++ b/src/lib/tool/ToolUtils.js @@ -0,0 +1,325 @@ +const ToolValidator = require('./ToolValidator'); +const { TOOL_ERROR_CODES, TOOL_RESULT_FORMAT } = require('./ToolInterface'); + +/** + * ToolUtils - 工具实用函数集合 + * 提供工具开发和使用的辅助函数 + */ +class ToolUtils { + /** + * 创建标准化的成功结果 + * @param {*} data - 结果数据 + * @param {Object} options - 选项 + * @returns {Object} 标准化结果 + */ + static createSuccessResult(data, options = {}) { + const { + tool = 'unknown', + executionTime = null, + metadata = {} + } = options; + + return { + success: true, + data: data, + metadata: { + tool: tool, + executionTime: executionTime, + timestamp: new Date().toISOString(), + ...metadata + } + }; + } + + /** + * 创建标准化的错误结果 + * @param {string} code - 错误代码 + * @param {string} message - 错误消息 + * @param {Object} options - 选项 + * @returns {Object} 标准化错误 + */ + static createErrorResult(code, message, options = {}) { + const { + tool = 'unknown', + details = {}, + metadata = {} + } = options; + + return { + success: false, + error: { + code: code, + message: message, + details: details + }, + metadata: { + tool: tool, + timestamp: new Date().toISOString(), + ...metadata + } + }; + } + + /** + * 验证工具结果格式 + * @param {Object} result - 工具结果 + * @returns {Object} 验证结果 + */ + static validateResult(result) { + const errors = []; + + if (!result || typeof result !== 'object') { + errors.push('结果必须是对象类型'); + return { valid: false, errors }; + } + + if (typeof result.success !== 'boolean') { + errors.push('结果必须包含success(boolean)字段'); + } + + if (result.success) { + // 成功结果验证 + if (!('data' in result)) { + errors.push('成功结果必须包含data字段'); + } + } else { + // 错误结果验证 + if (!result.error || typeof result.error !== 'object') { + errors.push('错误结果必须包含error(object)字段'); + } else { + if (!result.error.code || typeof result.error.code !== 'string') { + errors.push('错误结果必须包含error.code(string)字段'); + } + if (!result.error.message || typeof result.error.message !== 'string') { + errors.push('错误结果必须包含error.message(string)字段'); + } + } + } + + return { valid: errors.length === 0, errors }; + } + + /** + * 安全地执行工具方法 + * @param {Object} tool - 工具实例 + * @param {string} methodName - 方法名 + * @param {...any} args - 方法参数 + * @returns {Promise<*>} 执行结果 + */ + static async safeExecute(tool, methodName, ...args) { + try { + if (!tool || typeof tool[methodName] !== 'function') { + throw new Error(`工具不存在方法: ${methodName}`); + } + + const result = await tool[methodName](...args); + return result; + } catch (error) { + throw new Error(`方法执行失败 ${methodName}: ${error.message}`); + } + } + + /** + * 工具性能分析 + * @param {Object} tool - 工具实例 + * @param {Object} parameters - 测试参数 + * @param {Object} options - 选项 + * @returns {Promise} 性能分析结果 + */ + static async benchmarkTool(tool, parameters = {}, options = {}) { + const { + iterations = 10, + warmup = 3 + } = options; + + const results = { + toolName: 'unknown', + iterations: iterations, + warmup: warmup, + times: [], + stats: {} + }; + + try { + // 获取工具名称 + if (tool.getMetadata) { + const metadata = tool.getMetadata(); + results.toolName = metadata.name || 'unknown'; + } + + // 验证工具接口 + const validation = ToolValidator.validateTool(tool); + if (!validation.valid) { + throw new Error(`工具接口验证失败: ${validation.errors.join(', ')}`); + } + + // 预热运行 + for (let i = 0; i < warmup; i++) { + await tool.execute(parameters); + } + + // 性能测试 + for (let i = 0; i < iterations; i++) { + const startTime = process.hrtime.bigint(); + await tool.execute(parameters); + const endTime = process.hrtime.bigint(); + + const executionTime = Number(endTime - startTime) / 1000000; // 转换为毫秒 + results.times.push(executionTime); + } + + // 计算统计信息 + results.stats = this.calculateStats(results.times); + + } catch (error) { + results.error = error.message; + } + + return results; + } + + /** + * 计算统计信息 + * @param {Array} times - 时间数组 + * @returns {Object} 统计信息 + */ + static calculateStats(times) { + if (times.length === 0) { + return {}; + } + + const sorted = [...times].sort((a, b) => a - b); + const sum = times.reduce((a, b) => a + b, 0); + + return { + count: times.length, + min: Math.min(...times), + max: Math.max(...times), + mean: sum / times.length, + median: sorted[Math.floor(sorted.length / 2)], + p95: sorted[Math.floor(sorted.length * 0.95)], + p99: sorted[Math.floor(sorted.length * 0.99)] + }; + } + + /** + * 生成工具模板代码 + * @param {Object} options - 工具选项 + * @returns {string} 工具模板代码 + */ + static generateToolTemplate(options = {}) { + const { + toolName = 'ExampleTool', + className = 'ExampleTool', + description = '示例工具', + category = 'utility', + author = 'PromptX Developer' + } = options; + + return `/** + * ${className} - ${description} + * 使用PromptX鸭子类型接口,无需继承任何基类 + */ +class ${className} { + getMetadata() { + return { + name: '${toolName}', + description: '${description}', + version: '1.0.0', + category: '${category}', + author: '${author}' + }; + } + + getSchema() { + return { + type: 'object', + properties: { + input: { + type: 'string', + description: '输入参数' + } + }, + required: ['input'], + additionalProperties: false + }; + } + + async execute(parameters) { + const { input } = parameters; + + try { + // TODO: 实现工具逻辑 + const result = \`处理结果: \${input}\`; + + return result; + } catch (error) { + throw new Error(\`执行失败: \${error.message}\`); + } + } + + // 可选:自定义参数验证 + validate(parameters) { + const errors = []; + + if (!parameters.input || parameters.input.trim() === '') { + errors.push('input不能为空'); + } + + return { valid: errors.length === 0, errors }; + } + + // 可选:清理资源 + cleanup() { + // 清理逻辑 + } +} + +module.exports = ${className}; +`; + } + + /** + * 创建工具开发指南 + * @returns {string} 开发指南 + */ + static getDevGuide() { + return ` +# PromptX Tool 开发指南 + +## 鸭子类型接口 +PromptX工具使用鸭子类型设计,无需继承任何基类。只需实现以下接口: + +### 必需方法 +1. \`getMetadata()\` - 返回工具元信息 +2. \`getSchema()\` - 返回参数JSON Schema +3. \`execute(parameters)\` - 执行工具逻辑 + +### 可选方法 +1. \`validate(parameters)\` - 自定义参数验证 +2. \`cleanup()\` - 清理资源 +3. \`init(config)\` - 初始化工具 + +## 开发步骤 +1. 使用 ToolUtils.generateToolTemplate() 生成模板 +2. 实现必需的接口方法 +3. 使用 ToolValidator.validateTool() 验证接口 +4. 使用 ToolUtils.benchmarkTool() 性能测试 +5. 注册到工具注册表 + +## 示例代码 +\`\`\`javascript +${this.generateToolTemplate()} +\`\`\` + +## 最佳实践 +- 保持execute方法的幂等性 +- 提供清晰的错误消息 +- 使用合适的JSON Schema验证 +- 实现适当的资源清理 +- 遵循统一的结果格式 +`; + } +} + +module.exports = ToolUtils; \ No newline at end of file diff --git a/src/lib/tool/ToolValidator.js b/src/lib/tool/ToolValidator.js new file mode 100644 index 0000000..dc4aa21 --- /dev/null +++ b/src/lib/tool/ToolValidator.js @@ -0,0 +1,374 @@ +const { TOOL_INTERFACE, TOOL_ERROR_CODES } = require('./ToolInterface'); + +/** + * ToolValidator - 工具接口验证器 + * 使用鸭子类型验证工具是否符合PromptX接口规范 + */ +class ToolValidator { + /** + * 验证工具是否符合接口规范 + * @param {any} tool - 待验证的工具对象 + * @returns {Object} 验证结果 {valid: boolean, errors: [], warnings: []} + */ + static validateTool(tool) { + const errors = []; + const warnings = []; + + // 基础类型检查 + if (!tool || typeof tool !== 'object') { + errors.push('工具必须是对象类型'); + return { valid: false, errors, warnings }; + } + + // 验证必需方法 + for (const methodSpec of TOOL_INTERFACE.required) { + const methodName = methodSpec.name; + + if (!(methodName in tool)) { + errors.push(`缺少必需方法: ${methodName}`); + continue; + } + + if (typeof tool[methodName] !== 'function') { + errors.push(`${methodName} 必须是函数类型`); + continue; + } + + // 方法签名验证 + try { + const validationResult = this.validateMethod(tool, methodSpec); + if (!validationResult.valid) { + errors.push(...validationResult.errors); + warnings.push(...validationResult.warnings); + } + } catch (error) { + warnings.push(`${methodName} 方法验证时出错: ${error.message}`); + } + } + + // 验证可选方法 + for (const methodSpec of TOOL_INTERFACE.optional) { + const methodName = methodSpec.name; + + if (methodName in tool) { + if (typeof tool[methodName] !== 'function') { + warnings.push(`${methodName} 应该是函数类型`); + } else { + try { + const validationResult = this.validateMethod(tool, methodSpec); + if (!validationResult.valid) { + warnings.push(...validationResult.errors); + } + } catch (error) { + warnings.push(`${methodName} 方法验证时出错: ${error.message}`); + } + } + } + } + + return { + valid: errors.length === 0, + errors, + warnings + }; + } + + /** + * 验证特定方法 + * @param {Object} tool - 工具对象 + * @param {Object} methodSpec - 方法规范 + * @returns {Object} 验证结果 + */ + static validateMethod(tool, methodSpec) { + const errors = []; + const warnings = []; + const methodName = methodSpec.name; + + try { + switch (methodName) { + case 'getMetadata': + return this.validateGetMetadata(tool); + case 'getSchema': + return this.validateGetSchema(tool); + case 'execute': + return this.validateExecute(tool); + case 'validate': + return this.validateValidateMethod(tool); + default: + return { valid: true, errors: [], warnings: [] }; + } + } catch (error) { + errors.push(`${methodName} 方法调用失败: ${error.message}`); + return { valid: false, errors, warnings }; + } + } + + /** + * 验证getMetadata方法 + * @param {Object} tool - 工具对象 + * @returns {Object} 验证结果 + */ + static validateGetMetadata(tool) { + const errors = []; + const warnings = []; + + try { + const metadata = tool.getMetadata(); + + if (!metadata || typeof metadata !== 'object') { + errors.push('getMetadata() 必须返回对象'); + return { valid: false, errors, warnings }; + } + + // 验证必需字段 + if (!metadata.name || typeof metadata.name !== 'string') { + errors.push('metadata.name 必须是非空字符串'); + } + + if (!metadata.description || typeof metadata.description !== 'string') { + errors.push('metadata.description 必须是非空字符串'); + } + + if (!metadata.version || typeof metadata.version !== 'string') { + errors.push('metadata.version 必须是非空字符串'); + } + + // 验证可选字段 + if (metadata.category && typeof metadata.category !== 'string') { + warnings.push('metadata.category 应该是字符串类型'); + } + + if (metadata.author && typeof metadata.author !== 'string') { + warnings.push('metadata.author 应该是字符串类型'); + } + + } catch (error) { + errors.push(`getMetadata() 执行失败: ${error.message}`); + } + + return { valid: errors.length === 0, errors, warnings }; + } + + /** + * 验证getSchema方法 + * @param {Object} tool - 工具对象 + * @returns {Object} 验证结果 + */ + static validateGetSchema(tool) { + const errors = []; + const warnings = []; + + try { + const schema = tool.getSchema(); + + if (!schema || typeof schema !== 'object') { + errors.push('getSchema() 必须返回对象'); + return { valid: false, errors, warnings }; + } + + // 基础JSON Schema验证 + if (!schema.type) { + warnings.push('schema.type 建议定义'); + } + + if (schema.type && typeof schema.type !== 'string') { + errors.push('schema.type 必须是字符串'); + } + + if (schema.properties && typeof schema.properties !== 'object') { + errors.push('schema.properties 必须是对象'); + } + + if (schema.required && !Array.isArray(schema.required)) { + errors.push('schema.required 必须是数组'); + } + + } catch (error) { + errors.push(`getSchema() 执行失败: ${error.message}`); + } + + return { valid: errors.length === 0, errors, warnings }; + } + + /** + * 验证execute方法 + * @param {Object} tool - 工具对象 + * @returns {Object} 验证结果 + */ + static validateExecute(tool) { + const errors = []; + const warnings = []; + + // 检查方法签名 + const executeMethod = tool.execute; + if (executeMethod.length === 0) { + warnings.push('execute() 方法建议接受parameters参数'); + } + + // 注意:这里不实际调用execute方法,因为可能有副作用 + // 只进行静态检查 + + return { valid: errors.length === 0, errors, warnings }; + } + + /** + * 验证validate方法(可选) + * @param {Object} tool - 工具对象 + * @returns {Object} 验证结果 + */ + static validateValidateMethod(tool) { + const errors = []; + const warnings = []; + + try { + // 测试validate方法的返回格式 + const testParams = {}; + const result = tool.validate(testParams); + + if (!result || typeof result !== 'object') { + errors.push('validate() 必须返回对象'); + return { valid: false, errors, warnings }; + } + + if (typeof result.valid !== 'boolean') { + errors.push('validate() 返回值必须包含valid(boolean)字段'); + } + + if (result.errors && !Array.isArray(result.errors)) { + errors.push('validate() 返回值的errors字段必须是数组'); + } + + } catch (error) { + warnings.push(`validate() 方法测试失败: ${error.message}`); + } + + return { valid: errors.length === 0, errors, warnings }; + } + + /** + * 为工具提供默认的validate方法实现 + * @param {Object} tool - 工具对象 + * @param {Object} parameters - 待验证参数 + * @returns {Object} 验证结果 + */ + static defaultValidate(tool, parameters) { + const errors = []; + + try { + // 获取schema + const schema = tool.getSchema(); + + // 基础类型检查 + if (!parameters || typeof parameters !== 'object') { + errors.push('参数必须是对象类型'); + return { valid: false, errors }; + } + + // 必需参数检查 + if (schema.required && Array.isArray(schema.required)) { + for (const field of schema.required) { + if (!(field in parameters)) { + errors.push(`缺少必需参数: ${field}`); + } + } + } + + // 基础字段类型检查 + if (schema.properties && typeof schema.properties === 'object') { + for (const [field, fieldSchema] of Object.entries(schema.properties)) { + if (field in parameters) { + const value = parameters[field]; + const expectedType = fieldSchema.type; + + if (expectedType && !this.validateType(value, expectedType)) { + errors.push(`参数 ${field} 类型错误,期望 ${expectedType},实际 ${typeof value}`); + } + } + } + } + + } catch (error) { + errors.push(`参数验证失败: ${error.message}`); + } + + return { valid: errors.length === 0, errors }; + } + + /** + * 类型验证辅助方法 + * @param {*} value - 待验证值 + * @param {string} expectedType - 期望类型 + * @returns {boolean} 是否匹配 + */ + static validateType(value, expectedType) { + switch (expectedType) { + case 'string': + return typeof value === 'string'; + case 'number': + return typeof value === 'number'; + case 'boolean': + return typeof value === 'boolean'; + case 'object': + return typeof value === 'object' && value !== null; + case 'array': + return Array.isArray(value); + default: + return true; // 未知类型,跳过验证 + } + } + + /** + * 生成工具接口报告 + * @param {Object} tool - 工具对象 + * @returns {Object} 接口报告 + */ + static generateInterfaceReport(tool) { + const validation = this.validateTool(tool); + const report = { + toolName: 'unknown', + valid: validation.valid, + errors: validation.errors, + warnings: validation.warnings, + implementedMethods: { + required: [], + optional: [] + }, + metadata: null, + schema: null + }; + + try { + // 获取工具名称 + if (tool.getMetadata) { + const metadata = tool.getMetadata(); + report.toolName = metadata.name || 'unknown'; + report.metadata = metadata; + } + + // 获取schema + if (tool.getSchema) { + report.schema = tool.getSchema(); + } + + // 检查已实现的方法 + for (const methodSpec of TOOL_INTERFACE.required) { + if (typeof tool[methodSpec.name] === 'function') { + report.implementedMethods.required.push(methodSpec.name); + } + } + + for (const methodSpec of TOOL_INTERFACE.optional) { + if (typeof tool[methodSpec.name] === 'function') { + report.implementedMethods.optional.push(methodSpec.name); + } + } + + } catch (error) { + report.warnings.push(`生成报告时出错: ${error.message}`); + } + + return report; + } +} + +module.exports = ToolValidator; \ No newline at end of file diff --git a/src/lib/tool/index.js b/src/lib/tool/index.js new file mode 100644 index 0000000..176c603 --- /dev/null +++ b/src/lib/tool/index.js @@ -0,0 +1,110 @@ +/** + * PromptX Tool Framework + * 统一的工具框架入口文件 + */ + +const ToolExecutor = require('./ToolExecutor'); +const ToolValidator = require('./ToolValidator'); +const ToolUtils = require('./ToolUtils'); +const { TOOL_INTERFACE, TOOL_ERROR_CODES, TOOL_RESULT_FORMAT, EXAMPLE_TOOL } = require('./ToolInterface'); + +// 创建全局工具实例 +let globalExecutor = null; + +/** + * 获取全局工具执行器 + * @param {Object} options - 配置选项 + * @returns {ToolExecutor} 工具执行器实例 + */ +function getGlobalToolExecutor(options = {}) { + if (!globalExecutor) { + globalExecutor = new ToolExecutor(options); + } + return globalExecutor; +} + +/** + * 初始化工具框架 + * @param {Object} options - 配置选项 + * @returns {Object} 初始化结果 + */ +function initialize(options = {}) { + try { + const executor = getGlobalToolExecutor(options.executor); + + return { + success: true, + message: '工具框架初始化成功', + executor: { + maxConcurrency: executor.options.maxConcurrency, + timeout: executor.options.timeout + } + }; + } catch (error) { + return { + success: false, + message: `工具框架初始化失败: ${error.message}`, + error: error + }; + } +} + +/** + * 执行工具的便捷方法 + * @param {string} toolContent - 工具JavaScript代码内容 + * @param {Object} parameters - 工具参数 + * @param {Object} context - 执行上下文 + * @returns {Promise} 执行结果 + */ +async function executeTool(toolContent, parameters = {}, context = {}) { + const executor = getGlobalToolExecutor(); + return await executor.execute(toolContent, parameters, context); +} + +/** + * 重置工具框架 + */ +function reset() { + if (globalExecutor) { + globalExecutor.cleanup(); + globalExecutor = null; + } +} + +/** + * 获取工具框架统计信息 + * @returns {Object} 统计信息 + */ +function getStats() { + const executorStats = globalExecutor ? globalExecutor.getStats() : {}; + + return { + executor: executorStats, + framework: { + initialized: !!globalExecutor, + version: '1.0.0' + } + }; +} + +module.exports = { + // 核心类 + ToolExecutor, + ToolValidator, + ToolUtils, + + // 接口规范 + TOOL_INTERFACE, + TOOL_ERROR_CODES, + TOOL_RESULT_FORMAT, + EXAMPLE_TOOL, + + // 全局实例获取器 + getGlobalToolExecutor, + + // 便捷方法 + initialize, + executeTool, + reset, + getStats +}; \ No newline at end of file