foliko 1.1.13 → 1.1.14
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/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/poster-expert.md +135 -196
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +404 -9
- package/.agent/data/plugins-state.json +172 -173
- package/.agent/data/puppeteer-sessions/undefined.json +6 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
- package/.agent/mcp_config.json +21 -0
- package/.agent/memory/feedback/mnygjgox-ualjip.md +11 -0
- package/.agent/memory/project/mnqx54u5-loqtoe.md +9 -0
- package/.agent/memory/project/mnqx84cv-mx6dmd.md +9 -0
- package/.agent/memory/project/mnsacuyr-hgtk5n.md +20 -0
- package/.agent/memory/project/mnu5hy2x-bjsg7u.md +9 -0
- package/.agent/memory/project/mny28ot4-8qe9au.md +9 -0
- package/.agent/memory/reference/mnre3cww-penbo1.md +9 -0
- package/.agent/memory/reference/mns9wn48-luerua.md +14 -0
- package/.agent/memory/reference/mns9yz5c-thc2s0.md +16 -0
- package/.agent/memory/reference/mnsfy4um-910f1o.md +23 -0
- package/.agent/memory/reference/mnsg37dp-lmfj18.md +32 -0
- package/.agent/memory/reference/mnsll60q-0j911u.md +36 -0
- package/.agent/memory/reference/mnsmlb5y-nej31u.md +16 -0
- package/.agent/memory/reference/mnssle72-yrot96.md +9 -0
- package/.agent/memory/reference/mnygj8nb-bjthmc.md +20 -0
- package/.agent/memory/user/mnsfuon6-l416q1.md +21 -0
- package/.agent/memory/user/mnsg9kut-95m7rf.md +20 -0
- package/.agent/memory/user/mnu2eo1v-yy6fhe.md +9 -0
- package/.agent/memory/user/mnu2etuo-8u8jk8.md +9 -0
- package/.agent/memory/user/mnx0rk6g-gsznjj.md +9 -0
- package/.agent/memory/user/mnyf1riz-4yo5yz.md +9 -0
- package/.agent/plugins/puppeteer-plugin/README.md +147 -0
- package/.agent/plugins/puppeteer-plugin/index.js +1422 -0
- package/.agent/plugins/puppeteer-plugin/package.json +9 -0
- package/.agent/plugins.json +5 -11
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/sessions/cli_default.json +11 -641
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/poster-design/SKILL.md +385 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +23 -1
- package/.env.example +56 -56
- package/README.md +441 -441
- package/cli/src/commands/chat.js +9 -15
- package/cli/src/ui/chat-ui.js +41 -71
- package/package.json +1 -1
- 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 +50 -34
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/src/core/agent-chat.js +460 -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/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/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/data/email/processed-emails.json +0 -1
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/data/web/web-config.json +0 -5
- 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/package.json +0 -8
- package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
- package/.agent/plugins/daytona/README.md +0 -89
- package/.agent/plugins/daytona/index.js +0 -377
- package/.agent/plugins/daytona/package.json +0 -12
- package/.agent/plugins/marknative/README.md +0 -134
- package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/plugins/marknative/index.js +0 -256
- package/.agent/plugins/marknative/package.json +0 -12
- package/.agent/plugins/system-info/index.js +0 -387
- package/.agent/plugins/system-info/package.json +0 -4
- package/.agent/plugins/system-info/test.js +0 -40
- package/.agent/plugins/test-plugin.py +0 -123
- package/.agent/plugins/test_nested_plugin.py +0 -85
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/sessions/test.json +0 -16
- package/.agent/skills/agent-browser/SKILL.md +0 -311
- package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
- package/.agent/skills/sysinfo/SKILL.md +0 -38
- package/.agent/skills/sysinfo/system-info.sh +0 -130
- package/.agent/skills/workflow/SKILL.md +0 -324
- package/.agent/test-agent.js +0 -35
- package/.agent/weixin.json +0 -6
- package/.agent/workflows/email-digest.json +0 -50
- package/.agent/workflows/file-backup.json +0 -21
- package/.agent/workflows/get-ip-notify.json +0 -32
- package/.agent/workflows/news-aggregator.json +0 -93
- package/.agent/workflows/news-dashboard-v2.json +0 -94
- package/.agent/workflows/notification-batch.json +0 -32
- package/plugins/python-plugin-loader.js.bak +0 -856
- package/src/core/agent-context.js +0 -188
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenCounter - Token 计算工具
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 计算文本的 token 数量
|
|
6
|
+
* 2. 计算消息数组的 token 总数
|
|
7
|
+
* 3. 计算工具定义的 token 总数
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 简单的中英文混合 tokenizer
|
|
12
|
+
* 粗略估计:中文每个字符 2 字节,英文每个单词约 1.5 字节
|
|
13
|
+
* @param {string} text - 文本
|
|
14
|
+
* @param {number} bytesPerToken - 每 token 字节数,默认 4
|
|
15
|
+
* @returns {number} token 数量
|
|
16
|
+
*/
|
|
17
|
+
function encode(text, bytesPerToken = 4) {
|
|
18
|
+
if (!text) return 0;
|
|
19
|
+
const bytes = Buffer.byteLength(String(text), 'utf8');
|
|
20
|
+
return Math.ceil(bytes / bytesPerToken);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 计算 JSON 字符串的 token 数量
|
|
25
|
+
* @param {string} text - JSON 字符串
|
|
26
|
+
* @returns {number} token 数量
|
|
27
|
+
*/
|
|
28
|
+
function encodeForJSON(text) {
|
|
29
|
+
if (!text) return 0;
|
|
30
|
+
// JSON 字符串需要额外计算引号和转义
|
|
31
|
+
const encoded = encode(text);
|
|
32
|
+
return encoded + Math.ceil(Buffer.byteLength(JSON.stringify(text), 'utf8') / 100);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class TokenCounter {
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object} config - 配置
|
|
38
|
+
* @param {Object} config.toolSchema - 工具 schema(用于工具 token 计算)
|
|
39
|
+
*/
|
|
40
|
+
constructor(config = {}) {
|
|
41
|
+
this.toolSchema = config.toolSchema || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 计算文本 token
|
|
46
|
+
* @param {string} text - 文本
|
|
47
|
+
* @param {number} bytesPerToken - 每 token 字节数
|
|
48
|
+
* @returns {number} token 数量
|
|
49
|
+
*/
|
|
50
|
+
countText(text, bytesPerToken = 4) {
|
|
51
|
+
return encode(text, bytesPerToken);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 计算消息数组的 token 总数
|
|
56
|
+
* @param {Array} messages - 消息数组
|
|
57
|
+
* @returns {number} token 总数
|
|
58
|
+
*/
|
|
59
|
+
countMessages(messages) {
|
|
60
|
+
if (!Array.isArray(messages)) return 0;
|
|
61
|
+
return messages.reduce((sum, msg) => sum + this.countMessage(msg), 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 计算单条消息的 token
|
|
66
|
+
* @param {Object} msg - 消息
|
|
67
|
+
* @returns {number} token 数量
|
|
68
|
+
*/
|
|
69
|
+
countMessage(msg) {
|
|
70
|
+
if (!msg) return 0;
|
|
71
|
+
|
|
72
|
+
let total = 0;
|
|
73
|
+
|
|
74
|
+
// 角色和格式开销
|
|
75
|
+
total += 4;
|
|
76
|
+
|
|
77
|
+
if (typeof msg.content === 'string') {
|
|
78
|
+
total += this.countText(msg.content);
|
|
79
|
+
} else if (Array.isArray(msg.content)) {
|
|
80
|
+
for (const block of msg.content) {
|
|
81
|
+
total += this.countContentBlock(block);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// tool_calls 开销
|
|
86
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
87
|
+
total += 15; // overhead per tool_calls block
|
|
88
|
+
for (const tc of msg.tool_calls) {
|
|
89
|
+
if (tc.function) {
|
|
90
|
+
total += this.countText(tc.function.name) + this.countText(tc.function.arguments);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// tool_call_id 开销
|
|
96
|
+
if (msg.tool_call_id) {
|
|
97
|
+
total += 15;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return total;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 计算 content block 的 token
|
|
105
|
+
* @param {Object} block - content block
|
|
106
|
+
* @returns {number} token 数量
|
|
107
|
+
*/
|
|
108
|
+
countContentBlock(block) {
|
|
109
|
+
if (!block) return 0;
|
|
110
|
+
|
|
111
|
+
switch (block.type) {
|
|
112
|
+
case 'text':
|
|
113
|
+
return this.countText(block.text);
|
|
114
|
+
case 'tool-call':
|
|
115
|
+
case 'tool-use':
|
|
116
|
+
if (block.input) {
|
|
117
|
+
return this.countText(JSON.stringify(block.input));
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
case 'tool-result':
|
|
121
|
+
case 'tool_result':
|
|
122
|
+
if (block.content) {
|
|
123
|
+
const content =
|
|
124
|
+
typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
125
|
+
return this.countText(content);
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
case 'image':
|
|
129
|
+
// 图片按 token 估算
|
|
130
|
+
return 85;
|
|
131
|
+
default:
|
|
132
|
+
return this.countText(JSON.stringify(block));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 计算工具定义的 token 总数
|
|
138
|
+
* @param {Array} tools - 工具数组
|
|
139
|
+
* @returns {number} token 总数
|
|
140
|
+
*/
|
|
141
|
+
countTools(tools) {
|
|
142
|
+
if (!tools || !Array.isArray(tools)) return 0;
|
|
143
|
+
|
|
144
|
+
let total = 0;
|
|
145
|
+
for (const tool of tools) {
|
|
146
|
+
// 工具名和描述
|
|
147
|
+
total += 20; // overhead
|
|
148
|
+
|
|
149
|
+
if (tool.description) {
|
|
150
|
+
total += this.countText(tool.description);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 参数
|
|
154
|
+
if (tool.inputSchema) {
|
|
155
|
+
const schema =
|
|
156
|
+
tool.inputSchema.jsonSchema || tool.inputSchema.inputSchema || tool.inputSchema;
|
|
157
|
+
if (schema.properties) {
|
|
158
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
159
|
+
total += this.countText(name) + 10;
|
|
160
|
+
if (prop.description) {
|
|
161
|
+
total += this.countText(prop.description);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return total;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 估算完整请求的 token(消息 + 工具 + 系统提示)
|
|
173
|
+
* @param {Object} params - 请求参数
|
|
174
|
+
* @returns {Object} - { messagesTokens, toolsTokens, systemPromptTokens, total }
|
|
175
|
+
*/
|
|
176
|
+
estimateRequest({ messages, tools, systemPrompt }) {
|
|
177
|
+
const messagesTokens = this.countMessages(messages);
|
|
178
|
+
const toolsTokens = this.countTools(tools);
|
|
179
|
+
const systemPromptTokens = systemPrompt ? this.countText(systemPrompt) : 0;
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
messagesTokens,
|
|
183
|
+
toolsTokens,
|
|
184
|
+
systemPromptTokens,
|
|
185
|
+
total: messagesTokens + toolsTokens + systemPromptTokens,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { TokenCounter, encode, encodeForJSON };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExecutor - 工具执行器
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 工具发现和注册
|
|
6
|
+
* 2. 工具执行
|
|
7
|
+
* 3. 工具调用验证
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { EventEmitter } = require('../utils/event-emitter');
|
|
11
|
+
const { logger } = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
class ToolExecutor extends EventEmitter {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} config - 配置
|
|
16
|
+
*/
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.agent = config.agent;
|
|
22
|
+
this.framework = config.framework;
|
|
23
|
+
|
|
24
|
+
// 工具注册表: name -> toolDef
|
|
25
|
+
this._tools = new Map();
|
|
26
|
+
|
|
27
|
+
// 工具调用统计
|
|
28
|
+
this._toolStats = {
|
|
29
|
+
totalCalls: 0,
|
|
30
|
+
failedCalls: 0,
|
|
31
|
+
lastCall: null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 注册工具
|
|
37
|
+
* @param {Object} tool - 工具定义
|
|
38
|
+
*/
|
|
39
|
+
registerTool(tool) {
|
|
40
|
+
if (!tool || !tool.name) {
|
|
41
|
+
logger.warn('ToolExecutor', 'Ignoring tool with no name');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this._tools.set(tool.name, tool);
|
|
45
|
+
logger.debug('ToolExecutor', `Registered tool: ${tool.name}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 批量注册工具
|
|
50
|
+
* @param {Array} tools - 工具数组
|
|
51
|
+
*/
|
|
52
|
+
registerTools(tools) {
|
|
53
|
+
if (!Array.isArray(tools)) return;
|
|
54
|
+
for (const tool of tools) {
|
|
55
|
+
this.registerTool(tool);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 注销工具
|
|
61
|
+
* @param {string} name - 工具名
|
|
62
|
+
*/
|
|
63
|
+
unregisterTool(name) {
|
|
64
|
+
this._tools.delete(name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 获取工具
|
|
69
|
+
* @param {string} name - 工具名
|
|
70
|
+
* @returns {Object|null}
|
|
71
|
+
*/
|
|
72
|
+
getTool(name) {
|
|
73
|
+
return this._tools.get(name) || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 获取所有工具
|
|
78
|
+
* @returns {Array}
|
|
79
|
+
*/
|
|
80
|
+
getAllTools() {
|
|
81
|
+
return Array.from(this._tools.values());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 检查工具是否存在
|
|
86
|
+
* @param {string} name - 工具名
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
hasTool(name) {
|
|
90
|
+
return this._tools.has(name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 执行工具
|
|
95
|
+
* @param {string} name - 工具名
|
|
96
|
+
* @param {Object} args - 参数
|
|
97
|
+
* @param {Object} options - 选项
|
|
98
|
+
* @returns {Promise}
|
|
99
|
+
*/
|
|
100
|
+
async executeTool(name, args = {}, options = {}) {
|
|
101
|
+
const tool = this._tools.get(name);
|
|
102
|
+
if (!tool) {
|
|
103
|
+
const error = `Tool '${name}' not found`;
|
|
104
|
+
logger.warn('ToolExecutor', error);
|
|
105
|
+
throw new Error(error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._toolStats.totalCalls++;
|
|
109
|
+
this._toolStats.lastCall = {
|
|
110
|
+
name,
|
|
111
|
+
args,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
this.emit('tool:call', { name, args, source: options.source });
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// 统一 execute 签名为 (args, framework)
|
|
119
|
+
const result = await tool.execute(args, this.framework);
|
|
120
|
+
|
|
121
|
+
this.emit('tool:result', { name, args, result, source: options.source });
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
this._toolStats.failedCalls++;
|
|
126
|
+
logger.error('ToolExecutor', `Tool '${name}' failed:`, err.message);
|
|
127
|
+
|
|
128
|
+
this.emit('tool:error', {
|
|
129
|
+
name,
|
|
130
|
+
args,
|
|
131
|
+
error: err.message,
|
|
132
|
+
source: options.source,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 批量执行工具
|
|
141
|
+
* @param {Array} tools - 工具调用数组 [{name, args}, ...]
|
|
142
|
+
* @param {Object} options - 选项
|
|
143
|
+
* @returns {Promise<Array>}
|
|
144
|
+
*/
|
|
145
|
+
async executeTools(tools, options = {}) {
|
|
146
|
+
if (!Array.isArray(tools)) {
|
|
147
|
+
throw new Error('tools must be an array');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const results = [];
|
|
151
|
+
for (const toolCall of tools) {
|
|
152
|
+
try {
|
|
153
|
+
const result = await this.executeTool(toolCall.name, toolCall.args || {}, options);
|
|
154
|
+
results.push({ success: true, name: toolCall.name, result });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
results.push({ success: false, name: toolCall.name, error: err.message });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取工具的 AI 格式(用于发送给 AI)
|
|
164
|
+
* @returns {Object} AI SDK 格式的工具对象
|
|
165
|
+
*/
|
|
166
|
+
getToolsForAI() {
|
|
167
|
+
const tools = {};
|
|
168
|
+
for (const [name, tool] of this._tools) {
|
|
169
|
+
if (!tool.description) continue;
|
|
170
|
+
|
|
171
|
+
tools[name] = {
|
|
172
|
+
description: tool.description,
|
|
173
|
+
inputSchema: tool.inputSchema,
|
|
174
|
+
execute: tool.execute ? tool.execute.bind(tool) : undefined,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return tools;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 验证工具调用的参数
|
|
182
|
+
* @param {Array} messages - 消息数组
|
|
183
|
+
* @returns {Array} 验证后的消息
|
|
184
|
+
*/
|
|
185
|
+
validateToolCalls(messages) {
|
|
186
|
+
let fixedCount = 0;
|
|
187
|
+
// 收集被跳过的 toolCallId,用于清理对应的 tool-result
|
|
188
|
+
const invalidatedToolCallIds = new Set();
|
|
189
|
+
|
|
190
|
+
for (const msg of messages) {
|
|
191
|
+
// 清理 assistant 消息中的不完整 tool-call
|
|
192
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
193
|
+
for (const item of msg.content) {
|
|
194
|
+
// 兼容 tool-call 和 tool-use 两种类型
|
|
195
|
+
if (item.type !== 'tool-call' && item.type !== 'tool-use') {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const input = item.input;
|
|
200
|
+
if (typeof input !== 'string') {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 检查 input 是否是有效的 JSON(不是不完整的)
|
|
205
|
+
const trimmed = input.trim();
|
|
206
|
+
if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
|
|
207
|
+
// 不完整的 JSON,移除这个 tool-call
|
|
208
|
+
// 记录 toolCallId,以便后续清理对应的 tool-result
|
|
209
|
+
if (item.toolCallId) {
|
|
210
|
+
invalidatedToolCallIds.add(item.toolCallId);
|
|
211
|
+
}
|
|
212
|
+
logger.warn(
|
|
213
|
+
`_validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}, converting to text`
|
|
214
|
+
);
|
|
215
|
+
item.type = 'text';
|
|
216
|
+
item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
|
|
217
|
+
delete item.toolCallId;
|
|
218
|
+
delete item.toolName;
|
|
219
|
+
delete item.input;
|
|
220
|
+
fixedCount++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 如果有无效的 tool-call,清理对应的 tool-result
|
|
227
|
+
if (invalidatedToolCallIds.size > 0) {
|
|
228
|
+
logger.warn(
|
|
229
|
+
`_validateToolCalls: removing ${invalidatedToolCallIds.size} tool-results with invalidated toolCallIds`
|
|
230
|
+
);
|
|
231
|
+
for (const msg of messages) {
|
|
232
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
233
|
+
// 过滤掉引用了无效 toolCallId 的 tool-result
|
|
234
|
+
const oldLen = msg.content.length;
|
|
235
|
+
msg.content = msg.content.filter((item) => {
|
|
236
|
+
if (item.type !== 'tool-result' && item.type !== 'tool_result') {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
// 如果 tool-result 引用的 toolCallId 已被标记为无效,则移除
|
|
240
|
+
if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
|
|
241
|
+
logger.warn(
|
|
242
|
+
`_validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
|
|
243
|
+
);
|
|
244
|
+
fixedCount++;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (fixedCount > 0) {
|
|
254
|
+
logger.info(`_validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 获取工具统计
|
|
260
|
+
* @returns {Object}
|
|
261
|
+
*/
|
|
262
|
+
getStats() {
|
|
263
|
+
return {
|
|
264
|
+
...this._toolStats,
|
|
265
|
+
toolCount: this._tools.size,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = { ToolExecutor };
|
|
@@ -37,6 +37,10 @@ class MCPClientWrapper {
|
|
|
37
37
|
async connect() {
|
|
38
38
|
if (this.connected) return;
|
|
39
39
|
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutMs = this.timeout || 30000;
|
|
42
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
43
|
+
|
|
40
44
|
try {
|
|
41
45
|
// 动态导入 @ai-sdk/mcp
|
|
42
46
|
let createMCPClient;
|
|
@@ -63,7 +67,10 @@ class MCPClientWrapper {
|
|
|
63
67
|
args: shellResolve ? [shellResolve, ...this.args] : this.args,
|
|
64
68
|
env: { ...process.env, ...this.env },
|
|
65
69
|
});
|
|
66
|
-
this.client = await createMCPClient({
|
|
70
|
+
this.client = await createMCPClient({
|
|
71
|
+
transport,
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
67
74
|
} catch (transportErr) {
|
|
68
75
|
log.error(` Transport error:`, transportErr.message);
|
|
69
76
|
throw transportErr;
|
|
@@ -85,8 +92,14 @@ class MCPClientWrapper {
|
|
|
85
92
|
this.connected = true;
|
|
86
93
|
log.info(` Connected to ${this.serverName} with ${this.tools.length} tools`);
|
|
87
94
|
} catch (err) {
|
|
95
|
+
if (controller.signal.aborted) {
|
|
96
|
+
log.error(` Connection timeout (${timeoutMs}ms) for ${this.serverName}`);
|
|
97
|
+
throw new Error(`连接超时 (${timeoutMs}ms): ${this.serverName}`);
|
|
98
|
+
}
|
|
88
99
|
log.error(` Failed to connect to ${this.serverName}:`, err.message);
|
|
89
100
|
// 不抛出错误,让框架继续运行
|
|
101
|
+
} finally {
|
|
102
|
+
clearTimeout(timeoutId);
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|