foliko 1.1.13 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/data/plugins-state.json +1 -1
- package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
- package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
- package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
- package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
- package/.agent/mcp_config.json +7 -0
- package/.agent/memory/feedback/mnxe0cxc-14l6q5.md +17 -0
- package/.agent/memory/feedback/mnxe11pa-nxf577.md +9 -0
- package/.agent/memory/feedback/mnxe1an2-84faff.md +9 -0
- package/.agent/memory/feedback/mnxgcfj0-qg3wjc.md +9 -0
- package/.agent/memory/feedback/mnxgcn3y-40mqss.md +9 -0
- package/.agent/memory/feedback/mnxgcxq9-jm7ydl.md +9 -0
- package/.agent/memory/feedback/mnxgdyfj-pzjvkb.md +9 -0
- package/.agent/memory/feedback/mnxge3z1-7vyit1.md +9 -0
- package/.agent/memory/feedback/mnxhrg28-41hhjr.md +9 -0
- package/.agent/memory/feedback/mnxhrx0e-yth94k.md +9 -0
- package/.agent/memory/feedback/mnxhs3jd-rvx8aq.md +9 -0
- package/.agent/memory/feedback/mnxhs7p7-g5rtn9.md +9 -0
- package/.agent/memory/feedback/mnxhslx5-oqwuhr.md +9 -0
- package/.agent/memory/feedback/mnxhsvd6-nuyvvc.md +9 -0
- package/.agent/memory/project/mnxegq6z-5fc64w.md +22 -0
- package/.agent/memory/project/mnxh2w4r-le9hur.md +17 -0
- package/.agent/memory/project/mnxhq2yv-9qa8ay.md +31 -0
- package/.agent/memory/project/mnxhql11-iaun2o.md +34 -0
- package/.agent/memory/project/mnxhr78p-jpg7eq.md +23 -0
- package/.agent/memory/reference/mnxe0oa9-p6wzk6.md +27 -0
- package/.agent/memory/reference/mnxehcll-kcrmpf.md +29 -0
- package/.agent/memory/reference/mnxei0ts-jw091y.md +18 -0
- package/.agent/memory/reference/mnxfnrr4-rski36.md +40 -0
- package/.agent/memory/reference/mnxfo6n5-af9zls.md +18 -0
- package/.agent/memory/reference/mnxh2ady-u6cmvk.md +61 -0
- package/.agent/memory/reference/mnxhqdqh-ucsbsk.md +31 -0
- package/.agent/memory/reference/mnxiixyp-rz2gvw.md +34 -0
- package/.agent/memory/user/mnxhqxk3-vjjhlf.md +23 -0
- package/.agent/sessions/cli_default.json +11 -639
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +25 -0
- package/.claude/settings.local.json +23 -1
- package/cli/src/commands/chat.js +9 -15
- package/cli/src/ui/chat-ui.js +40 -71
- package/package.json +4 -2
- package/plugins/default-plugins.js +5 -5
- package/plugins/file-system-plugin.js +1 -1
- package/plugins/memory-plugin.js +12 -12
- package/plugins/plugin-manager-plugin.js +1 -0
- package/plugins/subagent-plugin.js +55 -1
- package/plugins/telegram-plugin.js +9 -6
- package/plugins/weixin-plugin.js +75 -78
- package/src/core/agent-chat.js +468 -1612
- package/src/core/agent.js +53 -134
- package/src/core/chat-session.js +423 -0
- package/src/core/context-compressor.js +473 -0
- package/src/core/context-manager.js +0 -48
- package/src/core/framework.js +95 -68
- package/src/core/index.js +11 -0
- package/src/core/notification-manager.js +125 -0
- package/src/core/subagent.js +295 -0
- package/src/core/token-counter.js +190 -0
- package/src/core/tool-executor.js +270 -0
- package/src/executors/mcp-executor.js +14 -1
- package/src/utils/download.js +596 -0
- package/system.md +312 -2373
- package/.agent/agents/code-assistant.json +0 -17
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -18
- package/.agent/agents/orchestrator-demo.md +0 -53
- package/.agent/agents/orchestrator.json +0 -7
- package/.agent/agents/poster-expert.md +0 -228
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/memory/feedback/mnv3nu27-3o15pf.md +0 -9
- package/.agent/memory/feedback/mnv3o078-b959yj.md +0 -9
- package/.agent/memory/feedback/mnv3o6ej-u0fif5.md +0 -9
- package/.agent/memory/feedback/mnv3obgl-bkkjoj.md +0 -9
- package/.agent/memory/feedback/mnv4a3js-dv6onx.md +0 -9
- package/.agent/memory/feedback/mnv4aacm-sxxowp.md +0 -9
- package/.agent/memory/feedback/mnv4ahto-w40ffm.md +0 -9
- package/.agent/memory/feedback/mnv4anvp-3cs06y.md +0 -9
- package/.agent/memory/feedback/mnvzgvtd-0o2900.md +0 -9
- package/.agent/memory/feedback/mnvzhajn-swbx61.md +0 -15
- package/.agent/memory/feedback/mnvzhgsp-p5vog3.md +0 -9
- package/.agent/memory/feedback/mnvzho0c-fgql7q.md +0 -14
- package/.agent/memory/feedback/mnvzhtzq-ufr5at.md +0 -9
- package/.agent/memory/feedback/mnvzhyb3-9byq2z.md +0 -9
- package/.agent/memory/feedback/mnvzi7hp-hyeafp.md +0 -9
- package/.agent/memory/feedback/mnvzibph-z7rwp5.md +0 -9
- package/.agent/memory/feedback/mnvzilys-7h176w.md +0 -14
- package/.agent/memory/feedback/mnvziuh5-zjshci.md +0 -9
- package/.agent/memory/feedback/mnw07wde-6zqsc8.md +0 -9
- package/.agent/memory/feedback/mnw084bp-j0ba2a.md +0 -9
- package/.agent/memory/user/mnv3n62r-y0h79j.md +0 -21
- package/.agent/memory/user/mnv3n9yf-ead4g8.md +0 -13
- package/.agent/memory/user/mnv3ne3j-82tq1k.md +0 -19
- package/.agent/memory/user/mnv3nhgm-g2s2us.md +0 -11
- package/.agent/memory/user/mnv3nl9u-ejd998.md +0 -16
- package/.agent/memory/user/mnv3nofp-ya5szl.md +0 -10
- package/.agent/memory/user/mnv49qne-bhk0ki.md +0 -9
- package/.agent/memory/user/mnv49w3y-rzr8ju.md +0 -13
- package/.agent/sessions/test.json +0 -16
- package/plugins/python-plugin-loader.js.bak +0 -856
- package/src/core/agent-context.js +0 -188
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PythonPluginLoader - Python 插件加载器
|
|
3
|
-
* 通过 python_execute 工具执行 Python 插件
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs')
|
|
7
|
-
const path = require('path')
|
|
8
|
-
const { Plugin } = require('../src/core/plugin-base')
|
|
9
|
-
const { logger } = require('../src/utils/logger')
|
|
10
|
-
const log = logger.child('PythonPluginLoader')
|
|
11
|
-
const { z } = require('zod')
|
|
12
|
-
const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown')
|
|
13
|
-
|
|
14
|
-
// 将 JSON Schema 转换为 Zod Schema
|
|
15
|
-
function jsonSchemaToZod(jsonSchema) {
|
|
16
|
-
if (!jsonSchema || jsonSchema.type !== 'object') {
|
|
17
|
-
return z.object({})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const properties = jsonSchema.properties || {}
|
|
21
|
-
const required = jsonSchema.required || []
|
|
22
|
-
|
|
23
|
-
const shape = {}
|
|
24
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
25
|
-
let zodType
|
|
26
|
-
switch (prop.type) {
|
|
27
|
-
case 'string':
|
|
28
|
-
zodType = z.string()
|
|
29
|
-
break
|
|
30
|
-
case 'number':
|
|
31
|
-
zodType = z.number()
|
|
32
|
-
break
|
|
33
|
-
case 'boolean':
|
|
34
|
-
zodType = z.boolean()
|
|
35
|
-
break
|
|
36
|
-
case 'array':
|
|
37
|
-
zodType = z.array(z.any())
|
|
38
|
-
break
|
|
39
|
-
case 'object':
|
|
40
|
-
zodType = jsonSchemaToZod(prop)
|
|
41
|
-
break
|
|
42
|
-
default:
|
|
43
|
-
zodType = z.any()
|
|
44
|
-
}
|
|
45
|
-
// 如果不是 required 字段,则标记为 optional
|
|
46
|
-
if (!required.includes(key)) {
|
|
47
|
-
zodType = zodType.optional()
|
|
48
|
-
}
|
|
49
|
-
shape[key] = zodType
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return z.object(shape)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
class PythonPluginLoader extends Plugin {
|
|
56
|
-
constructor(config = {}) {
|
|
57
|
-
super()
|
|
58
|
-
this.name = 'python-plugin-loader'
|
|
59
|
-
this.version = '1.0.0'
|
|
60
|
-
this.description = 'Python 插件加载器,属于Python的插件'
|
|
61
|
-
|
|
62
|
-
this._agentDir = config.agentDir || '.agent'
|
|
63
|
-
this._pythonPlugins = new Map()
|
|
64
|
-
this._framework = null
|
|
65
|
-
this.system = true
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
install(framework) {
|
|
69
|
-
this._framework = framework
|
|
70
|
-
return this
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
start(framework) {
|
|
74
|
-
// 先设置 framework 引用(供 _loadPythonPlugins 使用)
|
|
75
|
-
this._framework = framework
|
|
76
|
-
|
|
77
|
-
// 获取 ExtensionExecutorPlugin 实例,用于直接注册 Python 插件工具
|
|
78
|
-
this._extensionExecutor = framework.pluginManager?.get('extension-executor') || null
|
|
79
|
-
|
|
80
|
-
// 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
|
|
81
|
-
this._loadPythonPlugins()
|
|
82
|
-
|
|
83
|
-
// 监听 agent 创建事件,附加 Python 插件信息到系统提示词
|
|
84
|
-
framework.on('agent:created', (agent) => {
|
|
85
|
-
this._refreshAgentPythonPluginsPrompt(agent)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
|
|
89
|
-
if (framework._ready) {
|
|
90
|
-
this._refreshAllAgentsPythonPluginsPrompt(framework)
|
|
91
|
-
} else {
|
|
92
|
-
framework.once('framework:ready', () => {
|
|
93
|
-
this._refreshAllAgentsPythonPluginsPrompt(framework)
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return this
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 构建 Python 插件描述
|
|
102
|
-
* 格式与 MCP 一致,通过 ext_call 调用
|
|
103
|
-
*/
|
|
104
|
-
_buildPythonPluginsDescription() {
|
|
105
|
-
if (this._pythonPlugins.size === 0) {
|
|
106
|
-
return ''
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let desc = '## 【Python 插件】\n\n'
|
|
110
|
-
desc += 'Python 插件工具通过 `ext_call` 调用:\n\n'
|
|
111
|
-
|
|
112
|
-
for (const [name, plugin] of this._pythonPlugins) {
|
|
113
|
-
desc += `### ${plugin.info.name}\n`
|
|
114
|
-
desc += `${plugin.info.description || ''}\n\n`
|
|
115
|
-
if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
|
|
116
|
-
for (const tool of plugin.info.tools) {
|
|
117
|
-
const fullName = `${name}_${tool.name}`
|
|
118
|
-
desc += `- **${fullName}**: ${tool.description || '无描述'}\n`
|
|
119
|
-
// 使用 zodSchemaToMarkdown 生成参数描述
|
|
120
|
-
const params = tool.params || {}
|
|
121
|
-
if (Object.keys(params).length > 0) {
|
|
122
|
-
try {
|
|
123
|
-
const jsonSchema = this._paramsToJsonSchema(params)
|
|
124
|
-
const zodSchema = this._jsonSchemaToZod(jsonSchema)
|
|
125
|
-
const schemaMd = zodSchemaToMarkdown(zodSchema)
|
|
126
|
-
if (schemaMd) {
|
|
127
|
-
desc += `**参数:**\n\n${schemaMd}\n`
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
// 忽略参数描述生成错误
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
desc += '\n'
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
desc += '**调用格式:**\n'
|
|
139
|
-
desc += '```\next_call({ plugin: "python", tool: "插件名_工具名", args: {...} })\n'
|
|
140
|
-
desc += '```\n'
|
|
141
|
-
return desc.trim()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 将参数对象转换为 JSON Schema 格式
|
|
146
|
-
*/
|
|
147
|
-
_paramsToJsonSchema(params) {
|
|
148
|
-
const properties = {}
|
|
149
|
-
const required = []
|
|
150
|
-
|
|
151
|
-
for (const [key, value] of Object.entries(params)) {
|
|
152
|
-
let type = 'string'
|
|
153
|
-
if (typeof value === 'boolean') type = 'boolean'
|
|
154
|
-
else if (typeof value === 'number') type = 'number'
|
|
155
|
-
else if (Array.isArray(value)) type = 'array'
|
|
156
|
-
else if (typeof value === 'object' && value !== null) type = 'object'
|
|
157
|
-
|
|
158
|
-
properties[key] = {
|
|
159
|
-
type,
|
|
160
|
-
description: ''
|
|
161
|
-
}
|
|
162
|
-
required.push(key)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
type: 'object',
|
|
167
|
-
properties,
|
|
168
|
-
required
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* 将 JSON Schema 转换为 Zod schema
|
|
174
|
-
*/
|
|
175
|
-
_jsonSchemaToZod(jsonSchema) {
|
|
176
|
-
if (!jsonSchema || !jsonSchema.properties) {
|
|
177
|
-
return z.object({})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const shape = {}
|
|
182
|
-
const properties = jsonSchema.properties
|
|
183
|
-
const required = jsonSchema.required || []
|
|
184
|
-
|
|
185
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
186
|
-
shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return z.object(shape)
|
|
190
|
-
} catch (e) {
|
|
191
|
-
return z.object({})
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 将 JSON Schema 属性转换为 Zod 类型
|
|
197
|
-
*/
|
|
198
|
-
_jsonSchemaPropToZod(prop, isRequired) {
|
|
199
|
-
if (prop.enum) {
|
|
200
|
-
let zodType = z.string().enum(prop.enum)
|
|
201
|
-
return isRequired ? zodType : zodType.optional()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const type = prop.type || 'string'
|
|
205
|
-
switch (type) {
|
|
206
|
-
case 'string':
|
|
207
|
-
return isRequired ? z.string() : z.string().optional()
|
|
208
|
-
case 'number':
|
|
209
|
-
case 'integer':
|
|
210
|
-
return isRequired ? z.number() : z.number().optional()
|
|
211
|
-
case 'boolean':
|
|
212
|
-
return isRequired ? z.boolean() : z.boolean().optional()
|
|
213
|
-
case 'array':
|
|
214
|
-
return isRequired ? z.array(z.any()) : z.array(z.any()).optional()
|
|
215
|
-
case 'object':
|
|
216
|
-
if (prop.properties) {
|
|
217
|
-
const nested = {}
|
|
218
|
-
for (const [k, v] of Object.entries(prop.properties)) {
|
|
219
|
-
nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false)
|
|
220
|
-
}
|
|
221
|
-
return isRequired ? z.object(nested) : z.object(nested).optional()
|
|
222
|
-
}
|
|
223
|
-
return isRequired ? z.record(z.any()) : z.record(z.any()).optional()
|
|
224
|
-
default:
|
|
225
|
-
return isRequired ? z.any() : z.any().optional()
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* 刷新单个 agent 的 Python 插件提示词
|
|
231
|
-
*/
|
|
232
|
-
_refreshAgentPythonPluginsPrompt(agent) {
|
|
233
|
-
const existingPrompt = agent._originalPrompt || ''
|
|
234
|
-
if (existingPrompt.includes('【Python 插件】')) {
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const pyDesc = this._buildPythonPluginsDescription()
|
|
239
|
-
if (!pyDesc) return
|
|
240
|
-
|
|
241
|
-
// 将 Python 插件描述追加到系统提示词
|
|
242
|
-
agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 刷新所有 agent 的 Python 插件提示词
|
|
247
|
-
*/
|
|
248
|
-
_refreshAllAgentsPythonPluginsPrompt(framework) {
|
|
249
|
-
const visited = new Set()
|
|
250
|
-
|
|
251
|
-
const traverse = (agent) => {
|
|
252
|
-
if (!agent || visited.has(agent)) return
|
|
253
|
-
visited.add(agent)
|
|
254
|
-
this._refreshAgentPythonPluginsPrompt(agent)
|
|
255
|
-
|
|
256
|
-
// 递归处理子 agent
|
|
257
|
-
const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
|
|
258
|
-
for (const [name, subAgentInfo] of subAgents) {
|
|
259
|
-
traverse(subAgentInfo.agent)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const agents = framework._agents || []
|
|
264
|
-
for (const agent of agents) {
|
|
265
|
-
traverse(agent)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* 加载所有 Python 插件
|
|
271
|
-
*/
|
|
272
|
-
_loadPythonPlugins() {
|
|
273
|
-
const pluginsDir = path.join(this._agentDir, 'plugins')
|
|
274
|
-
if (!fs.existsSync(pluginsDir)) {
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
|
|
279
|
-
|
|
280
|
-
for (const file of files) {
|
|
281
|
-
try {
|
|
282
|
-
const pluginPath = path.join(pluginsDir, file)
|
|
283
|
-
const pluginName = file.replace('.py', '')
|
|
284
|
-
|
|
285
|
-
const pluginInfo = this._loadPythonPluginMeta(pluginPath)
|
|
286
|
-
if (pluginInfo) {
|
|
287
|
-
this._pythonPlugins.set(pluginName, {
|
|
288
|
-
name: pluginName,
|
|
289
|
-
path: pluginPath,
|
|
290
|
-
info: pluginInfo,
|
|
291
|
-
code: fs.readFileSync(pluginPath, 'utf-8')
|
|
292
|
-
})
|
|
293
|
-
log.info(` Loaded: ${pluginName}`)
|
|
294
|
-
|
|
295
|
-
// 注册插件的每个工具
|
|
296
|
-
if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
|
|
297
|
-
for (const tool of pluginInfo.tools) {
|
|
298
|
-
this._registerPythonTool(pluginName, tool)
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
} catch (err) {
|
|
303
|
-
log.error(` Failed to load ${file}:`, err.message)
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* 注册 Python 插件的工具
|
|
312
|
-
* 工具名格式:插件名_工具名(如 stock_get_price)
|
|
313
|
-
* 直接注册到 ExtensionExecutor,使用 ext_call({ plugin: "python", tool: "插件名_工具名", args: {...} }) 调用
|
|
314
|
-
*/
|
|
315
|
-
_registerPythonTool(pluginName, tool) {
|
|
316
|
-
if (!tool.name) return
|
|
317
|
-
|
|
318
|
-
// 格式:pluginname_toolname
|
|
319
|
-
const fullToolName = `${pluginName}_${tool.name}`
|
|
320
|
-
|
|
321
|
-
const toolDef = {
|
|
322
|
-
name: fullToolName,
|
|
323
|
-
description: tool.description || `${pluginName} 的 ${tool.name} 工具`,
|
|
324
|
-
inputSchema: this._parseToolParams(tool.params || {}),
|
|
325
|
-
execute: async (args) => {
|
|
326
|
-
return this._executePythonTool(pluginName, tool.name, args)
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
// 直接注册到 ExtensionExecutor(如果可用)
|
|
332
|
-
if (this._extensionExecutor) {
|
|
333
|
-
this._extensionExecutor.registerTool(
|
|
334
|
-
'python', // 插件名,用于 ext_call 的 plugin 参数
|
|
335
|
-
{ name: 'python', description: 'Python 插件工具', version: '1.0.0' },
|
|
336
|
-
toolDef
|
|
337
|
-
)
|
|
338
|
-
} else {
|
|
339
|
-
// 回退到基类的 registerTool
|
|
340
|
-
this.registerTool({
|
|
341
|
-
name: fullToolName,
|
|
342
|
-
description: `[${pluginName}] ${tool.description || ''}`,
|
|
343
|
-
pluginName: pluginName,
|
|
344
|
-
inputSchema: this._parseToolParams(tool.params || {}),
|
|
345
|
-
execute: toolDef.execute
|
|
346
|
-
})
|
|
347
|
-
}
|
|
348
|
-
} catch (err) {
|
|
349
|
-
log.error(` Failed to register tool ${fullToolName}:`, err.message)
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* 解析工具参数 schema
|
|
355
|
-
*/
|
|
356
|
-
_parseToolParams(params) {
|
|
357
|
-
// 构建 JSON Schema 格式
|
|
358
|
-
const properties = {}
|
|
359
|
-
const required = []
|
|
360
|
-
|
|
361
|
-
for (const [key, value] of Object.entries(params)) {
|
|
362
|
-
let type = 'string'
|
|
363
|
-
if (typeof value === 'boolean') type = 'boolean'
|
|
364
|
-
else if (typeof value === 'number') type = 'number'
|
|
365
|
-
else if (Array.isArray(value)) type = 'array'
|
|
366
|
-
else if (typeof value === 'object') type = 'object'
|
|
367
|
-
|
|
368
|
-
properties[key] = {
|
|
369
|
-
type,
|
|
370
|
-
description: ''
|
|
371
|
-
}
|
|
372
|
-
required.push(key)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const jsonSchema = {
|
|
376
|
-
type: 'object',
|
|
377
|
-
properties,
|
|
378
|
-
required
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// 转换为 Zod Schema 以兼容 AI SDK
|
|
382
|
-
return jsonSchemaToZod(jsonSchema)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* 格式化参数描述(使用 zodSchemaToMarkdown)
|
|
387
|
-
*/
|
|
388
|
-
_formatParams(params) {
|
|
389
|
-
if (!params || Object.keys(params).length === 0) {
|
|
390
|
-
return '无参数'
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
try {
|
|
394
|
-
// 构建 JSON Schema 格式
|
|
395
|
-
const properties = {}
|
|
396
|
-
const required = []
|
|
397
|
-
|
|
398
|
-
for (const [key, value] of Object.entries(params)) {
|
|
399
|
-
let type = 'string'
|
|
400
|
-
if (typeof value === 'boolean') type = 'boolean'
|
|
401
|
-
else if (typeof value === 'number') type = 'number'
|
|
402
|
-
else if (Array.isArray(value)) type = 'array'
|
|
403
|
-
else if (typeof value === 'object' && value !== null) type = 'object'
|
|
404
|
-
|
|
405
|
-
properties[key] = {
|
|
406
|
-
type,
|
|
407
|
-
description: ''
|
|
408
|
-
}
|
|
409
|
-
required.push(key)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const jsonSchema = {
|
|
413
|
-
type: 'object',
|
|
414
|
-
properties,
|
|
415
|
-
required
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// 转换为 Zod Schema
|
|
419
|
-
const zodSchema = jsonSchemaToZod(jsonSchema)
|
|
420
|
-
|
|
421
|
-
// 使用 zodSchemaToMarkdown 生成表格
|
|
422
|
-
const table = zodSchemaToMarkdown(zodSchema)
|
|
423
|
-
|
|
424
|
-
// 提取表格中的参数行,格式化为 "param: type" 形式
|
|
425
|
-
const lines = table.split('\n')
|
|
426
|
-
const paramLines = []
|
|
427
|
-
|
|
428
|
-
for (const line of lines) {
|
|
429
|
-
if (line.startsWith('| ') && !line.includes('字段') && !line.includes('---')) {
|
|
430
|
-
const parts = line.split('|').map(s => s.trim())
|
|
431
|
-
if (parts.length >= 3 && parts[1]) {
|
|
432
|
-
const paramName = parts[1]
|
|
433
|
-
const type = parts[2]
|
|
434
|
-
paramLines.push(`${paramName}: ${type}`)
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return paramLines.join(', ') || '无参数'
|
|
440
|
-
} catch (err) {
|
|
441
|
-
// fallback 到简单的参数名列表
|
|
442
|
-
return Object.keys(params).join(', ') || '无参数'
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* 加载单个 Python 插件的元信息
|
|
448
|
-
* 支持 PLUGIN 和 TOOLS 格式
|
|
449
|
-
*/
|
|
450
|
-
_loadPythonPluginMeta(pluginPath) {
|
|
451
|
-
const code = fs.readFileSync(pluginPath, 'utf-8')
|
|
452
|
-
|
|
453
|
-
const pluginIdx = code.indexOf('PLUGIN')
|
|
454
|
-
const toolsIdx = code.indexOf('TOOLS')
|
|
455
|
-
|
|
456
|
-
if (pluginIdx === -1 && toolsIdx === -1) {
|
|
457
|
-
console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: no PLUGIN or TOOLS found`)
|
|
458
|
-
return null
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
const info = {}
|
|
463
|
-
|
|
464
|
-
// 解析 PLUGIN
|
|
465
|
-
if (pluginIdx !== -1) {
|
|
466
|
-
const pluginData = this._extractPythonDict(code, pluginIdx)
|
|
467
|
-
if (pluginData && pluginData.name) {
|
|
468
|
-
info.name = pluginData.name
|
|
469
|
-
info.version = pluginData.version || '1.0.0'
|
|
470
|
-
info.description = pluginData.description || ''
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// 解析 TOOLS
|
|
475
|
-
if (toolsIdx !== -1) {
|
|
476
|
-
const toolsData = this._extractPythonList(code, toolsIdx)
|
|
477
|
-
if (toolsData && Array.isArray(toolsData)) {
|
|
478
|
-
info.tools = toolsData
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (!info.name || !info.tools) {
|
|
483
|
-
console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: invalid format - name: ${info.name}, tools: ${info.tools ? info.tools.length : 0}`)
|
|
484
|
-
return null
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return info
|
|
488
|
-
} catch (err) {
|
|
489
|
-
console.warn(`[PythonPluginLoader] Failed to parse ${path.basename(pluginPath)}:`, err.message)
|
|
490
|
-
return null
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* 从指定位置提取 Python 字典
|
|
496
|
-
*/
|
|
497
|
-
_extractPythonDict(code, startIdx) {
|
|
498
|
-
const braceStart = code.indexOf('{', startIdx)
|
|
499
|
-
if (braceStart === -1) return null
|
|
500
|
-
|
|
501
|
-
let braceCount = 0
|
|
502
|
-
let endIdx = -1
|
|
503
|
-
for (let i = braceStart; i < code.length; i++) {
|
|
504
|
-
if (code[i] === '{') braceCount++
|
|
505
|
-
else if (code[i] === '}') {
|
|
506
|
-
braceCount--
|
|
507
|
-
if (braceCount === 0) {
|
|
508
|
-
endIdx = i
|
|
509
|
-
break
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (endIdx === -1) return null
|
|
515
|
-
return this._parsePythonDict(code.substring(braceStart, endIdx + 1))
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* 从指定位置提取 Python 列表
|
|
520
|
-
*/
|
|
521
|
-
_extractPythonList(code, startIdx) {
|
|
522
|
-
const bracketStart = code.indexOf('[', startIdx)
|
|
523
|
-
if (bracketStart === -1) return null
|
|
524
|
-
|
|
525
|
-
let bracketCount = 0
|
|
526
|
-
let endIdx = -1
|
|
527
|
-
for (let i = bracketStart; i < code.length; i++) {
|
|
528
|
-
if (code[i] === '[') bracketCount++
|
|
529
|
-
else if (code[i] === ']') {
|
|
530
|
-
bracketCount--
|
|
531
|
-
if (bracketCount === 0) {
|
|
532
|
-
endIdx = i
|
|
533
|
-
break
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (endIdx === -1) return null
|
|
539
|
-
return this._parsePythonList(code.substring(bracketStart, endIdx + 1))
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* 解析 Python 字典为 JS 对象
|
|
544
|
-
*/
|
|
545
|
-
_parsePythonDict(str) {
|
|
546
|
-
str = str.replace(/#.*$/gm, '').trim()
|
|
547
|
-
if (!str || str === '{}') return {}
|
|
548
|
-
|
|
549
|
-
// 直接尝试 JSON.parse(PLUGIN 字典是有效的 JSON 格式)
|
|
550
|
-
try {
|
|
551
|
-
return JSON.parse(str)
|
|
552
|
-
} catch {
|
|
553
|
-
// 如果失败,使用手动解析
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const result = {}
|
|
557
|
-
let i = 0
|
|
558
|
-
let key = null
|
|
559
|
-
let valueStart = -1
|
|
560
|
-
let depth = 0
|
|
561
|
-
let inString = false
|
|
562
|
-
let stringChar = null
|
|
563
|
-
let escaped = false
|
|
564
|
-
|
|
565
|
-
while (i < str.length) {
|
|
566
|
-
const char = str[i]
|
|
567
|
-
|
|
568
|
-
if (escaped) {
|
|
569
|
-
escaped = false
|
|
570
|
-
i++
|
|
571
|
-
continue
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (char === '\\' && inString) {
|
|
575
|
-
escaped = true
|
|
576
|
-
i++
|
|
577
|
-
continue
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if ((char === '"' || char === "'") && !inString) {
|
|
581
|
-
inString = true
|
|
582
|
-
stringChar = char
|
|
583
|
-
i++
|
|
584
|
-
continue
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (inString && char === stringChar) {
|
|
588
|
-
inString = false
|
|
589
|
-
i++
|
|
590
|
-
continue
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (inString) {
|
|
594
|
-
i++
|
|
595
|
-
continue
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (char === '{' || char === '[') {
|
|
599
|
-
if (depth === 0 && char === '{') {
|
|
600
|
-
valueStart = i + 1
|
|
601
|
-
}
|
|
602
|
-
depth++
|
|
603
|
-
i++
|
|
604
|
-
continue
|
|
605
|
-
}
|
|
606
|
-
if (char === '}' || char === ']') {
|
|
607
|
-
if (depth > 0) {
|
|
608
|
-
depth--
|
|
609
|
-
if (depth === 0 && char === '}') {
|
|
610
|
-
if (key !== null && valueStart !== -1) {
|
|
611
|
-
const valueStr = str.substring(valueStart, i).trim()
|
|
612
|
-
if (valueStr) result[key] = this._parseValue(valueStr)
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
i++
|
|
617
|
-
continue
|
|
618
|
-
}
|
|
619
|
-
if (depth > 0) {
|
|
620
|
-
i++
|
|
621
|
-
continue
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (char === ':') {
|
|
625
|
-
key = str.substring(valueStart, i).trim().replace(/["']/g, '')
|
|
626
|
-
valueStart = i + 1
|
|
627
|
-
i++
|
|
628
|
-
continue
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
if (char === ',') {
|
|
632
|
-
if (key !== null && valueStart !== -1) {
|
|
633
|
-
const valueStr = str.substring(valueStart, i).trim()
|
|
634
|
-
if (valueStr) result[key] = this._parseValue(valueStr)
|
|
635
|
-
}
|
|
636
|
-
key = null
|
|
637
|
-
valueStart = i + 1
|
|
638
|
-
i++
|
|
639
|
-
continue
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
i++
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return result
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* 解析 Python 列表为 JS 数组
|
|
650
|
-
*/
|
|
651
|
-
_parsePythonList(str) {
|
|
652
|
-
str = str.replace(/#.*$/gm, '').trim()
|
|
653
|
-
if (!str || str === '[]') return []
|
|
654
|
-
|
|
655
|
-
// 直接尝试 JSON.parse(TOOLS 列表是有效的 JSON 格式)
|
|
656
|
-
try {
|
|
657
|
-
return JSON.parse(str)
|
|
658
|
-
} catch {
|
|
659
|
-
// 如果失败,使用手动解析
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const result = []
|
|
663
|
-
let i = 0
|
|
664
|
-
let itemStart = 1
|
|
665
|
-
let depth = 0
|
|
666
|
-
let inString = false
|
|
667
|
-
let stringChar = null
|
|
668
|
-
let escaped = false
|
|
669
|
-
|
|
670
|
-
while (i < str.length) {
|
|
671
|
-
const char = str[i]
|
|
672
|
-
|
|
673
|
-
if (escaped) {
|
|
674
|
-
escaped = false
|
|
675
|
-
i++
|
|
676
|
-
continue
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (char === '\\' && inString) {
|
|
680
|
-
escaped = true
|
|
681
|
-
i++
|
|
682
|
-
continue
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if ((char === '"' || char === "'") && !inString) {
|
|
686
|
-
inString = true
|
|
687
|
-
stringChar = char
|
|
688
|
-
i++
|
|
689
|
-
continue
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
if (inString && char === stringChar) {
|
|
693
|
-
inString = false
|
|
694
|
-
i++
|
|
695
|
-
continue
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (inString) {
|
|
699
|
-
i++
|
|
700
|
-
continue
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
if (char === '{' || char === '[') {
|
|
704
|
-
if (depth === 0) itemStart = i
|
|
705
|
-
depth++
|
|
706
|
-
i++
|
|
707
|
-
continue
|
|
708
|
-
}
|
|
709
|
-
if (char === '}' || char === ']') {
|
|
710
|
-
if (depth > 0) {
|
|
711
|
-
depth--
|
|
712
|
-
if (depth === 0) {
|
|
713
|
-
const itemStr = str.substring(itemStart, i + 1).trim()
|
|
714
|
-
if (itemStr && itemStr !== ',') {
|
|
715
|
-
if (itemStr.startsWith('{')) {
|
|
716
|
-
result.push(this._parsePythonDict(itemStr))
|
|
717
|
-
} else {
|
|
718
|
-
result.push(this._parseValue(itemStr))
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
itemStart = i + 1
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
i++
|
|
725
|
-
continue
|
|
726
|
-
}
|
|
727
|
-
if (depth > 0) {
|
|
728
|
-
i++
|
|
729
|
-
continue
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
if (char === ',') {
|
|
733
|
-
const itemStr = str.substring(itemStart, i).trim()
|
|
734
|
-
if (itemStr) {
|
|
735
|
-
if (itemStr.startsWith('{')) {
|
|
736
|
-
result.push(this._parsePythonDict(itemStr))
|
|
737
|
-
} else {
|
|
738
|
-
result.push(this._parseValue(itemStr))
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
itemStart = i + 1
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
i++
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
return result
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* 解析单个值
|
|
752
|
-
*/
|
|
753
|
-
_parseValue(str) {
|
|
754
|
-
str = str.trim()
|
|
755
|
-
if (!str) return null
|
|
756
|
-
|
|
757
|
-
if (str.startsWith('{')) {
|
|
758
|
-
return this._parsePythonDict(str)
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
if (str.startsWith('"') || str.startsWith("'")) {
|
|
762
|
-
return str.slice(1, -1)
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
if (str === 'True') return true
|
|
766
|
-
if (str === 'False') return false
|
|
767
|
-
if (str === 'None') return null
|
|
768
|
-
|
|
769
|
-
if (!isNaN(str)) return Number(str)
|
|
770
|
-
|
|
771
|
-
return str
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
/**
|
|
775
|
-
* 执行 Python 插件工具
|
|
776
|
-
*/
|
|
777
|
-
async _executePythonTool(pluginName, toolName, params) {
|
|
778
|
-
const plugin = this._pythonPlugins.get(pluginName)
|
|
779
|
-
if (!plugin) {
|
|
780
|
-
return { success: false, error: `Plugin '${pluginName}' not found` }
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// 构建执行代码 - 使用数组拼接避免缩进问题
|
|
784
|
-
const pluginDir = path.dirname(plugin.path)
|
|
785
|
-
const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
|
|
786
|
-
|
|
787
|
-
// 从 TOOLS 提取工具名称列表,构建路由函数
|
|
788
|
-
const toolNames = (plugin.info.tools || []).map(t => t.name)
|
|
789
|
-
|
|
790
|
-
const execCode = [
|
|
791
|
-
'import sys',
|
|
792
|
-
'import base64',
|
|
793
|
-
'import json as _json',
|
|
794
|
-
`sys.path.insert(0, r'${pluginDir}')`,
|
|
795
|
-
'',
|
|
796
|
-
'# 加载插件代码(使用 textwrap.dedent 去除缩进)',
|
|
797
|
-
`plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')`,
|
|
798
|
-
`import textwrap`,
|
|
799
|
-
`exec(compile(textwrap.dedent(plugin_code), '${pluginName}.py', 'exec'))`,
|
|
800
|
-
'',
|
|
801
|
-
'# 工具路由函数',
|
|
802
|
-
`_tool_map = {${toolNames.map(n => `"${n}": ${n}`).join(', ')}}`,
|
|
803
|
-
`def execute_tool(name, params):`,
|
|
804
|
-
` if name not in _tool_map:`,
|
|
805
|
-
` return {"success": False, "error": f"Tool '{name}' not found in plugin '${pluginName}'"}`,
|
|
806
|
-
` try:`,
|
|
807
|
-
` return _tool_map[name](params)`,
|
|
808
|
-
` except Exception as e:`,
|
|
809
|
-
` return {"success": False, "error": str(e)}`,
|
|
810
|
-
'',
|
|
811
|
-
'# 执行工具',
|
|
812
|
-
`result = execute_tool('${toolName}', ${JSON.stringify(params)})`,
|
|
813
|
-
'print(_json.dumps(result))'
|
|
814
|
-
].join('\n')
|
|
815
|
-
|
|
816
|
-
try {
|
|
817
|
-
// 通过 python-execute 工具执行
|
|
818
|
-
const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
|
|
819
|
-
if (!pythonExe) {
|
|
820
|
-
return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const pythonResult = await pythonExe.execute({ code: execCode })
|
|
824
|
-
|
|
825
|
-
if (pythonResult.success) {
|
|
826
|
-
try {
|
|
827
|
-
// 尝试解析 JSON 结果
|
|
828
|
-
const parsed = JSON.parse(pythonResult.stdout)
|
|
829
|
-
return parsed
|
|
830
|
-
} catch {
|
|
831
|
-
return { success: true, output: pythonResult.stdout }
|
|
832
|
-
}
|
|
833
|
-
} else {
|
|
834
|
-
return { success: false, error: pythonResult.stderr || pythonResult.error }
|
|
835
|
-
}
|
|
836
|
-
} catch (err) {
|
|
837
|
-
return { success: false, error: err.message }
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* 获取所有已加载的 Python 插件
|
|
843
|
-
*/
|
|
844
|
-
getPythonPlugins() {
|
|
845
|
-
return Array.from(this._pythonPlugins.values())
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* 获取 Python 插件信息
|
|
850
|
-
*/
|
|
851
|
-
getPythonPlugin(name) {
|
|
852
|
-
return this._pythonPlugins.get(name)
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
module.exports = PythonPluginLoader
|