feat: 实现@tool协议完整功能 - JavaScript工具执行框架
✨ 核心功能 - 新增ToolProtocol处理器,支持@tool://协议解析 - 实现PromptXToolCommand,统一MCP/CLI工具调用 - 完善ToolExecutor,支持工具实例化和参数验证 - 新增calculator和send-email示例工具 🔧 技术改进 - 优化PackageDiscovery统一资源扫描逻辑 - 增强CrossPlatformFileScanner支持.tool.js文件 - 完善ResourceManager集成ToolProtocol - 更新MCP工具定义支持promptx_tool 📋 详细变更 Core: - src/lib/core/resource/protocols/ToolProtocol.js: 新增工具协议处理器 - src/lib/commands/PromptXToolCommand.js: 新增工具命令处理器 - src/lib/tool/ToolExecutor.js: 增强工具执行器兼容性 Discovery: - src/lib/core/resource/discovery/PackageDiscovery.js: 统一资源扫描 - src/lib/core/resource/discovery/CrossPlatformFileScanner.js: 支持tool文件 - src/lib/core/resource/discovery/ProjectDiscovery.js: 增加tool验证 Integration: - src/lib/core/resource/resourceManager.js: 集成ToolProtocol - src/lib/mcp/toolDefinitions.js: 新增promptx_tool定义 - src/lib/commands/MCPServerCommand.js: 支持tool参数转换 - src/bin/promptx.js: 新增tool命令行支持 Tools: - prompt/tool/calculator.tool.js: 数学计算工具示例 - prompt/tool/send-email.tool.js: 邮件发送工具示例 Registry: - src/package.registry.json: 自动生成包含2个tool资源 🧪 测试验证 - ✅ @tool://calculator 数学计算: 25 + 37 = 62 - ✅ @tool://send-email 邮件发送演示版本 - ✅ CLI和MCP双模式支持 - ✅ 完整的错误处理和执行元数据 - ✅ 资源自动发现和注册 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
139
prompt/tool/calculator.tool.js
Normal file
139
prompt/tool/calculator.tool.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* @tool calculator
|
||||||
|
* @description 数学计算工具
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* 获取工具元信息
|
||||||
|
* @returns {Object} 工具元信息
|
||||||
|
*/
|
||||||
|
getMetadata() {
|
||||||
|
return {
|
||||||
|
name: 'calculator',
|
||||||
|
description: '提供基础数学计算功能,支持加减乘除运算',
|
||||||
|
version: '1.0.0',
|
||||||
|
author: 'PromptX Framework',
|
||||||
|
category: 'math',
|
||||||
|
tags: ['calculator', 'math', 'arithmetic']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数Schema
|
||||||
|
* @returns {Object} JSON Schema定义
|
||||||
|
*/
|
||||||
|
getSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
operation: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['add', 'subtract', 'multiply', 'divide'],
|
||||||
|
description: '数学运算类型'
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
type: 'number',
|
||||||
|
description: '第一个操作数'
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
type: 'number',
|
||||||
|
description: '第二个操作数'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['operation', 'a', 'b'],
|
||||||
|
additionalProperties: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证参数
|
||||||
|
* @param {Object} params - 输入参数
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
validate(params) {
|
||||||
|
const { operation, a, b } = params
|
||||||
|
|
||||||
|
// 检查必需参数
|
||||||
|
if (!operation || typeof a !== 'number' || typeof b !== 'number') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查操作类型
|
||||||
|
const validOperations = ['add', 'subtract', 'multiply', 'divide']
|
||||||
|
if (!validOperations.includes(operation)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查除零
|
||||||
|
if (operation === 'divide' && b === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行计算
|
||||||
|
* @param {Object} params - 计算参数
|
||||||
|
* @param {string} params.operation - 运算类型 ('add', 'subtract', 'multiply', 'divide')
|
||||||
|
* @param {number} params.a - 第一个操作数
|
||||||
|
* @param {number} params.b - 第二个操作数
|
||||||
|
* @returns {Object} 计算结果
|
||||||
|
*/
|
||||||
|
async execute(params) {
|
||||||
|
const { operation, a, b } = params
|
||||||
|
|
||||||
|
let result
|
||||||
|
let expression
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case 'add':
|
||||||
|
result = a + b
|
||||||
|
expression = `${a} + ${b}`
|
||||||
|
break
|
||||||
|
case 'subtract':
|
||||||
|
result = a - b
|
||||||
|
expression = `${a} - ${b}`
|
||||||
|
break
|
||||||
|
case 'multiply':
|
||||||
|
result = a * b
|
||||||
|
expression = `${a} × ${b}`
|
||||||
|
break
|
||||||
|
case 'divide':
|
||||||
|
if (b === 0) {
|
||||||
|
throw new Error('Division by zero is not allowed')
|
||||||
|
}
|
||||||
|
result = a / b
|
||||||
|
expression = `${a} ÷ ${b}`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown operation: ${operation}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
expression,
|
||||||
|
result,
|
||||||
|
operation,
|
||||||
|
operands: { a, b },
|
||||||
|
formatted: `${expression} = ${result}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具初始化(可选)
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
// 可以在这里进行工具初始化工作
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具清理(可选)
|
||||||
|
*/
|
||||||
|
async cleanup() {
|
||||||
|
// 可以在这里进行清理工作
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
202
prompt/tool/send-email.tool.js
Normal file
202
prompt/tool/send-email.tool.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
* @tool send-email
|
||||||
|
* @description 邮件发送工具(演示版)
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* 获取工具元信息
|
||||||
|
* @returns {Object} 工具元信息
|
||||||
|
*/
|
||||||
|
getMetadata() {
|
||||||
|
return {
|
||||||
|
name: 'send-email',
|
||||||
|
description: '邮件发送工具,支持发送格式化邮件(演示版本)',
|
||||||
|
version: '1.0.0',
|
||||||
|
author: 'PromptX Framework',
|
||||||
|
category: 'communication',
|
||||||
|
tags: ['email', 'communication', 'notification'],
|
||||||
|
demo: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数Schema
|
||||||
|
* @returns {Object} JSON Schema定义
|
||||||
|
*/
|
||||||
|
getSchema() {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
to: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'email',
|
||||||
|
description: '收件人邮箱地址'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
type: 'string',
|
||||||
|
minLength: 1,
|
||||||
|
description: '邮件主题'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: 'string',
|
||||||
|
minLength: 1,
|
||||||
|
description: '邮件内容'
|
||||||
|
},
|
||||||
|
cc: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'email'
|
||||||
|
},
|
||||||
|
description: '抄送邮箱列表(可选)'
|
||||||
|
},
|
||||||
|
priority: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['low', 'normal', 'high'],
|
||||||
|
default: 'normal',
|
||||||
|
description: '邮件优先级'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['to', 'subject', 'content'],
|
||||||
|
additionalProperties: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证参数
|
||||||
|
* @param {Object} params - 输入参数
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
validate(params) {
|
||||||
|
const { to, subject, content } = params
|
||||||
|
|
||||||
|
// 检查必需参数
|
||||||
|
if (!to || !subject || !content) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的邮箱格式验证
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
if (!emailRegex.test(to)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查抄送邮箱格式(如果存在)
|
||||||
|
if (params.cc && Array.isArray(params.cc)) {
|
||||||
|
for (const ccEmail of params.cc) {
|
||||||
|
if (!emailRegex.test(ccEmail)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件(演示版本)
|
||||||
|
* @param {Object} params - 邮件参数
|
||||||
|
* @param {string} params.to - 收件人邮箱
|
||||||
|
* @param {string} params.subject - 邮件主题
|
||||||
|
* @param {string} params.content - 邮件内容
|
||||||
|
* @param {string[]} params.cc - 抄送邮箱列表(可选)
|
||||||
|
* @param {string} params.priority - 邮件优先级(可选)
|
||||||
|
* @returns {Object} 发送结果
|
||||||
|
*/
|
||||||
|
async execute(params) {
|
||||||
|
const { to, subject, content, cc = [], priority = 'normal' } = params
|
||||||
|
|
||||||
|
// 生成邮件ID(演示用)
|
||||||
|
const emailId = `email_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
|
||||||
|
// 格式化邮件内容
|
||||||
|
const formattedEmail = this.formatEmail({
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
content,
|
||||||
|
cc,
|
||||||
|
priority,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟发送延迟
|
||||||
|
await this.simulateDelay(500)
|
||||||
|
|
||||||
|
// 演示版本:不实际发送邮件,只返回格式化结果
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
email_id: emailId,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
content_preview: content.slice(0, 100) + (content.length > 100 ? '...' : ''),
|
||||||
|
cc_count: cc.length,
|
||||||
|
priority,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
formatted_email: formattedEmail,
|
||||||
|
demo_note: '这是演示版本,邮件未实际发送',
|
||||||
|
status: 'demo_sent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化邮件内容
|
||||||
|
* @param {Object} emailData - 邮件数据
|
||||||
|
* @returns {string} 格式化的邮件
|
||||||
|
*/
|
||||||
|
formatEmail(emailData) {
|
||||||
|
const { to, subject, content, cc, priority, timestamp } = emailData
|
||||||
|
|
||||||
|
let formatted = `收件人: ${to}\n`
|
||||||
|
|
||||||
|
if (cc.length > 0) {
|
||||||
|
formatted += `抄送: ${cc.join(', ')}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted += `主题: ${subject}\n`
|
||||||
|
formatted += `优先级: ${this.getPriorityText(priority)}\n`
|
||||||
|
formatted += `时间: ${new Date(timestamp).toLocaleString('zh-CN')}\n`
|
||||||
|
formatted += `\n内容:\n${content}\n`
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取优先级文本
|
||||||
|
* @param {string} priority - 优先级
|
||||||
|
* @returns {string} 优先级文本
|
||||||
|
*/
|
||||||
|
getPriorityText(priority) {
|
||||||
|
const priorityMap = {
|
||||||
|
low: '低',
|
||||||
|
normal: '普通',
|
||||||
|
high: '高'
|
||||||
|
}
|
||||||
|
return priorityMap[priority] || '普通'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟网络延迟
|
||||||
|
* @param {number} ms - 延迟毫秒数
|
||||||
|
*/
|
||||||
|
async simulateDelay(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具初始化
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
// 在实际版本中,这里可以初始化邮件服务连接
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具清理
|
||||||
|
*/
|
||||||
|
async cleanup() {
|
||||||
|
// 在实际版本中,这里可以关闭邮件服务连接
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -96,6 +96,44 @@ program
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Tool命令
|
||||||
|
program
|
||||||
|
.command('tool <arguments>')
|
||||||
|
.description('🔧 tool锦囊 - 执行通过@tool协议声明的JavaScript工具')
|
||||||
|
.action(async (argumentsJson, options) => {
|
||||||
|
try {
|
||||||
|
let args = {};
|
||||||
|
|
||||||
|
// 支持两种调用方式:
|
||||||
|
// 1. 从MCP传来的对象(通过cli.execute调用)
|
||||||
|
// 2. 从CLI传来的JSON字符串(直接命令行调用)
|
||||||
|
if (typeof argumentsJson === 'object') {
|
||||||
|
args = argumentsJson;
|
||||||
|
} else if (typeof argumentsJson === 'string') {
|
||||||
|
try {
|
||||||
|
args = JSON.parse(argumentsJson);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 参数解析错误,请提供有效的JSON格式');
|
||||||
|
console.error('格式示例: \'{"tool_resource": "@tool://calculator", "parameters": {"operation": "add", "a": 25, "b": 37}}\'');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证必需参数
|
||||||
|
if (!args.tool_resource || !args.parameters) {
|
||||||
|
console.error('❌ 缺少必需参数');
|
||||||
|
console.error('必需参数: tool_resource (工具资源引用), parameters (工具参数)');
|
||||||
|
console.error('格式示例: \'{"tool_resource": "@tool://calculator", "parameters": {"operation": "add", "a": 25, "b": 37}}\'');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await cli.execute('tool', args);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Tool命令执行失败: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// MCP Server命令
|
// MCP Server命令
|
||||||
program
|
program
|
||||||
.command('mcp-server')
|
.command('mcp-server')
|
||||||
|
|||||||
@ -491,7 +491,9 @@ class MCPServerCommand {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
'promptx_dacp': (args) => [args]
|
'promptx_dacp': (args) => [args],
|
||||||
|
|
||||||
|
'promptx_tool': (args) => [args]
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapper = paramMapping[toolName];
|
const mapper = paramMapping[toolName];
|
||||||
|
|||||||
271
src/lib/commands/PromptXToolCommand.js
Normal file
271
src/lib/commands/PromptXToolCommand.js
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
const BasePouchCommand = require('../core/pouch/BasePouchCommand')
|
||||||
|
const { getGlobalResourceManager } = require('../core/resource')
|
||||||
|
const ToolExecutor = require('../tool/ToolExecutor')
|
||||||
|
const logger = require('../utils/logger')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PromptX Tool命令处理器
|
||||||
|
* 实现promptx_tool MCP工具,执行通过@tool协议声明的工具
|
||||||
|
*/
|
||||||
|
class PromptXToolCommand extends BasePouchCommand {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.toolExecutor = new ToolExecutor()
|
||||||
|
this.resourceManager = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或初始化ResourceManager
|
||||||
|
*/
|
||||||
|
async getResourceManager() {
|
||||||
|
if (!this.resourceManager) {
|
||||||
|
this.resourceManager = getGlobalResourceManager()
|
||||||
|
// 确保ResourceManager已初始化
|
||||||
|
if (!this.resourceManager.initialized) {
|
||||||
|
await this.resourceManager.initializeWithNewArchitecture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.resourceManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePouchCommand的抽象方法实现
|
||||||
|
getPurpose() {
|
||||||
|
return '执行通过@tool协议声明的JavaScript工具'
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContent(args) {
|
||||||
|
try {
|
||||||
|
// 处理参数:如果是数组,取第一个元素;否则直接使用
|
||||||
|
const toolArgs = Array.isArray(args) ? args[0] : args
|
||||||
|
|
||||||
|
// 执行工具调用
|
||||||
|
const result = await this.executeToolInternal(toolArgs)
|
||||||
|
|
||||||
|
// 格式化响应
|
||||||
|
if (result.success) {
|
||||||
|
return `🔧 Tool执行成功
|
||||||
|
|
||||||
|
📋 工具资源: ${result.tool_resource}
|
||||||
|
📊 执行结果:
|
||||||
|
${JSON.stringify(result.result, null, 2)}
|
||||||
|
|
||||||
|
⏱️ 性能指标:
|
||||||
|
- 执行时间: ${result.metadata.execution_time_ms}ms
|
||||||
|
- 时间戳: ${result.metadata.timestamp}
|
||||||
|
- 版本: ${result.metadata.version}`
|
||||||
|
} else {
|
||||||
|
return `❌ Tool执行失败
|
||||||
|
|
||||||
|
📋 工具资源: ${result.tool_resource}
|
||||||
|
❌ 错误信息: ${result.error.message}
|
||||||
|
🏷️ 错误类型: ${result.error.type}
|
||||||
|
🔢 错误代码: ${result.error.code}
|
||||||
|
|
||||||
|
⏱️ 执行时间: ${result.metadata.execution_time_ms}ms`
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return `❌ Tool执行异常
|
||||||
|
|
||||||
|
错误详情: ${error.message}
|
||||||
|
|
||||||
|
💡 请检查:
|
||||||
|
1. 工具资源引用格式是否正确 (@tool://tool-name)
|
||||||
|
2. 工具参数是否有效
|
||||||
|
3. 工具文件是否存在并可执行`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPATEOAS(args) {
|
||||||
|
return {
|
||||||
|
currentState: 'tool_executed',
|
||||||
|
nextActions: [
|
||||||
|
{
|
||||||
|
action: 'execute_another_tool',
|
||||||
|
description: '执行其他工具',
|
||||||
|
method: 'promptx tool'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'view_available_tools',
|
||||||
|
description: '查看可用工具',
|
||||||
|
method: 'promptx welcome'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部工具执行方法
|
||||||
|
* @param {Object} args - 命令参数
|
||||||
|
* @param {string} args.tool_resource - 工具资源引用,格式:@tool://tool-name
|
||||||
|
* @param {Object} args.parameters - 传递给工具的参数
|
||||||
|
* @param {Object} args.context - 执行上下文信息(可选)
|
||||||
|
* @returns {Promise<Object>} 执行结果
|
||||||
|
*/
|
||||||
|
async executeToolInternal(args) {
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 参数验证
|
||||||
|
this.validateArguments(args)
|
||||||
|
|
||||||
|
const { tool_resource, parameters, context = {} } = args
|
||||||
|
|
||||||
|
logger.debug(`[PromptXTool] 开始执行工具: ${tool_resource}`)
|
||||||
|
|
||||||
|
// 2. 通过ResourceManager解析工具资源
|
||||||
|
const resourceManager = await this.getResourceManager()
|
||||||
|
const toolInfo = await resourceManager.loadResource(tool_resource)
|
||||||
|
|
||||||
|
// 3. 准备工具执行上下文
|
||||||
|
const executionContext = {
|
||||||
|
...context,
|
||||||
|
tool_resource,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
execution_id: this.generateExecutionId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用ToolExecutor执行工具
|
||||||
|
const result = await this.toolExecutor.execute(
|
||||||
|
toolInfo.content,
|
||||||
|
parameters,
|
||||||
|
executionContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// 5. 格式化成功结果
|
||||||
|
return this.formatSuccessResult(result, tool_resource, startTime)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// 6. 格式化错误结果
|
||||||
|
logger.error(`[PromptXTool] 工具执行失败: ${error.message}`, error)
|
||||||
|
return this.formatErrorResult(error, args.tool_resource, startTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证命令参数
|
||||||
|
* @param {Object} args - 命令参数
|
||||||
|
*/
|
||||||
|
validateArguments(args) {
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('Missing arguments')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.tool_resource) {
|
||||||
|
throw new Error('Missing required parameter: tool_resource')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.tool_resource.startsWith('@tool://')) {
|
||||||
|
throw new Error('Invalid tool_resource format. Must start with @tool://')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.parameters || typeof args.parameters !== 'object') {
|
||||||
|
throw new Error('Missing or invalid parameters. Must be an object')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化成功结果
|
||||||
|
* @param {*} result - 工具执行结果
|
||||||
|
* @param {string} toolResource - 工具资源引用
|
||||||
|
* @param {number} startTime - 开始时间
|
||||||
|
* @returns {Object} 格式化的成功结果
|
||||||
|
*/
|
||||||
|
formatSuccessResult(result, toolResource, startTime) {
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
tool_resource: toolResource,
|
||||||
|
result: result,
|
||||||
|
metadata: {
|
||||||
|
execution_time_ms: duration,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化错误结果
|
||||||
|
* @param {Error} error - 错误对象
|
||||||
|
* @param {string} toolResource - 工具资源引用(可能为空)
|
||||||
|
* @param {number} startTime - 开始时间
|
||||||
|
* @returns {Object} 格式化的错误结果
|
||||||
|
*/
|
||||||
|
formatErrorResult(error, toolResource, startTime) {
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
tool_resource: toolResource || 'unknown',
|
||||||
|
error: {
|
||||||
|
type: error.constructor.name,
|
||||||
|
message: error.message,
|
||||||
|
code: this.getErrorCode(error)
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
execution_time_ms: duration,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据错误类型获取错误代码
|
||||||
|
* @param {Error} error - 错误对象
|
||||||
|
* @returns {string} 错误代码
|
||||||
|
*/
|
||||||
|
getErrorCode(error) {
|
||||||
|
if (error.message.includes('not found')) {
|
||||||
|
return 'TOOL_NOT_FOUND'
|
||||||
|
}
|
||||||
|
if (error.message.includes('Invalid tool_resource format')) {
|
||||||
|
return 'INVALID_TOOL_RESOURCE'
|
||||||
|
}
|
||||||
|
if (error.message.includes('Missing')) {
|
||||||
|
return 'MISSING_PARAMETER'
|
||||||
|
}
|
||||||
|
if (error.message.includes('syntax')) {
|
||||||
|
return 'TOOL_SYNTAX_ERROR'
|
||||||
|
}
|
||||||
|
if (error.message.includes('timeout')) {
|
||||||
|
return 'EXECUTION_TIMEOUT'
|
||||||
|
}
|
||||||
|
return 'UNKNOWN_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成执行ID
|
||||||
|
* @returns {string} 唯一的执行ID
|
||||||
|
*/
|
||||||
|
generateExecutionId() {
|
||||||
|
return `tool_exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工具命令的元信息
|
||||||
|
* @returns {Object} 命令元信息
|
||||||
|
*/
|
||||||
|
getMetadata() {
|
||||||
|
return {
|
||||||
|
name: 'promptx_tool',
|
||||||
|
description: '执行通过@tool协议声明的工具',
|
||||||
|
version: '1.0.0',
|
||||||
|
author: 'PromptX Framework',
|
||||||
|
supports: {
|
||||||
|
protocols: ['@tool://'],
|
||||||
|
formats: ['.tool.js'],
|
||||||
|
features: [
|
||||||
|
'JavaScript工具执行',
|
||||||
|
'参数验证',
|
||||||
|
'错误处理',
|
||||||
|
'执行监控',
|
||||||
|
'上下文传递'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PromptXToolCommand
|
||||||
@ -31,7 +31,8 @@ class PouchCLI {
|
|||||||
learn: commands.LearnCommand,
|
learn: commands.LearnCommand,
|
||||||
recall: commands.RecallCommand,
|
recall: commands.RecallCommand,
|
||||||
remember: commands.RememberCommand,
|
remember: commands.RememberCommand,
|
||||||
dacp: commands.DACPCommand
|
dacp: commands.DACPCommand,
|
||||||
|
tool: commands.PromptXToolCommand
|
||||||
})
|
})
|
||||||
|
|
||||||
// 将命令注册到状态机
|
// 将命令注册到状态机
|
||||||
|
|||||||
@ -9,6 +9,7 @@ const LearnCommand = require('./LearnCommand')
|
|||||||
const RecallCommand = require('./RecallCommand')
|
const RecallCommand = require('./RecallCommand')
|
||||||
const RememberCommand = require('./RememberCommand')
|
const RememberCommand = require('./RememberCommand')
|
||||||
const DACPCommand = require('./DACPCommand')
|
const DACPCommand = require('./DACPCommand')
|
||||||
|
const PromptXToolCommand = require('../../../commands/PromptXToolCommand')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
InitCommand,
|
InitCommand,
|
||||||
@ -17,5 +18,6 @@ module.exports = {
|
|||||||
LearnCommand,
|
LearnCommand,
|
||||||
RecallCommand,
|
RecallCommand,
|
||||||
RememberCommand,
|
RememberCommand,
|
||||||
DACPCommand
|
DACPCommand,
|
||||||
|
PromptXToolCommand
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,10 @@ class CrossPlatformFileScanner {
|
|||||||
knowledge: {
|
knowledge: {
|
||||||
extensions: ['.knowledge.md'],
|
extensions: ['.knowledge.md'],
|
||||||
subdirs: null // 不限制子目录,在所有地方查找knowledge文件
|
subdirs: null // 不限制子目录,在所有地方查找knowledge文件
|
||||||
|
},
|
||||||
|
tool: {
|
||||||
|
extensions: ['.tool.js'],
|
||||||
|
subdirs: null // 不限制子目录,在所有地方查找tool文件
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -193,16 +193,15 @@ class PackageDiscovery extends BaseDiscovery {
|
|||||||
*/
|
*/
|
||||||
async _scanDirectory(promptDir, registryData) {
|
async _scanDirectory(promptDir, registryData) {
|
||||||
try {
|
try {
|
||||||
// 扫描domain目录下的角色
|
// 统一扫描:扫描prompt下所有目录的所有资源类型文件
|
||||||
const domainDir = path.join(promptDir, 'domain')
|
const resourceTypes = ['role', 'execution', 'thought', 'knowledge', 'tool']
|
||||||
if (await fs.pathExists(domainDir)) {
|
|
||||||
await this._scanDomainDirectory(domainDir, registryData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扫描core目录下的资源
|
for (const resourceType of resourceTypes) {
|
||||||
const coreDir = path.join(promptDir, 'core')
|
const files = await this.fileScanner.scanResourceFiles(promptDir, resourceType)
|
||||||
if (await fs.pathExists(coreDir)) {
|
|
||||||
await this._scanCoreDirectory(coreDir, registryData)
|
for (const filePath of files) {
|
||||||
|
await this._processResourceFile(filePath, resourceType, registryData, promptDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -210,6 +209,88 @@ class PackageDiscovery extends BaseDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个资源文件
|
||||||
|
* @param {string} filePath - 文件路径
|
||||||
|
* @param {string} resourceType - 资源类型
|
||||||
|
* @param {RegistryData} registryData - 注册表数据
|
||||||
|
* @param {string} promptDir - prompt目录路径
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _processResourceFile(filePath, resourceType, registryData, promptDir) {
|
||||||
|
try {
|
||||||
|
// 提取资源ID
|
||||||
|
const fileName = path.basename(filePath)
|
||||||
|
let resourceId
|
||||||
|
|
||||||
|
if (resourceType === 'tool') {
|
||||||
|
// tool文件:calculator.tool.js -> calculator
|
||||||
|
resourceId = fileName.replace('.tool.js', '')
|
||||||
|
} else {
|
||||||
|
// 其他文件:assistant.role.md -> assistant
|
||||||
|
resourceId = fileName.replace(`.${resourceType}.md`, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成引用路径
|
||||||
|
const relativePath = path.relative(path.dirname(promptDir), filePath)
|
||||||
|
const reference = `@package://${relativePath.replace(/\\/g, '/')}`
|
||||||
|
|
||||||
|
// 创建资源数据
|
||||||
|
const resourceData = new ResourceData({
|
||||||
|
id: resourceId,
|
||||||
|
source: 'package',
|
||||||
|
protocol: resourceType,
|
||||||
|
name: ResourceData._generateDefaultName(resourceId, resourceType),
|
||||||
|
description: ResourceData._generateDefaultDescription(resourceId, resourceType),
|
||||||
|
reference: reference,
|
||||||
|
metadata: {
|
||||||
|
scannedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 对tool文件进行语法验证
|
||||||
|
if (resourceType === 'tool') {
|
||||||
|
if (await this._validateToolFile(filePath)) {
|
||||||
|
registryData.addResource(resourceData)
|
||||||
|
} else {
|
||||||
|
logger.warn(`[PackageDiscovery] Tool文件验证失败,跳过: ${filePath}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registryData.addResource(resourceData)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[PackageDiscovery] 处理资源文件失败: ${filePath} - ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Tool文件格式
|
||||||
|
* @param {string} filePath - Tool文件路径
|
||||||
|
* @returns {Promise<boolean>} 是否有效
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _validateToolFile(filePath) {
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(filePath, 'utf8')
|
||||||
|
|
||||||
|
// 检查JavaScript语法
|
||||||
|
new Function(content)
|
||||||
|
|
||||||
|
// 检查必需的exports
|
||||||
|
if (!content.includes('module.exports')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查必需的方法
|
||||||
|
const requiredMethods = ['getMetadata', 'execute']
|
||||||
|
return requiredMethods.some(method => content.includes(method))
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描domain目录(角色资源)
|
* 扫描domain目录(角色资源)
|
||||||
* @param {string} domainDir - domain目录路径
|
* @param {string} domainDir - domain目录路径
|
||||||
@ -431,7 +512,7 @@ class PackageDiscovery extends BaseDiscovery {
|
|||||||
const resources = []
|
const resources = []
|
||||||
|
|
||||||
// 定义要扫描的资源类型
|
// 定义要扫描的资源类型
|
||||||
const resourceTypes = ['role', 'execution', 'thought', 'knowledge']
|
const resourceTypes = ['role', 'execution', 'thought', 'knowledge', 'tool']
|
||||||
|
|
||||||
// 并行扫描所有资源类型
|
// 并行扫描所有资源类型
|
||||||
for (const resourceType of resourceTypes) {
|
for (const resourceType of resourceTypes) {
|
||||||
|
|||||||
@ -160,7 +160,7 @@ class ProjectDiscovery extends BaseDiscovery {
|
|||||||
const resources = []
|
const resources = []
|
||||||
|
|
||||||
// 定义要扫描的资源类型
|
// 定义要扫描的资源类型
|
||||||
const resourceTypes = ['role', 'execution', 'thought', 'knowledge']
|
const resourceTypes = ['role', 'execution', 'thought', 'knowledge', 'tool']
|
||||||
|
|
||||||
// 并行扫描所有资源类型
|
// 并行扫描所有资源类型
|
||||||
for (const resourceType of resourceTypes) {
|
for (const resourceType of resourceTypes) {
|
||||||
@ -254,6 +254,9 @@ class ProjectDiscovery extends BaseDiscovery {
|
|||||||
// knowledge类型比较灵活,只要文件有内容就认为是有效的
|
// knowledge类型比较灵活,只要文件有内容就认为是有效的
|
||||||
// 可以是纯文本、链接、图片等任何形式的知识内容
|
// 可以是纯文本、链接、图片等任何形式的知识内容
|
||||||
return true
|
return true
|
||||||
|
case 'tool':
|
||||||
|
// tool类型必须是有效的JavaScript代码
|
||||||
|
return this._validateToolFile(content)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -263,6 +266,34 @@ class ProjectDiscovery extends BaseDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Tool文件是否为有效的JavaScript代码
|
||||||
|
* @param {string} content - 文件内容
|
||||||
|
* @returns {boolean} 是否为有效的Tool文件
|
||||||
|
*/
|
||||||
|
_validateToolFile(content) {
|
||||||
|
try {
|
||||||
|
// 1. 基本的JavaScript语法检查
|
||||||
|
new Function(content);
|
||||||
|
|
||||||
|
// 2. 检查是否包含module.exports(CommonJS格式)
|
||||||
|
if (!content.includes('module.exports')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查是否包含工具必需的方法(getMetadata, execute等)
|
||||||
|
const requiredMethods = ['getMetadata', 'execute'];
|
||||||
|
const hasRequiredMethods = requiredMethods.some(method =>
|
||||||
|
content.includes(method)
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasRequiredMethods;
|
||||||
|
} catch (syntaxError) {
|
||||||
|
// JavaScript语法错误
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成项目引用路径
|
* 生成项目引用路径
|
||||||
* @param {string} filePath - 文件绝对路径
|
* @param {string} filePath - 文件绝对路径
|
||||||
|
|||||||
116
src/lib/core/resource/protocols/ToolProtocol.js
Normal file
116
src/lib/core/resource/protocols/ToolProtocol.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
const ResourceProtocol = require('./ResourceProtocol');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool协议处理器
|
||||||
|
* 处理 @tool://tool-name 格式的资源引用
|
||||||
|
* 从注册表中查找并加载工具JavaScript代码
|
||||||
|
*/
|
||||||
|
class ToolProtocol extends ResourceProtocol {
|
||||||
|
constructor() {
|
||||||
|
super('tool');
|
||||||
|
this.registryManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置注册表管理器引用
|
||||||
|
* @param {Object} manager - ResourceManager实例
|
||||||
|
*/
|
||||||
|
setRegistryManager(manager) {
|
||||||
|
this.registryManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析工具资源路径
|
||||||
|
* @param {string} toolPath - 工具名称,如 "calculator"
|
||||||
|
* @param {Object} queryParams - 查询参数(可选)
|
||||||
|
* @returns {Promise<Object>} 工具代码和元数据
|
||||||
|
*/
|
||||||
|
async resolve(toolPath, queryParams = {}) {
|
||||||
|
if (!this.registryManager) {
|
||||||
|
throw new Error('ToolProtocol: Registry manager not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 从注册表查找tool资源
|
||||||
|
const toolResource = this.registryManager.registryData
|
||||||
|
.findResourceById(toolPath, 'tool');
|
||||||
|
|
||||||
|
if (!toolResource) {
|
||||||
|
throw new Error(`Tool '${toolPath}' not found in registry`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 加载tool文件内容
|
||||||
|
const toolContent = await this.registryManager
|
||||||
|
.loadResourceByProtocol(toolResource.reference);
|
||||||
|
|
||||||
|
// 3. 验证工具代码格式
|
||||||
|
this.validateToolContent(toolContent, toolPath);
|
||||||
|
|
||||||
|
// 4. 返回工具信息
|
||||||
|
return {
|
||||||
|
id: toolPath,
|
||||||
|
content: toolContent,
|
||||||
|
metadata: toolResource,
|
||||||
|
source: toolResource.source || 'unknown'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证工具内容格式
|
||||||
|
* @param {string} content - 工具文件内容
|
||||||
|
* @param {string} toolPath - 工具路径
|
||||||
|
*/
|
||||||
|
validateToolContent(content, toolPath) {
|
||||||
|
if (!content || typeof content !== 'string') {
|
||||||
|
throw new Error(`Tool '${toolPath}': Invalid or empty content`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基本的JavaScript语法检查
|
||||||
|
try {
|
||||||
|
// 尝试创建一个函数来验证语法
|
||||||
|
new Function(content);
|
||||||
|
} catch (syntaxError) {
|
||||||
|
throw new Error(`Tool '${toolPath}': JavaScript syntax error - ${syntaxError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取协议信息
|
||||||
|
* @returns {Object} 协议描述信息
|
||||||
|
*/
|
||||||
|
getProtocolInfo() {
|
||||||
|
return {
|
||||||
|
name: 'tool',
|
||||||
|
description: 'Tool资源协议 - 加载可执行的JavaScript工具',
|
||||||
|
syntax: 'tool://{tool_id}',
|
||||||
|
examples: [
|
||||||
|
'tool://calculator',
|
||||||
|
'tool://send-email',
|
||||||
|
'tool://data-processor',
|
||||||
|
'tool://api-client'
|
||||||
|
],
|
||||||
|
supportedFileTypes: ['.tool.js'],
|
||||||
|
usageNote: '工具文件必须导出符合PromptX Tool Interface的对象'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查缓存策略
|
||||||
|
* @param {string} toolPath - 工具路径
|
||||||
|
* @returns {boolean} 是否应该缓存
|
||||||
|
*/
|
||||||
|
shouldCache(toolPath) {
|
||||||
|
// 工具代码通常比较稳定,启用缓存以提高性能
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存键
|
||||||
|
* @param {string} toolPath - 工具路径
|
||||||
|
* @returns {string} 缓存键
|
||||||
|
*/
|
||||||
|
getCacheKey(toolPath) {
|
||||||
|
return `tool://${toolPath}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ToolProtocol;
|
||||||
@ -11,6 +11,7 @@ const RoleProtocol = require('./protocols/RoleProtocol')
|
|||||||
const ThoughtProtocol = require('./protocols/ThoughtProtocol')
|
const ThoughtProtocol = require('./protocols/ThoughtProtocol')
|
||||||
const ExecutionProtocol = require('./protocols/ExecutionProtocol')
|
const ExecutionProtocol = require('./protocols/ExecutionProtocol')
|
||||||
const KnowledgeProtocol = require('./protocols/KnowledgeProtocol')
|
const KnowledgeProtocol = require('./protocols/KnowledgeProtocol')
|
||||||
|
const ToolProtocol = require('./protocols/ToolProtocol')
|
||||||
const UserProtocol = require('./protocols/UserProtocol')
|
const UserProtocol = require('./protocols/UserProtocol')
|
||||||
const FileProtocol = require('./protocols/FileProtocol')
|
const FileProtocol = require('./protocols/FileProtocol')
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ class ResourceManager {
|
|||||||
this.protocols.set('thought', new ThoughtProtocol())
|
this.protocols.set('thought', new ThoughtProtocol())
|
||||||
this.protocols.set('execution', new ExecutionProtocol())
|
this.protocols.set('execution', new ExecutionProtocol())
|
||||||
this.protocols.set('knowledge', new KnowledgeProtocol())
|
this.protocols.set('knowledge', new KnowledgeProtocol())
|
||||||
|
this.protocols.set('tool', new ToolProtocol())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,6 +112,7 @@ class ResourceManager {
|
|||||||
const executionProtocol = this.protocols.get('execution')
|
const executionProtocol = this.protocols.get('execution')
|
||||||
const thoughtProtocol = this.protocols.get('thought')
|
const thoughtProtocol = this.protocols.get('thought')
|
||||||
const knowledgeProtocol = this.protocols.get('knowledge')
|
const knowledgeProtocol = this.protocols.get('knowledge')
|
||||||
|
const toolProtocol = this.protocols.get('tool')
|
||||||
|
|
||||||
if (roleProtocol) {
|
if (roleProtocol) {
|
||||||
roleProtocol.setRegistryManager(this)
|
roleProtocol.setRegistryManager(this)
|
||||||
@ -123,6 +126,9 @@ class ResourceManager {
|
|||||||
if (knowledgeProtocol) {
|
if (knowledgeProtocol) {
|
||||||
knowledgeProtocol.setRegistryManager(this)
|
knowledgeProtocol.setRegistryManager(this)
|
||||||
}
|
}
|
||||||
|
if (toolProtocol) {
|
||||||
|
toolProtocol.setRegistryManager(this)
|
||||||
|
}
|
||||||
|
|
||||||
// 逻辑协议设置完成,不输出日志避免干扰用户界面
|
// 逻辑协议设置完成,不输出日志避免干扰用户界面
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,6 +150,50 @@ const TOOL_DEFINITIONS = [
|
|||||||
context: z.object({}).optional().describe('上下文信息')
|
context: z.object({}).optional().describe('上下文信息')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'promptx_tool',
|
||||||
|
description: '🔧 [工具执行器] 执行通过@tool协议声明的JavaScript工具 - 支持角色配置中定义的专业工具能力,如@tool://calculator数学计算、@tool://send-email邮件发送等。提供安全沙箱执行、参数验证、错误处理和性能监控。',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
tool_resource: {
|
||||||
|
type: 'string',
|
||||||
|
description: '工具资源引用,格式:@tool://tool-name,如@tool://calculator',
|
||||||
|
pattern: '^@tool://.+'
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
description: '传递给工具的参数对象'
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
type: 'object',
|
||||||
|
description: '执行上下文信息(可选)',
|
||||||
|
properties: {
|
||||||
|
role_id: {
|
||||||
|
type: 'string',
|
||||||
|
description: '当前激活的角色ID'
|
||||||
|
},
|
||||||
|
session_id: {
|
||||||
|
type: 'string',
|
||||||
|
description: '会话ID'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['tool_resource', 'parameters']
|
||||||
|
},
|
||||||
|
zodSchema: z.object({
|
||||||
|
tool_resource: z.string()
|
||||||
|
.regex(/^@tool:\/\/.+/, '工具资源必须以@tool://开头')
|
||||||
|
.describe('工具资源引用,格式:@tool://tool-name'),
|
||||||
|
parameters: z.object({}).passthrough()
|
||||||
|
.describe('传递给工具的参数对象'),
|
||||||
|
context: z.object({
|
||||||
|
role_id: z.string().optional().describe('当前激活的角色ID'),
|
||||||
|
session_id: z.string().optional().describe('会话ID')
|
||||||
|
}).optional().describe('执行上下文信息')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -95,15 +95,29 @@ class ToolExecutor {
|
|||||||
|
|
||||||
script.runInContext(context);
|
script.runInContext(context);
|
||||||
|
|
||||||
// 获取导出的工具类
|
// 获取导出的工具
|
||||||
const ToolClass = context.module.exports;
|
const exported = context.module.exports;
|
||||||
|
|
||||||
if (!ToolClass || typeof ToolClass !== 'function') {
|
if (!exported) {
|
||||||
throw new Error(`工具未正确导出类: ${toolName}`);
|
throw new Error(`工具未正确导出: ${toolName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建工具实例
|
// 支持两种导出方式:
|
||||||
return new ToolClass();
|
// 1. 导出类(构造函数)- 需要实例化
|
||||||
|
// 2. 导出对象 - 直接使用
|
||||||
|
let toolInstance;
|
||||||
|
|
||||||
|
if (typeof exported === 'function') {
|
||||||
|
// 导出的是类,需要实例化
|
||||||
|
toolInstance = new exported();
|
||||||
|
} else if (typeof exported === 'object') {
|
||||||
|
// 导出的是对象,直接使用
|
||||||
|
toolInstance = exported;
|
||||||
|
} else {
|
||||||
|
throw new Error(`工具导出格式不正确,必须是类或对象: ${toolName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolInstance;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`工具代码执行失败 ${toolName}: ${error.message}`);
|
throw new Error(`工具代码执行失败 ${toolName}: ${error.message}`);
|
||||||
@ -185,7 +199,18 @@ class ToolExecutor {
|
|||||||
validateParameters(tool, parameters) {
|
validateParameters(tool, parameters) {
|
||||||
// 如果工具有自定义validate方法,使用它
|
// 如果工具有自定义validate方法,使用它
|
||||||
if (typeof tool.validate === 'function') {
|
if (typeof tool.validate === 'function') {
|
||||||
return tool.validate(parameters);
|
const result = tool.validate(parameters);
|
||||||
|
|
||||||
|
// 支持两种返回格式:
|
||||||
|
// 1. boolean - 转换为标准格式
|
||||||
|
// 2. {valid: boolean, errors?: array} - 标准格式
|
||||||
|
if (typeof result === 'boolean') {
|
||||||
|
return { valid: result, errors: result ? [] : ['Validation failed'] };
|
||||||
|
} else if (result && typeof result === 'object') {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return { valid: false, errors: ['Invalid validation result'] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 否则使用默认验证
|
// 否则使用默认验证
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
31
test-tool-validation.js
Normal file
31
test-tool-validation.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 测试工具文件验证
|
||||||
|
function testToolValidation() {
|
||||||
|
const toolPath = path.join(process.cwd(), 'prompt/tool/calculator.tool.js');
|
||||||
|
console.log(`🔍 Testing tool validation for: ${toolPath}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(toolPath, 'utf8');
|
||||||
|
console.log('📄 File content loaded, length:', content.length);
|
||||||
|
|
||||||
|
// 模拟我们的验证逻辑
|
||||||
|
console.log('✅ Contains module.exports:', content.includes('module.exports'));
|
||||||
|
console.log('✅ Contains getMetadata:', content.includes('getMetadata'));
|
||||||
|
console.log('✅ Contains execute:', content.includes('execute'));
|
||||||
|
|
||||||
|
// 尝试语法检查
|
||||||
|
try {
|
||||||
|
new Function(content);
|
||||||
|
console.log('✅ JavaScript syntax is valid');
|
||||||
|
} catch (syntaxError) {
|
||||||
|
console.log('❌ JavaScript syntax error:', syntaxError.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Failed to read file:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testToolValidation();
|
||||||
19
test-tool.js
Normal file
19
test-tool.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const { getGlobalResourceManager } = require('./src/lib/core/resource');
|
||||||
|
|
||||||
|
async function testToolResource() {
|
||||||
|
try {
|
||||||
|
const rm = getGlobalResourceManager();
|
||||||
|
if (!rm.initialized) {
|
||||||
|
await rm.initializeWithNewArchitecture();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔍 Testing tool resource loading...');
|
||||||
|
const result = await rm.loadResource('@tool://calculator');
|
||||||
|
console.log('✅ Tool resource loaded successfully:', result);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Tool resource loading failed:', error.message);
|
||||||
|
console.log('Error details:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testToolResource();
|
||||||
Reference in New Issue
Block a user