foliko 1.1.7 → 1.1.9
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/agents/poster-expert.md +228 -0
- package/.agent/data/default.json +10 -0
- package/.agent/data/plugins-state.json +1 -1
- package/.agent/mcp_config.json +0 -6
- package/.agent/plugins/test-plugin.py +15 -0
- package/.agent/plugins/test_nested_plugin.py +85 -0
- package/.agent/sessions/cli_default.json +83 -803
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
- package/.claude/settings.local.json +8 -1
- package/cli/src/ui/chat-ui.js +32 -21
- package/foliko_poster.png +0 -0
- package/output/business_poster_final.png +0 -0
- package/output/emoji_test_v2.png +0 -0
- package/output/foliko-ai-launch-poster.png +0 -0
- package/output/foliko-poster.png +0 -0
- package/output/muji_style_poster.png +0 -0
- package/output/new_product_launch.png +0 -0
- package/output/news_poster_625yi_subsidy.png +0 -0
- package/output/tech_future_2026.png +0 -0
- package/output/tech_future_poster.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +60 -7
- package/plugins/extension-executor-plugin.js +32 -22
- package/plugins/feishu-plugin.js +2 -2
- package/plugins/python-plugin-loader.js +461 -40
- package/plugins/python-plugin-loader.js.bak +856 -0
- package/plugins/subagent-plugin.js +0 -121
- package/plugins/telegram-plugin.js +2 -2
- package/plugins/weixin-plugin.js +40 -31
- package/poster.png +0 -0
- package/skills/foliko-dev/SKILL.md +6 -0
- package/skills/python-plugin-dev/SKILL.md +124 -2
- package/src/core/agent-chat.js +220 -281
- package/src/core/agent.js +127 -29
- package/src/executors/mcp-executor.js +282 -26
- package/src/utils/plugin-helpers.js +22 -4
- package/system.md +362 -109
- package/test-scan.js +18 -0
- package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +0 -15
- package/.agent/memory/feedback/mnt88ja3-al4fuy.md +0 -9
- package/.agent/plugins/poster-plugin/README.md +0 -304
- package/.agent/plugins/poster-plugin/emojis/rocket.png +0 -1
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/SegoeUI Emoji.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +0 -13
- package/.agent/plugins/poster-plugin/package.json +0 -29
- package/.agent/plugins/poster-plugin/src/canvas.js +0 -224
- package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
- package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
- package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -120
- package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -153
- package/.agent/plugins/poster-plugin/src/components/button.js +0 -124
- package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
- package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/columns.js +0 -107
- package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
- package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
- package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
- package/.agent/plugins/poster-plugin/src/components/grid.js +0 -118
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -144
- package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -155
- package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
- package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
- package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
- package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
- package/.agent/plugins/poster-plugin/src/components/quote.js +0 -97
- package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -193
- package/.agent/plugins/poster-plugin/src/components/seal.js +0 -146
- package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
- package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
- package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
- package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -52
- package/.agent/plugins/poster-plugin/src/composer.js +0 -2146
- package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -60
- package/.agent/plugins/poster-plugin/src/elements/background.js +0 -84
- package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
- package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
- package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -230
- package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
- package/.agent/plugins/poster-plugin/src/elements/text.js +0 -109
- package/.agent/plugins/poster-plugin/src/fonts.js +0 -243
- package/.agent/plugins/poster-plugin/src/index.js +0 -2081
- package/.agent/plugins/poster-plugin/src/presets.js +0 -36
- package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
- package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
- package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
- package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
- package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
- package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
- package/.agent/plugins/poster-plugin/test-background.svg +0 -1
- package/.agent/plugins/poster-plugin/test-full-poster.svg +0 -2
- package/.agent/plugins/poster-plugin/test-image.png +0 -0
- package/.agent/plugins/poster-plugin/yarn.lock +0 -1007
- package/output/zen_silence.png +0 -0
|
@@ -74,6 +74,9 @@ class PythonPluginLoader extends Plugin {
|
|
|
74
74
|
// 先设置 framework 引用(供 _loadPythonPlugins 使用)
|
|
75
75
|
this._framework = framework
|
|
76
76
|
|
|
77
|
+
// 获取 ExtensionExecutorPlugin 实例,用于直接注册 Python 插件工具
|
|
78
|
+
this._extensionExecutor = framework.pluginManager?.get('extension-executor') || null
|
|
79
|
+
|
|
77
80
|
// 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
|
|
78
81
|
this._loadPythonPlugins()
|
|
79
82
|
|
|
@@ -96,14 +99,15 @@ class PythonPluginLoader extends Plugin {
|
|
|
96
99
|
|
|
97
100
|
/**
|
|
98
101
|
* 构建 Python 插件描述
|
|
102
|
+
* 格式与 MCP 一致,通过 ext_call 调用
|
|
99
103
|
*/
|
|
100
104
|
_buildPythonPluginsDescription() {
|
|
101
105
|
if (this._pythonPlugins.size === 0) {
|
|
102
106
|
return ''
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
let desc = '## 【Python 插件】\n'
|
|
106
|
-
desc += 'Python
|
|
109
|
+
let desc = '## 【Python 插件】\n\n'
|
|
110
|
+
desc += 'Python 插件工具通过 `ext_call` 调用:\n\n'
|
|
107
111
|
|
|
108
112
|
for (const [name, plugin] of this._pythonPlugins) {
|
|
109
113
|
desc += `### ${plugin.info.name}\n`
|
|
@@ -111,8 +115,21 @@ class PythonPluginLoader extends Plugin {
|
|
|
111
115
|
if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
|
|
112
116
|
for (const tool of plugin.info.tools) {
|
|
113
117
|
const fullName = `${name}_${tool.name}`
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
}
|
|
116
133
|
}
|
|
117
134
|
}
|
|
118
135
|
desc += '\n'
|
|
@@ -124,6 +141,127 @@ class PythonPluginLoader extends Plugin {
|
|
|
124
141
|
return desc.trim()
|
|
125
142
|
}
|
|
126
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
|
+
// 1. 处理 enum(枚举值)
|
|
200
|
+
if (prop.enum) {
|
|
201
|
+
let zodType = z.string().enum(prop.enum)
|
|
202
|
+
return isRequired ? zodType : zodType.optional()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const type = prop.type || 'string'
|
|
206
|
+
|
|
207
|
+
// 2. 简单类型
|
|
208
|
+
switch (type) {
|
|
209
|
+
case 'string':
|
|
210
|
+
return isRequired ? z.string() : z.string().optional()
|
|
211
|
+
case 'number':
|
|
212
|
+
case 'integer':
|
|
213
|
+
return isRequired ? z.number() : z.number().optional()
|
|
214
|
+
case 'boolean':
|
|
215
|
+
return isRequired ? z.boolean() : z.boolean().optional()
|
|
216
|
+
default:
|
|
217
|
+
break
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 3. 数组类型
|
|
221
|
+
if (type === 'array') {
|
|
222
|
+
let itemsZod = z.any()
|
|
223
|
+
if (prop.items) {
|
|
224
|
+
// 支持 oneOf(联合类型数组)
|
|
225
|
+
if (prop.items.oneOf) {
|
|
226
|
+
const unionTypes = prop.items.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
|
|
227
|
+
if (unionTypes.length > 0) {
|
|
228
|
+
itemsZod = z.union(unionTypes)
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
itemsZod = this._jsonSchemaPropToZod(prop.items, true)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return isRequired ? z.array(itemsZod) : z.array(itemsZod).optional()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 4. 对象类型
|
|
238
|
+
if (type === 'object') {
|
|
239
|
+
if (prop.properties && Object.keys(prop.properties).length > 0) {
|
|
240
|
+
const nested = {}
|
|
241
|
+
for (const [k, v] of Object.entries(prop.properties)) {
|
|
242
|
+
nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false)
|
|
243
|
+
}
|
|
244
|
+
const objZod = z.object(nested)
|
|
245
|
+
return isRequired ? objZod : objZod.optional()
|
|
246
|
+
}
|
|
247
|
+
// 无 properties 的 object,视为 record
|
|
248
|
+
const recordZod = z.record(z.any())
|
|
249
|
+
return isRequired ? recordZod : recordZod.optional()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 5. oneOf(联合类型)
|
|
253
|
+
if (prop.oneOf) {
|
|
254
|
+
const unionTypes = prop.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
|
|
255
|
+
if (unionTypes.length > 0) {
|
|
256
|
+
const unionZod = z.union(unionTypes)
|
|
257
|
+
return isRequired ? unionZod : unionZod.optional()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 6. Fallback
|
|
262
|
+
return isRequired ? z.any() : z.any().optional()
|
|
263
|
+
}
|
|
264
|
+
|
|
127
265
|
/**
|
|
128
266
|
* 刷新单个 agent 的 Python 插件提示词
|
|
129
267
|
*/
|
|
@@ -208,6 +346,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
208
346
|
/**
|
|
209
347
|
* 注册 Python 插件的工具
|
|
210
348
|
* 工具名格式:插件名_工具名(如 stock_get_price)
|
|
349
|
+
* 直接注册到 ExtensionExecutor,使用 ext_call({ plugin: "python", tool: "插件名_工具名", args: {...} }) 调用
|
|
211
350
|
*/
|
|
212
351
|
_registerPythonTool(pluginName, tool) {
|
|
213
352
|
if (!tool.name) return
|
|
@@ -215,38 +354,56 @@ class PythonPluginLoader extends Plugin {
|
|
|
215
354
|
// 格式:pluginname_toolname
|
|
216
355
|
const fullToolName = `${pluginName}_${tool.name}`
|
|
217
356
|
|
|
357
|
+
const toolDef = {
|
|
358
|
+
name: fullToolName,
|
|
359
|
+
description: tool.description || `${pluginName} 的 ${tool.name} 工具`,
|
|
360
|
+
inputSchema: this._parseToolParams(tool.params || {}),
|
|
361
|
+
execute: async (args) => {
|
|
362
|
+
return this._executePythonTool(pluginName, tool.name, args)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
218
366
|
try {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
367
|
+
// 直接注册到 ExtensionExecutor(如果可用)
|
|
368
|
+
if (this._extensionExecutor) {
|
|
369
|
+
this._extensionExecutor.registerTool(
|
|
370
|
+
'python', // 插件名,用于 ext_call 的 plugin 参数
|
|
371
|
+
{ name: 'python', description: 'Python 插件工具', version: '1.0.0' },
|
|
372
|
+
toolDef
|
|
373
|
+
)
|
|
374
|
+
} else {
|
|
375
|
+
// 回退到基类的 registerTool
|
|
376
|
+
this.registerTool({
|
|
377
|
+
name: fullToolName,
|
|
378
|
+
description: `[${pluginName}] ${tool.description || ''}`,
|
|
379
|
+
pluginName: pluginName,
|
|
380
|
+
inputSchema: this._parseToolParams(tool.params || {}),
|
|
381
|
+
execute: toolDef.execute
|
|
382
|
+
})
|
|
383
|
+
}
|
|
228
384
|
} catch (err) {
|
|
229
385
|
log.error(` Failed to register tool ${fullToolName}:`, err.message)
|
|
230
386
|
}
|
|
231
387
|
}
|
|
232
388
|
|
|
233
389
|
/**
|
|
234
|
-
* 解析工具参数 schema
|
|
390
|
+
* 解析工具参数 schema - 支持多重嵌套解析
|
|
391
|
+
* 支持 Python 常见写法:
|
|
392
|
+
* - "name": "string" # 类型字符串占位符
|
|
393
|
+
* - "data": {"type": "object", "properties": {...}} # 完整 JSON Schema
|
|
394
|
+
* - "tags": ["string"] # 简单数组
|
|
395
|
+
* - "tags": {"type": "array", "items": "string"} # JSON Schema 数组
|
|
396
|
+
* - "filter": {} # 简单 dict
|
|
397
|
+
* - "filter": [] # 简单 list
|
|
235
398
|
*/
|
|
236
399
|
_parseToolParams(params) {
|
|
237
|
-
// 构建 JSON Schema 格式
|
|
238
400
|
const properties = {}
|
|
239
401
|
const required = []
|
|
240
402
|
|
|
241
403
|
for (const [key, value] of Object.entries(params)) {
|
|
242
|
-
|
|
243
|
-
if (typeof value === 'boolean') type = 'boolean'
|
|
244
|
-
else if (typeof value === 'number') type = 'number'
|
|
245
|
-
else if (Array.isArray(value)) type = 'array'
|
|
246
|
-
else if (typeof value === 'object') type = 'object'
|
|
247
|
-
|
|
404
|
+
const parsed = this._parseTypeValue(value)
|
|
248
405
|
properties[key] = {
|
|
249
|
-
|
|
406
|
+
...parsed,
|
|
250
407
|
description: ''
|
|
251
408
|
}
|
|
252
409
|
required.push(key)
|
|
@@ -259,7 +416,256 @@ class PythonPluginLoader extends Plugin {
|
|
|
259
416
|
}
|
|
260
417
|
|
|
261
418
|
// 转换为 Zod Schema 以兼容 AI SDK
|
|
262
|
-
return
|
|
419
|
+
return this._jsonSchemaToZod(jsonSchema)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 解析单个值的类型(支持递归嵌套)
|
|
424
|
+
* @param {any} value - Python 插件中的参数值
|
|
425
|
+
* @returns {object} - JSON Schema 属性对象
|
|
426
|
+
*/
|
|
427
|
+
_parseTypeValue(value) {
|
|
428
|
+
// 1. 空值/None/null
|
|
429
|
+
if (value === null || value === undefined) {
|
|
430
|
+
return { type: 'string' }
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 2. 字符串类型占位符(如 "string", "number", "boolean")
|
|
434
|
+
if (typeof value === 'string') {
|
|
435
|
+
return this._parseTypeString(value)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 3. JavaScript 原生类型
|
|
439
|
+
if (typeof value === 'boolean') {
|
|
440
|
+
return { type: 'boolean' }
|
|
441
|
+
}
|
|
442
|
+
if (typeof value === 'number') {
|
|
443
|
+
return { type: 'number' }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 4. 数组
|
|
447
|
+
if (Array.isArray(value)) {
|
|
448
|
+
return this._parseArrayValue(value)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 5. 对象(字典)
|
|
452
|
+
if (typeof value === 'object') {
|
|
453
|
+
return this._parseObjectValue(value)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 6. Fallback
|
|
457
|
+
return { type: 'string' }
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* 解析类型字符串占位符
|
|
462
|
+
* 支持:"string", "number", "boolean", "array", "object", "any"
|
|
463
|
+
* 也支持嵌套简写如:"string[]", "object{name:string}"
|
|
464
|
+
*/
|
|
465
|
+
_parseTypeString(str) {
|
|
466
|
+
const typeStringMap = {
|
|
467
|
+
'string': 'string',
|
|
468
|
+
'number': 'number',
|
|
469
|
+
'integer': 'number',
|
|
470
|
+
'boolean': 'boolean',
|
|
471
|
+
'bool': 'boolean',
|
|
472
|
+
'array': 'array',
|
|
473
|
+
'object': 'object',
|
|
474
|
+
'any': 'string'
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const normalized = str.toLowerCase().trim()
|
|
478
|
+
|
|
479
|
+
// 直接类型映射
|
|
480
|
+
if (typeStringMap[normalized]) {
|
|
481
|
+
return { type: typeStringMap[normalized] }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// 数组简写:string[] -> {type: array, items: {type: string}}
|
|
485
|
+
if (normalized.endsWith('[]')) {
|
|
486
|
+
const itemType = normalized.slice(0, -2).trim()
|
|
487
|
+
const mappedType = typeStringMap[itemType] || 'string'
|
|
488
|
+
return {
|
|
489
|
+
type: 'array',
|
|
490
|
+
items: { type: mappedType }
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// 对象简写:{name:string,age:number} -> {type: object, properties: {...}}
|
|
495
|
+
if (normalized.startsWith('{') && normalized.endsWith('}')) {
|
|
496
|
+
const inner = normalized.slice(1, -1)
|
|
497
|
+
const properties = {}
|
|
498
|
+
const required = []
|
|
499
|
+
|
|
500
|
+
const parts = this._splitByComma(inner)
|
|
501
|
+
for (const part of parts) {
|
|
502
|
+
const colonIdx = part.indexOf(':')
|
|
503
|
+
if (colonIdx > 0) {
|
|
504
|
+
const propName = part.substring(0, colonIdx).trim()
|
|
505
|
+
const propType = part.substring(colonIdx + 1).trim()
|
|
506
|
+
properties[propName] = this._parseTypeString(propType)
|
|
507
|
+
required.push(propName)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
type: 'object',
|
|
513
|
+
properties,
|
|
514
|
+
required
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 未知类型,默认 string
|
|
519
|
+
return { type: 'string' }
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* 分割字符串(考虑嵌套的括号)
|
|
524
|
+
*/
|
|
525
|
+
_splitByComma(str) {
|
|
526
|
+
const result = []
|
|
527
|
+
let current = ''
|
|
528
|
+
let depth = 0
|
|
529
|
+
|
|
530
|
+
for (const char of str) {
|
|
531
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
532
|
+
depth++
|
|
533
|
+
current += char
|
|
534
|
+
} else if (char === ')' || char === ']' || char === '}') {
|
|
535
|
+
depth--
|
|
536
|
+
current += char
|
|
537
|
+
} else if (char === ',' && depth === 0) {
|
|
538
|
+
result.push(current.trim())
|
|
539
|
+
current = ''
|
|
540
|
+
} else {
|
|
541
|
+
current += char
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (current.trim()) {
|
|
546
|
+
result.push(current.trim())
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return result
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* 解析数组值
|
|
554
|
+
* 支持:
|
|
555
|
+
* - [] -> {type: array, items: {type: string}}
|
|
556
|
+
* - ["string", "number"] -> {type: array, items: {type: string, enum: [...]}}
|
|
557
|
+
* - {"type": "array", "items": "string"} -> 完整 JSON Schema 格式
|
|
558
|
+
*/
|
|
559
|
+
_parseArrayValue(arr) {
|
|
560
|
+
// 空数组,默认 string 类型数组
|
|
561
|
+
if (arr.length === 0) {
|
|
562
|
+
return { type: 'array', items: { type: 'string' } }
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// 完整 JSON Schema 格式
|
|
566
|
+
if (arr.length === 1 && typeof arr[0] === 'object' && arr[0].type === 'array') {
|
|
567
|
+
return this._parseTypeValue(arr[0])
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// 简单同类型数组:["string", "number"] -> 收集所有类型
|
|
571
|
+
const types = new Set()
|
|
572
|
+
for (const item of arr) {
|
|
573
|
+
if (typeof item === 'string') {
|
|
574
|
+
types.add(item.toLowerCase())
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 检查是否是类型字符串数组
|
|
579
|
+
const typeStringMap = ['string', 'number', 'integer', 'boolean', 'bool', 'array', 'object', 'any']
|
|
580
|
+
const isTypeArray = types.size > 0 && [...types].every(t => typeStringMap.includes(t))
|
|
581
|
+
|
|
582
|
+
if (isTypeArray) {
|
|
583
|
+
// 单一类型:["string"] -> {type: array, items: {type: "string"}}
|
|
584
|
+
if (types.size === 1) {
|
|
585
|
+
const itemType = [...types][0]
|
|
586
|
+
const mappedType = {
|
|
587
|
+
'string': 'string', 'number': 'number', 'integer': 'number',
|
|
588
|
+
'boolean': 'boolean', 'bool': 'boolean', 'array': 'array', 'object': 'object', 'any': 'string'
|
|
589
|
+
}[itemType] || 'string'
|
|
590
|
+
return { type: 'array', items: { type: mappedType } }
|
|
591
|
+
}
|
|
592
|
+
// 多类型联合数组:["string", "number"] -> z.union
|
|
593
|
+
const zodTypes = [...types].map(t => {
|
|
594
|
+
const mappedType = {
|
|
595
|
+
'string': 'string', 'number': 'number', 'integer': 'number',
|
|
596
|
+
'boolean': 'boolean', 'bool': 'boolean', 'array': 'array', 'object': 'object', 'any': 'string'
|
|
597
|
+
}[t] || 'string'
|
|
598
|
+
return mappedType
|
|
599
|
+
})
|
|
600
|
+
// 转换为 JSON Schema 的 oneOf
|
|
601
|
+
return { type: 'array', items: { oneOf: zodTypes.map(t => ({ type: t })) } }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 复杂数组,内部是对象
|
|
605
|
+
const firstObj = arr.find(item => typeof item === 'object' && item !== null)
|
|
606
|
+
if (firstObj) {
|
|
607
|
+
return { type: 'array', items: this._parseTypeValue(firstObj) }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 默认返回 string 数组
|
|
611
|
+
return { type: 'array', items: { type: 'string' } }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* 解析对象值
|
|
616
|
+
* 支持:
|
|
617
|
+
* - {} -> {type: object}
|
|
618
|
+
* - {"type": "object", "properties": {...}} -> 完整 JSON Schema
|
|
619
|
+
* - {"type": "object", "properties": {"name": "string", "age": "number"}} -> 递归
|
|
620
|
+
*/
|
|
621
|
+
_parseObjectValue(obj) {
|
|
622
|
+
// 已经是完整 JSON Schema 格式
|
|
623
|
+
if (obj.type) {
|
|
624
|
+
const typeStr = typeof obj.type === 'string' ? obj.type.toLowerCase() : ''
|
|
625
|
+
|
|
626
|
+
// object 类型
|
|
627
|
+
if (typeStr === 'object' || typeStr === '') {
|
|
628
|
+
if (obj.properties) {
|
|
629
|
+
const properties = {}
|
|
630
|
+
const required = obj.required || []
|
|
631
|
+
|
|
632
|
+
for (const [key, value] of Object.entries(obj.properties)) {
|
|
633
|
+
properties[key] = this._parseTypeValue(value)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return { type: 'object', properties, required }
|
|
637
|
+
}
|
|
638
|
+
return { type: 'object' }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// array 类型
|
|
642
|
+
if (typeStr === 'array') {
|
|
643
|
+
const items = obj.items ? this._parseTypeValue(obj.items) : { type: 'string' }
|
|
644
|
+
return { type: 'array', items }
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// 简单类型
|
|
648
|
+
const typeMap = {
|
|
649
|
+
'string': 'string', 'number': 'number', 'integer': 'number',
|
|
650
|
+
'boolean': 'boolean', 'bool': 'boolean'
|
|
651
|
+
}
|
|
652
|
+
if (typeMap[typeStr]) {
|
|
653
|
+
return { type: typeMap[typeStr] }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return { type: typeStr }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 普通对象字面量,递归解析每个属性
|
|
660
|
+
const properties = {}
|
|
661
|
+
const required = []
|
|
662
|
+
|
|
663
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
664
|
+
properties[key] = this._parseTypeValue(value)
|
|
665
|
+
required.push(key)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return { type: 'object', properties, required }
|
|
263
669
|
}
|
|
264
670
|
|
|
265
671
|
/**
|
|
@@ -660,23 +1066,38 @@ class PythonPluginLoader extends Plugin {
|
|
|
660
1066
|
return { success: false, error: `Plugin '${pluginName}' not found` }
|
|
661
1067
|
}
|
|
662
1068
|
|
|
663
|
-
// 构建执行代码 -
|
|
1069
|
+
// 构建执行代码 - 使用数组拼接避免缩进问题
|
|
664
1070
|
const pluginDir = path.dirname(plugin.path)
|
|
665
1071
|
const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
|
|
666
1072
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
#
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
`
|
|
1073
|
+
// 从 TOOLS 提取工具名称列表,构建路由函数
|
|
1074
|
+
const toolNames = (plugin.info.tools || []).map(t => t.name)
|
|
1075
|
+
|
|
1076
|
+
const execCode = [
|
|
1077
|
+
'import sys',
|
|
1078
|
+
'import base64',
|
|
1079
|
+
'import json as _json',
|
|
1080
|
+
`sys.path.insert(0, r'${pluginDir}')`,
|
|
1081
|
+
'',
|
|
1082
|
+
'# 加载插件代码(使用 textwrap.dedent 去除缩进)',
|
|
1083
|
+
`plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')`,
|
|
1084
|
+
`import textwrap`,
|
|
1085
|
+
`exec(compile(textwrap.dedent(plugin_code), '${pluginName}.py', 'exec'))`,
|
|
1086
|
+
'',
|
|
1087
|
+
'# 工具路由函数',
|
|
1088
|
+
`_tool_map = {${toolNames.map(n => `"${n}": ${n}`).join(', ')}}`,
|
|
1089
|
+
`def execute_tool(name, params):`,
|
|
1090
|
+
` if name not in _tool_map:`,
|
|
1091
|
+
` return {"success": False, "error": f"Tool '{name}' not found in plugin '${pluginName}'"}`,
|
|
1092
|
+
` try:`,
|
|
1093
|
+
` return _tool_map[name](params)`,
|
|
1094
|
+
` except Exception as e:`,
|
|
1095
|
+
` return {"success": False, "error": str(e)}`,
|
|
1096
|
+
'',
|
|
1097
|
+
'# 执行工具',
|
|
1098
|
+
`result = execute_tool('${toolName}', ${JSON.stringify(params)})`,
|
|
1099
|
+
'print(_json.dumps(result))'
|
|
1100
|
+
].join('\n')
|
|
680
1101
|
|
|
681
1102
|
try {
|
|
682
1103
|
// 通过 python-execute 工具执行
|
|
@@ -690,13 +1111,13 @@ print(result)
|
|
|
690
1111
|
if (pythonResult.success) {
|
|
691
1112
|
try {
|
|
692
1113
|
// 尝试解析 JSON 结果
|
|
693
|
-
const parsed = JSON.parse(pythonResult.
|
|
1114
|
+
const parsed = JSON.parse(pythonResult.stdout)
|
|
694
1115
|
return parsed
|
|
695
1116
|
} catch {
|
|
696
|
-
return { success: true, output: pythonResult.
|
|
1117
|
+
return { success: true, output: pythonResult.stdout }
|
|
697
1118
|
}
|
|
698
1119
|
} else {
|
|
699
|
-
return { success: false, error: pythonResult.error }
|
|
1120
|
+
return { success: false, error: pythonResult.stderr || pythonResult.error }
|
|
700
1121
|
}
|
|
701
1122
|
} catch (err) {
|
|
702
1123
|
return { success: false, error: err.message }
|