foliko 1.0.48 → 1.0.50
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/.claude/settings.local.json +2 -1
- package/examples/basic.js +110 -110
- package/examples/mcp-example.js +53 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-mcp.js +79 -79
- package/examples/test-reload.js +61 -61
- package/package.json +2 -1
- package/plugins/email.js +73 -2
- package/plugins/session-plugin.js +1 -1
- package/plugins/web-plugin.js +13 -2
- package/src/core/agent-chat.js +185 -2
- package/src/executors/executor-base.js +58 -58
- package/test-server.js +25 -25
- package/test.txt +3 -3
|
@@ -116,7 +116,8 @@
|
|
|
116
116
|
"Bash(taskkill //F //PID 19848)",
|
|
117
117
|
"Bash(curl -s -X POST \"http://localhost:3000/webhook/075s5s2umn4smn4f\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"test\":\"data\"}' 2>&1)",
|
|
118
118
|
"Bash(curl -s http://localhost:3000/api/hello 2>&1)",
|
|
119
|
-
"Bash(curl -v -X POST \"http://localhost:3000/webhook/075s5s2umn4smn4f\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"test\":\"data\"}' 2>&1 | head -30)"
|
|
119
|
+
"Bash(curl -v -X POST \"http://localhost:3000/webhook/075s5s2umn4smn4f\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"test\":\"data\"}' 2>&1 | head -30)",
|
|
120
|
+
"Bash(cd D:/code/vb-agent && timeout 30 node test-compression.js 2>&1)"
|
|
120
121
|
]
|
|
121
122
|
}
|
|
122
123
|
}
|
package/examples/basic.js
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 基础示例
|
|
3
|
-
* 展示如何使用 Framework 和 Agent
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { Framework } = require('../src')
|
|
7
|
-
const { AIPlugin } = require('../plugins/ai-plugin')
|
|
8
|
-
const { z } = require('zod')
|
|
9
|
-
|
|
10
|
-
async function main() {
|
|
11
|
-
// 创建框架实例
|
|
12
|
-
const framework = new Framework({ debug: true })
|
|
13
|
-
|
|
14
|
-
// 加载 AI 插件
|
|
15
|
-
await framework.loadPlugin(new AIPlugin({
|
|
16
|
-
provider: 'deepseek',
|
|
17
|
-
model: 'deepseek-chat',
|
|
18
|
-
apiKey: process.env.DEEPSEEK_API_KEY || 'your-api-key'
|
|
19
|
-
}))
|
|
20
|
-
|
|
21
|
-
// 注册自定义工具(使用 inputSchema 格式)
|
|
22
|
-
framework.registerTool({
|
|
23
|
-
name: 'hello',
|
|
24
|
-
description: '打招呼工具',
|
|
25
|
-
inputSchema: z.object({
|
|
26
|
-
name: z.string().optional().describe('姓名')
|
|
27
|
-
}),
|
|
28
|
-
execute: async (args) => {
|
|
29
|
-
return `Hello, ${args.name || 'World'}!`
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// 注册计算器工具
|
|
34
|
-
framework.registerTool({
|
|
35
|
-
name: 'calculate',
|
|
36
|
-
description: '简单的计算器',
|
|
37
|
-
inputSchema: z.object({
|
|
38
|
-
expression: z.string().describe('数学表达式,如 2+3*4')
|
|
39
|
-
}),
|
|
40
|
-
execute: async (args) => {
|
|
41
|
-
try {
|
|
42
|
-
// 安全计算(仅支持基本运算)
|
|
43
|
-
const result = Function(`"use strict"; return (${args.expression})`)()
|
|
44
|
-
return { result }
|
|
45
|
-
} catch (e) {
|
|
46
|
-
return { error: e.message }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
console.log('[Framework] Ready!')
|
|
52
|
-
console.log('[Tools]', framework.getTools().map(t => t.name))
|
|
53
|
-
|
|
54
|
-
// 创建 Agent
|
|
55
|
-
const agent = framework.createAgent({
|
|
56
|
-
name: 'MyAgent',
|
|
57
|
-
systemPrompt: '你是一个有帮助的助手。当需要计算时,使用 calculate 工具。'
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// 监听事件
|
|
61
|
-
agent.on('tool-call', (tool) => {
|
|
62
|
-
console.log('[Agent] Tool call:', tool.name, tool.args)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
agent.on('tool-result', (result) => {
|
|
66
|
-
console.log('[Agent] Tool result:', result.name, result.result)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// AI 对话示例
|
|
70
|
-
console.log('\n=== AI Chat Example ===')
|
|
71
|
-
try {
|
|
72
|
-
const response = await agent.chat('你好!')
|
|
73
|
-
console.log('[Agent] Response:', response.message)
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error('[Agent] Error:', err.message)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 使用工具的对话示例
|
|
79
|
-
console.log('\n=== AI Chat with Tool Call ===')
|
|
80
|
-
try {
|
|
81
|
-
const response = await agent.chat('请帮我计算 (15 + 25) * 2 等于多少?')
|
|
82
|
-
console.log('[Agent] Response:', response.message)
|
|
83
|
-
} catch (err) {
|
|
84
|
-
console.error('[Agent] Error:', err.message)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 流式对话示例
|
|
88
|
-
console.log('\n=== Streaming Chat ===')
|
|
89
|
-
try {
|
|
90
|
-
for await (const chunk of agent.chatStream('请用中文介绍一下你自己')) {
|
|
91
|
-
if (chunk.type === 'text') {
|
|
92
|
-
process.stdout.write(chunk.text)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
console.log('\n')
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.error('[Agent] Stream Error:', err.message)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 热重载示例
|
|
101
|
-
console.log('\n=== Hot Reload ===')
|
|
102
|
-
await framework.reloadPlugin('ai')
|
|
103
|
-
console.log('AI plugin reloaded!')
|
|
104
|
-
|
|
105
|
-
// 清理
|
|
106
|
-
await framework.destroy()
|
|
107
|
-
console.log('\n[Done]')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
main().catch(console.error)
|
|
1
|
+
/**
|
|
2
|
+
* 基础示例
|
|
3
|
+
* 展示如何使用 Framework 和 Agent
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Framework } = require('../src')
|
|
7
|
+
const { AIPlugin } = require('../plugins/ai-plugin')
|
|
8
|
+
const { z } = require('zod')
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
// 创建框架实例
|
|
12
|
+
const framework = new Framework({ debug: true })
|
|
13
|
+
|
|
14
|
+
// 加载 AI 插件
|
|
15
|
+
await framework.loadPlugin(new AIPlugin({
|
|
16
|
+
provider: 'deepseek',
|
|
17
|
+
model: 'deepseek-chat',
|
|
18
|
+
apiKey: process.env.DEEPSEEK_API_KEY || 'your-api-key'
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
// 注册自定义工具(使用 inputSchema 格式)
|
|
22
|
+
framework.registerTool({
|
|
23
|
+
name: 'hello',
|
|
24
|
+
description: '打招呼工具',
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
name: z.string().optional().describe('姓名')
|
|
27
|
+
}),
|
|
28
|
+
execute: async (args) => {
|
|
29
|
+
return `Hello, ${args.name || 'World'}!`
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// 注册计算器工具
|
|
34
|
+
framework.registerTool({
|
|
35
|
+
name: 'calculate',
|
|
36
|
+
description: '简单的计算器',
|
|
37
|
+
inputSchema: z.object({
|
|
38
|
+
expression: z.string().describe('数学表达式,如 2+3*4')
|
|
39
|
+
}),
|
|
40
|
+
execute: async (args) => {
|
|
41
|
+
try {
|
|
42
|
+
// 安全计算(仅支持基本运算)
|
|
43
|
+
const result = Function(`"use strict"; return (${args.expression})`)()
|
|
44
|
+
return { result }
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return { error: e.message }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
console.log('[Framework] Ready!')
|
|
52
|
+
console.log('[Tools]', framework.getTools().map(t => t.name))
|
|
53
|
+
|
|
54
|
+
// 创建 Agent
|
|
55
|
+
const agent = framework.createAgent({
|
|
56
|
+
name: 'MyAgent',
|
|
57
|
+
systemPrompt: '你是一个有帮助的助手。当需要计算时,使用 calculate 工具。'
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 监听事件
|
|
61
|
+
agent.on('tool-call', (tool) => {
|
|
62
|
+
console.log('[Agent] Tool call:', tool.name, tool.args)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
agent.on('tool-result', (result) => {
|
|
66
|
+
console.log('[Agent] Tool result:', result.name, result.result)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// AI 对话示例
|
|
70
|
+
console.log('\n=== AI Chat Example ===')
|
|
71
|
+
try {
|
|
72
|
+
const response = await agent.chat('你好!')
|
|
73
|
+
console.log('[Agent] Response:', response.message)
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('[Agent] Error:', err.message)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 使用工具的对话示例
|
|
79
|
+
console.log('\n=== AI Chat with Tool Call ===')
|
|
80
|
+
try {
|
|
81
|
+
const response = await agent.chat('请帮我计算 (15 + 25) * 2 等于多少?')
|
|
82
|
+
console.log('[Agent] Response:', response.message)
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error('[Agent] Error:', err.message)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 流式对话示例
|
|
88
|
+
console.log('\n=== Streaming Chat ===')
|
|
89
|
+
try {
|
|
90
|
+
for await (const chunk of agent.chatStream('请用中文介绍一下你自己')) {
|
|
91
|
+
if (chunk.type === 'text') {
|
|
92
|
+
process.stdout.write(chunk.text)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
console.log('\n')
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error('[Agent] Stream Error:', err.message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 热重载示例
|
|
101
|
+
console.log('\n=== Hot Reload ===')
|
|
102
|
+
await framework.reloadPlugin('ai')
|
|
103
|
+
console.log('AI plugin reloaded!')
|
|
104
|
+
|
|
105
|
+
// 清理
|
|
106
|
+
await framework.destroy()
|
|
107
|
+
console.log('\n[Done]')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch(console.error)
|
package/examples/mcp-example.js
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP 执行器示例
|
|
3
|
-
* 展示如何连接 MCP 服务器
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { Framework } = require('../src')
|
|
7
|
-
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
8
|
-
|
|
9
|
-
async function main() {
|
|
10
|
-
console.log('=== MCP Executor Example ===\n')
|
|
11
|
-
|
|
12
|
-
// 创建框架
|
|
13
|
-
const framework = new Framework({ debug: true })
|
|
14
|
-
|
|
15
|
-
// 创建 MCP 执行器插件
|
|
16
|
-
const mcpPlugin = new MCPExecutorPlugin({
|
|
17
|
-
servers: [
|
|
18
|
-
// 示例:添加一个 MCP 服务器
|
|
19
|
-
// {
|
|
20
|
-
// name: 'example',
|
|
21
|
-
// command: 'uvx',
|
|
22
|
-
// args: ['example-mcp-server'],
|
|
23
|
-
// env: {}
|
|
24
|
-
// }
|
|
25
|
-
]
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// 加载插件
|
|
29
|
-
await framework.loadPlugin(mcpPlugin)
|
|
30
|
-
|
|
31
|
-
// 列出服务器
|
|
32
|
-
console.log('\n--- MCP Servers ---')
|
|
33
|
-
const servers = mcpPlugin.getServers()
|
|
34
|
-
console.log('Servers:', servers)
|
|
35
|
-
|
|
36
|
-
// 列出所有 MCP 工具
|
|
37
|
-
console.log('\n--- MCP Tools ---')
|
|
38
|
-
const tools = framework.getTools().filter(t => t.name.startsWith('mcp_'))
|
|
39
|
-
console.log('MCP tools:', tools.map(t => t.name))
|
|
40
|
-
|
|
41
|
-
// 如果有配置的服务器,可以这样调用:
|
|
42
|
-
// const result = await framework.executeTool('mcp_call', {
|
|
43
|
-
// server: 'example',
|
|
44
|
-
// tool: 'tool_name',
|
|
45
|
-
// args: { /* tool arguments */ }
|
|
46
|
-
// })
|
|
47
|
-
|
|
48
|
-
// 清理
|
|
49
|
-
await framework.destroy()
|
|
50
|
-
console.log('\n[Done]')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
main().catch(console.error)
|
|
1
|
+
/**
|
|
2
|
+
* MCP 执行器示例
|
|
3
|
+
* 展示如何连接 MCP 服务器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Framework } = require('../src')
|
|
7
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.log('=== MCP Executor Example ===\n')
|
|
11
|
+
|
|
12
|
+
// 创建框架
|
|
13
|
+
const framework = new Framework({ debug: true })
|
|
14
|
+
|
|
15
|
+
// 创建 MCP 执行器插件
|
|
16
|
+
const mcpPlugin = new MCPExecutorPlugin({
|
|
17
|
+
servers: [
|
|
18
|
+
// 示例:添加一个 MCP 服务器
|
|
19
|
+
// {
|
|
20
|
+
// name: 'example',
|
|
21
|
+
// command: 'uvx',
|
|
22
|
+
// args: ['example-mcp-server'],
|
|
23
|
+
// env: {}
|
|
24
|
+
// }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// 加载插件
|
|
29
|
+
await framework.loadPlugin(mcpPlugin)
|
|
30
|
+
|
|
31
|
+
// 列出服务器
|
|
32
|
+
console.log('\n--- MCP Servers ---')
|
|
33
|
+
const servers = mcpPlugin.getServers()
|
|
34
|
+
console.log('Servers:', servers)
|
|
35
|
+
|
|
36
|
+
// 列出所有 MCP 工具
|
|
37
|
+
console.log('\n--- MCP Tools ---')
|
|
38
|
+
const tools = framework.getTools().filter(t => t.name.startsWith('mcp_'))
|
|
39
|
+
console.log('MCP tools:', tools.map(t => t.name))
|
|
40
|
+
|
|
41
|
+
// 如果有配置的服务器,可以这样调用:
|
|
42
|
+
// const result = await framework.executeTool('mcp_call', {
|
|
43
|
+
// server: 'example',
|
|
44
|
+
// tool: 'tool_name',
|
|
45
|
+
// args: { /* tool arguments */ }
|
|
46
|
+
// })
|
|
47
|
+
|
|
48
|
+
// 清理
|
|
49
|
+
await framework.destroy()
|
|
50
|
+
console.log('\n[Done]')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main().catch(console.error)
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill 管理器示例
|
|
3
|
-
* 展示如何加载和使用 Skill
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { Framework } = require('../src')
|
|
7
|
-
const { SkillManagerPlugin } = require('../src/capabilities/skill-manager')
|
|
8
|
-
|
|
9
|
-
async function main() {
|
|
10
|
-
console.log('=== Skill Manager Example ===\n')
|
|
11
|
-
|
|
12
|
-
// 创建框架
|
|
13
|
-
const framework = new Framework({ debug: true })
|
|
14
|
-
|
|
15
|
-
// 创建 skills 目录(如果没有)
|
|
16
|
-
const fs = require('fs')
|
|
17
|
-
const skillsDir = './skills'
|
|
18
|
-
if (!fs.existsSync(skillsDir)) {
|
|
19
|
-
fs.mkdirSync(skillsDir, { recursive: true })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 加载 Skill 管理器插件
|
|
23
|
-
const skillPlugin = new SkillManagerPlugin({
|
|
24
|
-
skillsDir: skillsDir
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
await framework.loadPlugin(skillPlugin)
|
|
28
|
-
|
|
29
|
-
// 列出所有加载的 skills
|
|
30
|
-
console.log('\n--- Loaded Skills ---')
|
|
31
|
-
const skills = skillPlugin.getAllSkills()
|
|
32
|
-
console.log(`Found ${skills.length} skills:`)
|
|
33
|
-
for (const skill of skills) {
|
|
34
|
-
console.log(` - ${skill.name}: ${skill.metadata.description}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 获取单个 skill
|
|
38
|
-
if (skillPlugin.hasSkill('hello-skill')) {
|
|
39
|
-
console.log('\n--- Hello Skill ---')
|
|
40
|
-
const helloSkill = skillPlugin.getSkill('hello-skill')
|
|
41
|
-
console.log('Content preview:', helloSkill.content.substring(0, 100) + '...')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 清理
|
|
45
|
-
await framework.destroy()
|
|
46
|
-
console.log('\n[Done]')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
main().catch(console.error)
|
|
1
|
+
/**
|
|
2
|
+
* Skill 管理器示例
|
|
3
|
+
* 展示如何加载和使用 Skill
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Framework } = require('../src')
|
|
7
|
+
const { SkillManagerPlugin } = require('../src/capabilities/skill-manager')
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.log('=== Skill Manager Example ===\n')
|
|
11
|
+
|
|
12
|
+
// 创建框架
|
|
13
|
+
const framework = new Framework({ debug: true })
|
|
14
|
+
|
|
15
|
+
// 创建 skills 目录(如果没有)
|
|
16
|
+
const fs = require('fs')
|
|
17
|
+
const skillsDir = './skills'
|
|
18
|
+
if (!fs.existsSync(skillsDir)) {
|
|
19
|
+
fs.mkdirSync(skillsDir, { recursive: true })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 加载 Skill 管理器插件
|
|
23
|
+
const skillPlugin = new SkillManagerPlugin({
|
|
24
|
+
skillsDir: skillsDir
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
await framework.loadPlugin(skillPlugin)
|
|
28
|
+
|
|
29
|
+
// 列出所有加载的 skills
|
|
30
|
+
console.log('\n--- Loaded Skills ---')
|
|
31
|
+
const skills = skillPlugin.getAllSkills()
|
|
32
|
+
console.log(`Found ${skills.length} skills:`)
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
console.log(` - ${skill.name}: ${skill.metadata.description}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 获取单个 skill
|
|
38
|
+
if (skillPlugin.hasSkill('hello-skill')) {
|
|
39
|
+
console.log('\n--- Hello Skill ---')
|
|
40
|
+
const helloSkill = skillPlugin.getSkill('hello-skill')
|
|
41
|
+
console.log('Content preview:', helloSkill.content.substring(0, 100) + '...')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 清理
|
|
45
|
+
await framework.destroy()
|
|
46
|
+
console.log('\n[Done]')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch(console.error)
|
package/examples/test-mcp.js
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP 插件测试脚本
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { Framework } = require('../src')
|
|
6
|
-
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
|
-
|
|
8
|
-
async function test() {
|
|
9
|
-
console.log('=== MCP Plugin Test ===\n')
|
|
10
|
-
|
|
11
|
-
console.log('1. Creating framework...')
|
|
12
|
-
const framework = new Framework({ debug: true })
|
|
13
|
-
|
|
14
|
-
console.log('2. Creating MCP plugin with fetch server...')
|
|
15
|
-
const mcpPlugin = new MCPExecutorPlugin({
|
|
16
|
-
servers: [
|
|
17
|
-
{
|
|
18
|
-
name: 'fetch',
|
|
19
|
-
command: 'uvx',
|
|
20
|
-
args: ['mcp-server-fetch']
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
console.log('3. Loading MCP plugin...')
|
|
26
|
-
await framework.loadPlugin(mcpPlugin)
|
|
27
|
-
|
|
28
|
-
console.log('4. Starting framework...')
|
|
29
|
-
await framework.pluginManager.startAll()
|
|
30
|
-
|
|
31
|
-
// 等待 MCP 服务器连接
|
|
32
|
-
console.log('\n5. Waiting for MCP connection...')
|
|
33
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
34
|
-
|
|
35
|
-
console.log('\n6. Listing MCP servers...')
|
|
36
|
-
const servers = mcpPlugin.getServers()
|
|
37
|
-
console.log('Servers:', JSON.stringify(servers, null, 2))
|
|
38
|
-
|
|
39
|
-
console.log('\n7. Listing MCP tools...')
|
|
40
|
-
const mcpTools = framework.getTools().filter(t => t.name.startsWith('mcp_'))
|
|
41
|
-
console.log('MCP Tools:', mcpTools.map(t => t.name))
|
|
42
|
-
|
|
43
|
-
if (mcpTools.length > 0) {
|
|
44
|
-
console.log('\n8. Testing mcp_list_servers...')
|
|
45
|
-
const listResult = await framework.executeTool('mcp_list_servers', {})
|
|
46
|
-
console.log('List Servers Result:', JSON.stringify(listResult, null, 2))
|
|
47
|
-
|
|
48
|
-
console.log('\n9. Testing mcp_tool_schema...')
|
|
49
|
-
const schemaResult = await framework.executeTool('mcp_tool_schema', {
|
|
50
|
-
server: 'fetch',
|
|
51
|
-
tool: 'fetch'
|
|
52
|
-
})
|
|
53
|
-
console.log('Tool Schema:', JSON.stringify(schemaResult, null, 2))
|
|
54
|
-
|
|
55
|
-
console.log('\n10. Testing mcp_call (fetch a webpage)...')
|
|
56
|
-
const callResult = await framework.executeTool('mcp_call', {
|
|
57
|
-
server: 'fetch',
|
|
58
|
-
tool: 'fetch',
|
|
59
|
-
args_json: JSON.stringify({ url: 'https://httpbin.org/get' })
|
|
60
|
-
})
|
|
61
|
-
console.log('Fetch Result (truncated):', JSON.stringify(callResult, null, 2).substring(0, 500) + '...')
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
console.log('\n11. Destroying framework...')
|
|
65
|
-
await framework.destroy()
|
|
66
|
-
|
|
67
|
-
console.log('\n=== Test Complete ===')
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
test()
|
|
71
|
-
.then(() => {
|
|
72
|
-
console.log('\n✓ Test completed successfully')
|
|
73
|
-
process.exit(0)
|
|
74
|
-
})
|
|
75
|
-
.catch(err => {
|
|
76
|
-
console.error('\n✗ Test failed:', err.message)
|
|
77
|
-
console.error(err.stack)
|
|
78
|
-
process.exit(1)
|
|
79
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* MCP 插件测试脚本
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Framework } = require('../src')
|
|
6
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
|
+
|
|
8
|
+
async function test() {
|
|
9
|
+
console.log('=== MCP Plugin Test ===\n')
|
|
10
|
+
|
|
11
|
+
console.log('1. Creating framework...')
|
|
12
|
+
const framework = new Framework({ debug: true })
|
|
13
|
+
|
|
14
|
+
console.log('2. Creating MCP plugin with fetch server...')
|
|
15
|
+
const mcpPlugin = new MCPExecutorPlugin({
|
|
16
|
+
servers: [
|
|
17
|
+
{
|
|
18
|
+
name: 'fetch',
|
|
19
|
+
command: 'uvx',
|
|
20
|
+
args: ['mcp-server-fetch']
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
console.log('3. Loading MCP plugin...')
|
|
26
|
+
await framework.loadPlugin(mcpPlugin)
|
|
27
|
+
|
|
28
|
+
console.log('4. Starting framework...')
|
|
29
|
+
await framework.pluginManager.startAll()
|
|
30
|
+
|
|
31
|
+
// 等待 MCP 服务器连接
|
|
32
|
+
console.log('\n5. Waiting for MCP connection...')
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
34
|
+
|
|
35
|
+
console.log('\n6. Listing MCP servers...')
|
|
36
|
+
const servers = mcpPlugin.getServers()
|
|
37
|
+
console.log('Servers:', JSON.stringify(servers, null, 2))
|
|
38
|
+
|
|
39
|
+
console.log('\n7. Listing MCP tools...')
|
|
40
|
+
const mcpTools = framework.getTools().filter(t => t.name.startsWith('mcp_'))
|
|
41
|
+
console.log('MCP Tools:', mcpTools.map(t => t.name))
|
|
42
|
+
|
|
43
|
+
if (mcpTools.length > 0) {
|
|
44
|
+
console.log('\n8. Testing mcp_list_servers...')
|
|
45
|
+
const listResult = await framework.executeTool('mcp_list_servers', {})
|
|
46
|
+
console.log('List Servers Result:', JSON.stringify(listResult, null, 2))
|
|
47
|
+
|
|
48
|
+
console.log('\n9. Testing mcp_tool_schema...')
|
|
49
|
+
const schemaResult = await framework.executeTool('mcp_tool_schema', {
|
|
50
|
+
server: 'fetch',
|
|
51
|
+
tool: 'fetch'
|
|
52
|
+
})
|
|
53
|
+
console.log('Tool Schema:', JSON.stringify(schemaResult, null, 2))
|
|
54
|
+
|
|
55
|
+
console.log('\n10. Testing mcp_call (fetch a webpage)...')
|
|
56
|
+
const callResult = await framework.executeTool('mcp_call', {
|
|
57
|
+
server: 'fetch',
|
|
58
|
+
tool: 'fetch',
|
|
59
|
+
args_json: JSON.stringify({ url: 'https://httpbin.org/get' })
|
|
60
|
+
})
|
|
61
|
+
console.log('Fetch Result (truncated):', JSON.stringify(callResult, null, 2).substring(0, 500) + '...')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('\n11. Destroying framework...')
|
|
65
|
+
await framework.destroy()
|
|
66
|
+
|
|
67
|
+
console.log('\n=== Test Complete ===')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test()
|
|
71
|
+
.then(() => {
|
|
72
|
+
console.log('\n✓ Test completed successfully')
|
|
73
|
+
process.exit(0)
|
|
74
|
+
})
|
|
75
|
+
.catch(err => {
|
|
76
|
+
console.error('\n✗ Test failed:', err.message)
|
|
77
|
+
console.error(err.stack)
|
|
78
|
+
process.exit(1)
|
|
79
|
+
})
|
package/examples/test-reload.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP mcp_reload 功能测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { Framework } = require('../src')
|
|
6
|
-
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
|
-
const fs = require('fs')
|
|
8
|
-
const path = require('path')
|
|
9
|
-
|
|
10
|
-
async function test() {
|
|
11
|
-
console.log('=== Testing mcp_reload ===\n')
|
|
12
|
-
|
|
13
|
-
// 创建框架
|
|
14
|
-
const framework = new Framework({ debug: true })
|
|
15
|
-
|
|
16
|
-
// 创建 MCP 插件
|
|
17
|
-
const mcpPlugin = new MCPExecutorPlugin({
|
|
18
|
-
servers: [
|
|
19
|
-
{ name: 'fetch', command: 'uvx', args: ['mcp-server-fetch'] }
|
|
20
|
-
]
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
// 加载并启动
|
|
24
|
-
await framework.loadPlugin(mcpPlugin)
|
|
25
|
-
await framework.pluginManager.startAll()
|
|
26
|
-
|
|
27
|
-
// 等待连接
|
|
28
|
-
await new Promise(r => setTimeout(r, 2000))
|
|
29
|
-
|
|
30
|
-
console.log('\n1. 初始服务器列表:')
|
|
31
|
-
const initialServers = mcpPlugin.getServers()
|
|
32
|
-
console.log(JSON.stringify(initialServers, null, 2))
|
|
33
|
-
|
|
34
|
-
// 测试 mcp_reload
|
|
35
|
-
console.log('\n2. 调用 mcp_reload...')
|
|
36
|
-
const result = await framework.executeTool('mcp_reload', {})
|
|
37
|
-
console.log('mcp_reload 结果:')
|
|
38
|
-
console.log(JSON.stringify(result, null, 2))
|
|
39
|
-
|
|
40
|
-
// 检查配置
|
|
41
|
-
console.log('\n3. 当前配置文件:')
|
|
42
|
-
const configPath = path.resolve('.agent/mcp_config.json')
|
|
43
|
-
if (fs.existsSync(configPath)) {
|
|
44
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
45
|
-
console.log(JSON.stringify(config, null, 2))
|
|
46
|
-
} else {
|
|
47
|
-
console.log('配置文件不存在: ' + configPath)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 清理
|
|
51
|
-
await framework.destroy()
|
|
52
|
-
|
|
53
|
-
console.log('\n=== 测试完成 ===')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
test()
|
|
57
|
-
.then(() => process.exit(0))
|
|
58
|
-
.catch(err => {
|
|
59
|
-
console.error('测试失败:', err)
|
|
60
|
-
process.exit(1)
|
|
61
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* MCP mcp_reload 功能测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Framework } = require('../src')
|
|
6
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const path = require('path')
|
|
9
|
+
|
|
10
|
+
async function test() {
|
|
11
|
+
console.log('=== Testing mcp_reload ===\n')
|
|
12
|
+
|
|
13
|
+
// 创建框架
|
|
14
|
+
const framework = new Framework({ debug: true })
|
|
15
|
+
|
|
16
|
+
// 创建 MCP 插件
|
|
17
|
+
const mcpPlugin = new MCPExecutorPlugin({
|
|
18
|
+
servers: [
|
|
19
|
+
{ name: 'fetch', command: 'uvx', args: ['mcp-server-fetch'] }
|
|
20
|
+
]
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// 加载并启动
|
|
24
|
+
await framework.loadPlugin(mcpPlugin)
|
|
25
|
+
await framework.pluginManager.startAll()
|
|
26
|
+
|
|
27
|
+
// 等待连接
|
|
28
|
+
await new Promise(r => setTimeout(r, 2000))
|
|
29
|
+
|
|
30
|
+
console.log('\n1. 初始服务器列表:')
|
|
31
|
+
const initialServers = mcpPlugin.getServers()
|
|
32
|
+
console.log(JSON.stringify(initialServers, null, 2))
|
|
33
|
+
|
|
34
|
+
// 测试 mcp_reload
|
|
35
|
+
console.log('\n2. 调用 mcp_reload...')
|
|
36
|
+
const result = await framework.executeTool('mcp_reload', {})
|
|
37
|
+
console.log('mcp_reload 结果:')
|
|
38
|
+
console.log(JSON.stringify(result, null, 2))
|
|
39
|
+
|
|
40
|
+
// 检查配置
|
|
41
|
+
console.log('\n3. 当前配置文件:')
|
|
42
|
+
const configPath = path.resolve('.agent/mcp_config.json')
|
|
43
|
+
if (fs.existsSync(configPath)) {
|
|
44
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
45
|
+
console.log(JSON.stringify(config, null, 2))
|
|
46
|
+
} else {
|
|
47
|
+
console.log('配置文件不存在: ' + configPath)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 清理
|
|
51
|
+
await framework.destroy()
|
|
52
|
+
|
|
53
|
+
console.log('\n=== 测试完成 ===')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test()
|
|
57
|
+
.then(() => process.exit(0))
|
|
58
|
+
.catch(err => {
|
|
59
|
+
console.error('测试失败:', err)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.50",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"node-telegram-bot-api": "^0.67.0",
|
|
41
41
|
"nodemailer": "^6.10.0",
|
|
42
42
|
"qrcode-terminal": "^0.12.0",
|
|
43
|
+
"tiktoken": "^1.0.22",
|
|
43
44
|
"zod": "^3.24.0"
|
|
44
45
|
}
|
|
45
46
|
}
|
package/plugins/email.js
CHANGED
|
@@ -11,7 +11,12 @@ class EmailPlugin extends Plugin {
|
|
|
11
11
|
super()
|
|
12
12
|
this.name = 'email'
|
|
13
13
|
this.version = '1.0.0'
|
|
14
|
-
this.description =
|
|
14
|
+
this.description = `邮件收发插件 - 支持读取和发送电子邮件
|
|
15
|
+
发送邮件支持附件:
|
|
16
|
+
- attachments.path: 本地文件路径
|
|
17
|
+
- attachments.url: 远程图片/文件URL(自动下载)
|
|
18
|
+
- attachments.content: Base64内容
|
|
19
|
+
- attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
|
|
15
20
|
this.priority = 10
|
|
16
21
|
// 默认不启用,需要在 plugins.json 中设置 enabled: true
|
|
17
22
|
this.enabled = false
|
|
@@ -34,7 +39,14 @@ class EmailPlugin extends Plugin {
|
|
|
34
39
|
body: z.string().describe('邮件正文内容'),
|
|
35
40
|
cc: z.string().optional().describe('抄送邮箱地址,多个用逗号分隔'),
|
|
36
41
|
bcc: z.string().optional().describe('密送邮箱地址,多个用逗号分隔'),
|
|
37
|
-
isHtml: z.boolean().optional().describe('是否为HTML格式,默认false')
|
|
42
|
+
isHtml: z.boolean().optional().describe('是否为HTML格式,默认false'),
|
|
43
|
+
attachments: z.array(z.object({
|
|
44
|
+
filename: z.string().describe('附件文件名'),
|
|
45
|
+
path: z.string().optional().describe('本地文件路径'),
|
|
46
|
+
url: z.string().optional().describe('远程图片URL'),
|
|
47
|
+
content: z.string().optional().describe('Base64编码内容(可带前缀如 data:image/png;base64,)'),
|
|
48
|
+
cid: z.string().optional().describe('嵌入式图片的CID(用于在HTML中嵌入图片,如 <img src="cid:xxx">)')
|
|
49
|
+
})).optional().describe('附件列表,支持本地文件、远程URL或Base64内容')
|
|
38
50
|
}),
|
|
39
51
|
execute: async (args) => {
|
|
40
52
|
return this._sendEmail(args)
|
|
@@ -119,6 +131,10 @@ class EmailPlugin extends Plugin {
|
|
|
119
131
|
async _sendEmail(args) {
|
|
120
132
|
try {
|
|
121
133
|
const nodemailer = require('nodemailer')
|
|
134
|
+
const https = require('https')
|
|
135
|
+
const http = require('http')
|
|
136
|
+
const fs = require('fs')
|
|
137
|
+
const path = require('path')
|
|
122
138
|
|
|
123
139
|
const smtpConfig = {
|
|
124
140
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
@@ -142,6 +158,32 @@ class EmailPlugin extends Plugin {
|
|
|
142
158
|
bcc: args.bcc
|
|
143
159
|
}
|
|
144
160
|
|
|
161
|
+
// 处理附件
|
|
162
|
+
if (args.attachments && args.attachments.length > 0) {
|
|
163
|
+
mailOptions.attachments = []
|
|
164
|
+
for (const att of args.attachments) {
|
|
165
|
+
const attachment = { filename: att.filename }
|
|
166
|
+
|
|
167
|
+
if (att.path) {
|
|
168
|
+
// 本地文件
|
|
169
|
+
attachment.content = fs.createReadStream(att.path)
|
|
170
|
+
} else if (att.url) {
|
|
171
|
+
// 远程URL
|
|
172
|
+
attachment.content = await this._fetchUrl(att.url)
|
|
173
|
+
} else if (att.content) {
|
|
174
|
+
// Base64 内容
|
|
175
|
+
const base64Data = att.content.replace(/^data:[^;]+;base64,/, '')
|
|
176
|
+
attachment.content = Buffer.from(base64Data, 'base64')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (att.cid) {
|
|
180
|
+
attachment.cid = att.cid
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
mailOptions.attachments.push(attachment)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
145
187
|
const info = await transporter.sendMail(mailOptions)
|
|
146
188
|
return {
|
|
147
189
|
success: true,
|
|
@@ -159,6 +201,35 @@ class EmailPlugin extends Plugin {
|
|
|
159
201
|
}
|
|
160
202
|
}
|
|
161
203
|
|
|
204
|
+
_fetchUrl(url) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const protocol = url.startsWith('https') ? https : http
|
|
207
|
+
const lib = url.startsWith('https') ? require('https') : require('http')
|
|
208
|
+
|
|
209
|
+
const request = lib.get(url, (response) => {
|
|
210
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
211
|
+
// 处理重定向
|
|
212
|
+
this._fetchUrl(response.headers.location).then(resolve).catch(reject)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const chunks = []
|
|
217
|
+
response.on('data', (chunk) => chunks.push(chunk))
|
|
218
|
+
response.on('end', () => {
|
|
219
|
+
const buffer = Buffer.concat(chunks)
|
|
220
|
+
resolve(buffer)
|
|
221
|
+
})
|
|
222
|
+
response.on('error', reject)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
request.on('error', reject)
|
|
226
|
+
request.setTimeout(10000, () => {
|
|
227
|
+
request.destroy()
|
|
228
|
+
reject(new Error('下载超时'))
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
162
233
|
async _readEmails(args) {
|
|
163
234
|
try {
|
|
164
235
|
const Imap = require('imap-mkl')
|
|
@@ -32,7 +32,7 @@ class SessionPlugin extends Plugin {
|
|
|
32
32
|
this.config = {
|
|
33
33
|
sessionTTL: config.sessionTTL || 30 * 60 * 1000, // 30分钟
|
|
34
34
|
maxSessions: config.maxSessions || 100,
|
|
35
|
-
maxHistoryLength: config.maxHistoryLength ||
|
|
35
|
+
maxHistoryLength: config.maxHistoryLength || 150, // 放宽到 150,Agent 已有智能压缩
|
|
36
36
|
autoCleanup: config.autoCleanup !== false,
|
|
37
37
|
cleanupInterval: config.cleanupInterval || 5 * 60 * 1000 // 5分钟
|
|
38
38
|
}
|
package/plugins/web-plugin.js
CHANGED
|
@@ -256,7 +256,7 @@ class WebPlugin extends Plugin {
|
|
|
256
256
|
async _handleRoute(c, route, pathname) {
|
|
257
257
|
const params = this._extractParams(route.path, pathname)
|
|
258
258
|
const query = this._parseQuery(c)
|
|
259
|
-
const body = await
|
|
259
|
+
const body = await this._parseBody(c)
|
|
260
260
|
|
|
261
261
|
const context = { params, query, body }
|
|
262
262
|
const result = await this._executeHandler(route.handler, context)
|
|
@@ -265,7 +265,7 @@ class WebPlugin extends Plugin {
|
|
|
265
265
|
|
|
266
266
|
async _handleWebhook(c, webhook) {
|
|
267
267
|
const query = this._parseQuery(c)
|
|
268
|
-
const body = await
|
|
268
|
+
const body = await this._parseBody(c)
|
|
269
269
|
const webhookData = {
|
|
270
270
|
path: webhook.path,
|
|
271
271
|
method: c.req.method,
|
|
@@ -490,6 +490,17 @@ class WebPlugin extends Plugin {
|
|
|
490
490
|
return query
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
async _parseBody(c) {
|
|
494
|
+
try {
|
|
495
|
+
const rawText = await c.req.text()
|
|
496
|
+
console.log(rawText)
|
|
497
|
+
if (!rawText) return {}
|
|
498
|
+
return JSON.parse(rawText)
|
|
499
|
+
} catch (e) {
|
|
500
|
+
return {}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
493
504
|
_getUrl(path='') {
|
|
494
505
|
if (this._baseUrl) {
|
|
495
506
|
return `${this._baseUrl}${path}`
|
package/src/core/agent-chat.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
+
const tiktoken = require('tiktoken')
|
|
7
8
|
|
|
8
9
|
class AgentChatHandler extends EventEmitter {
|
|
9
10
|
/**
|
|
@@ -27,8 +28,172 @@ class AgentChatHandler extends EventEmitter {
|
|
|
27
28
|
this._tools = new Map()
|
|
28
29
|
this._maxSteps = 20
|
|
29
30
|
|
|
30
|
-
//
|
|
31
|
-
this.
|
|
31
|
+
// 上下文压缩配置
|
|
32
|
+
this._maxContextTokens = config.maxContextTokens || 100000
|
|
33
|
+
this._compressionThreshold = config.compressionThreshold || 0.75
|
|
34
|
+
this._keepRecentMessages = config.keepRecentMessages || 30
|
|
35
|
+
this._enableSmartCompress = config.enableSmartCompress !== false // 默认开启智能摘要
|
|
36
|
+
this._encoder = null
|
|
37
|
+
this._compressionCount = 0 // 压缩次数统计
|
|
38
|
+
|
|
39
|
+
// 初始化编码器
|
|
40
|
+
this._initEncoder()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 初始化 tiktoken 编码器
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
_initEncoder() {
|
|
48
|
+
try {
|
|
49
|
+
// cl100k_base 是 GPT-4/ChatGPT 使用的编码
|
|
50
|
+
this._encoder = tiktoken.get_encoding('cl100k_base')
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.warn('[AgentChat] Failed to initialize tiktoken encoder:', err.message)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 计算文本的 token 数
|
|
58
|
+
* @param {string} text
|
|
59
|
+
* @returns {number}
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
_countTokens(text) {
|
|
63
|
+
if (!this._encoder || !text) return 0
|
|
64
|
+
try {
|
|
65
|
+
return this._encoder.encode(text).length
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// 粗略估算:约 4 字符 = 1 token
|
|
68
|
+
return Math.ceil(text.length / 4)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 计算消息列表的总 token 数
|
|
74
|
+
* @param {Array} messages
|
|
75
|
+
* @returns {number}
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_countMessagesTokens(messages) {
|
|
79
|
+
let total = 0
|
|
80
|
+
for (const msg of messages) {
|
|
81
|
+
total += 4 // role 标记
|
|
82
|
+
if (typeof msg.content === 'string') {
|
|
83
|
+
total += this._countTokens(msg.content)
|
|
84
|
+
} else if (Array.isArray(msg.content)) {
|
|
85
|
+
for (const part of msg.content) {
|
|
86
|
+
if (part.text) {
|
|
87
|
+
total += this._countTokens(part.text)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
total += 4 // 结尾标记
|
|
93
|
+
return total
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 检查是否需要压缩上下文
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
_shouldCompress() {
|
|
102
|
+
const totalTokens = this._countMessagesTokens(this._messages)
|
|
103
|
+
return totalTokens > this._maxContextTokens * this._compressionThreshold
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 压缩上下文消息(智能摘要模式)
|
|
108
|
+
* 策略:
|
|
109
|
+
* 1. 如果启用了智能摘要且有 AI 客户端,对早期消息进行 AI 总结
|
|
110
|
+
* 2. 否则使用简单裁剪 + 标记
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
async _compressContext() {
|
|
114
|
+
if (this._messages.length <= this._keepRecentMessages) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const systemMessages = this._messages.filter(m => m.role === 'system')
|
|
119
|
+
const otherMessages = this._messages.filter(m => m.role !== 'system')
|
|
120
|
+
|
|
121
|
+
// 保留最近的 N 条非系统消息
|
|
122
|
+
const recentMessages = otherMessages.slice(-this._keepRecentMessages)
|
|
123
|
+
const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages)
|
|
124
|
+
|
|
125
|
+
const compressedCount = messagesToSummarize.length
|
|
126
|
+
let summaryContent = ''
|
|
127
|
+
|
|
128
|
+
// 尝试使用 AI 总结
|
|
129
|
+
if (this._enableSmartCompress && this._aiClient) {
|
|
130
|
+
try {
|
|
131
|
+
const summaryText = await this._summarizeMessages(messagesToSummarize)
|
|
132
|
+
summaryContent = `[早期对话摘要]: ${summaryText}`
|
|
133
|
+
console.log(`[AgentChat] AI 摘要生成成功 (${summaryText.length} chars)`)
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn('[AgentChat] AI 摘要失败,使用简单压缩:', err.message)
|
|
136
|
+
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const summary = {
|
|
143
|
+
role: 'system',
|
|
144
|
+
content: summaryContent
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this._messages = [...systemMessages, summary, ...recentMessages]
|
|
148
|
+
this._compressionCount++
|
|
149
|
+
|
|
150
|
+
const totalTokens = this._countMessagesTokens(this._messages)
|
|
151
|
+
console.log(`[AgentChat] Context compressed (${this._compressionCount} times). Messages: ${this._messages.length}, Est. tokens: ${totalTokens}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 使用 AI 对消息进行总结
|
|
156
|
+
* @param {Array} messages - 要总结的消息
|
|
157
|
+
* @returns {Promise<string>} 总结文本
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
async _summarizeMessages(messages) {
|
|
161
|
+
if (!this._aiClient || messages.length === 0) {
|
|
162
|
+
return '(无早期对话)'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 构建总结提示
|
|
166
|
+
const conversationText = messages.map(m => {
|
|
167
|
+
const role = m.role === 'user' ? '用户' : '助手'
|
|
168
|
+
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
|
|
169
|
+
return `${role}: ${content}`
|
|
170
|
+
}).join('\n')
|
|
171
|
+
|
|
172
|
+
const summarizePrompt = `请简洁地总结以下对话的要点,保留关键信息和用户需求:
|
|
173
|
+
|
|
174
|
+
${conversationText}
|
|
175
|
+
|
|
176
|
+
总结要求:
|
|
177
|
+
1. 提取用户的主要需求和意图
|
|
178
|
+
2. 保留关键的技术细节或决策
|
|
179
|
+
3. 不要超过 500 字
|
|
180
|
+
4. 用中文回复`
|
|
181
|
+
|
|
182
|
+
// 使用 AI 生成摘要
|
|
183
|
+
const { ToolLoopAgent } = await this._importAI()
|
|
184
|
+
const agent = new ToolLoopAgent({
|
|
185
|
+
model: this._aiClient,
|
|
186
|
+
instructions: '你是一个对话总结助手。请根据用户提供的对话生成简洁的摘要。',
|
|
187
|
+
tools: []
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const result = await agent.generate({
|
|
191
|
+
messages: [
|
|
192
|
+
{ role: 'user', content: summarizePrompt }
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return result.text || '(总结生成失败)'
|
|
32
197
|
}
|
|
33
198
|
|
|
34
199
|
/**
|
|
@@ -63,6 +228,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
63
228
|
*/
|
|
64
229
|
clearHistory() {
|
|
65
230
|
this._messages = []
|
|
231
|
+
this._compressionCount = 0
|
|
66
232
|
return this
|
|
67
233
|
}
|
|
68
234
|
|
|
@@ -108,6 +274,11 @@ class AgentChatHandler extends EventEmitter {
|
|
|
108
274
|
|
|
109
275
|
this._messages.push(userMessage)
|
|
110
276
|
|
|
277
|
+
// 检查是否需要压缩上下文
|
|
278
|
+
if (this._shouldCompress()) {
|
|
279
|
+
await this._compressContext()
|
|
280
|
+
}
|
|
281
|
+
|
|
111
282
|
const maxSteps = options.maxSteps || this._maxSteps
|
|
112
283
|
const tools = this._getAITools(tool)
|
|
113
284
|
|
|
@@ -169,6 +340,14 @@ class AgentChatHandler extends EventEmitter {
|
|
|
169
340
|
|
|
170
341
|
this._messages.push(userMessage)
|
|
171
342
|
|
|
343
|
+
// 检查是否需要压缩上下文(同步调用,不阻塞流式输出)
|
|
344
|
+
if (this._shouldCompress()) {
|
|
345
|
+
// 压缩在后台进行,不等待完成,避免阻塞流式输出
|
|
346
|
+
this._compressContext().catch(err => {
|
|
347
|
+
console.warn('[AgentChat] Background compression failed:', err.message)
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
172
351
|
const maxSteps = options.maxSteps || this._maxSteps
|
|
173
352
|
const tools = this._getAITools(tool)
|
|
174
353
|
|
|
@@ -301,6 +480,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
301
480
|
destroy() {
|
|
302
481
|
this._messages = []
|
|
303
482
|
this._tools.clear()
|
|
483
|
+
if (this._encoder) {
|
|
484
|
+
this._encoder.free()
|
|
485
|
+
this._encoder = null
|
|
486
|
+
}
|
|
304
487
|
this.removeAllListeners()
|
|
305
488
|
}
|
|
306
489
|
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executor 基类
|
|
3
|
-
* 执行器的基类,定义执行器接口
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
-
|
|
8
|
-
class ExecutorBase extends EventEmitter {
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} name - 执行器名称
|
|
11
|
-
*/
|
|
12
|
-
constructor(name) {
|
|
13
|
-
super()
|
|
14
|
-
this.name = name
|
|
15
|
-
this._enabled = true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 执行
|
|
20
|
-
* @param {Object} params - 执行参数
|
|
21
|
-
* @returns {Promise<any>}
|
|
22
|
-
*/
|
|
23
|
-
async execute(params) {
|
|
24
|
-
throw new Error('execute() must be implemented')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 启用执行器
|
|
29
|
-
*/
|
|
30
|
-
enable() {
|
|
31
|
-
this._enabled = true
|
|
32
|
-
return this
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 禁用执行器
|
|
37
|
-
*/
|
|
38
|
-
disable() {
|
|
39
|
-
this._enabled = false
|
|
40
|
-
return this
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 是否启用
|
|
45
|
-
*/
|
|
46
|
-
isEnabled() {
|
|
47
|
-
return this._enabled
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 销毁
|
|
52
|
-
*/
|
|
53
|
-
destroy() {
|
|
54
|
-
this.removeAllListeners()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = { ExecutorBase }
|
|
1
|
+
/**
|
|
2
|
+
* Executor 基类
|
|
3
|
+
* 执行器的基类,定义执行器接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
+
|
|
8
|
+
class ExecutorBase extends EventEmitter {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} name - 执行器名称
|
|
11
|
+
*/
|
|
12
|
+
constructor(name) {
|
|
13
|
+
super()
|
|
14
|
+
this.name = name
|
|
15
|
+
this._enabled = true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 执行
|
|
20
|
+
* @param {Object} params - 执行参数
|
|
21
|
+
* @returns {Promise<any>}
|
|
22
|
+
*/
|
|
23
|
+
async execute(params) {
|
|
24
|
+
throw new Error('execute() must be implemented')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 启用执行器
|
|
29
|
+
*/
|
|
30
|
+
enable() {
|
|
31
|
+
this._enabled = true
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 禁用执行器
|
|
37
|
+
*/
|
|
38
|
+
disable() {
|
|
39
|
+
this._enabled = false
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 是否启用
|
|
45
|
+
*/
|
|
46
|
+
isEnabled() {
|
|
47
|
+
return this._enabled
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 销毁
|
|
52
|
+
*/
|
|
53
|
+
destroy() {
|
|
54
|
+
this.removeAllListeners()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { ExecutorBase }
|
package/test-server.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const { serve } = require('@hono/node-server');
|
|
2
|
-
const { Hono } = require('hono');
|
|
3
|
-
|
|
4
|
-
const app = new Hono();
|
|
5
|
-
|
|
6
|
-
// 简单路由
|
|
7
|
-
app.get('/test', (c) => {
|
|
8
|
-
console.log('Handler called');
|
|
9
|
-
return c.json({ message: 'Hello World' });
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const server = serve({
|
|
13
|
-
fetch: app.fetch,
|
|
14
|
-
port: 3001
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
server.on('request', (req) => {
|
|
18
|
-
console.log('Request:', req.method, req.url);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
server.on('error', (err) => {
|
|
22
|
-
console.error('Server error:', err);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
console.log('Server started on port 3001');
|
|
1
|
+
const { serve } = require('@hono/node-server');
|
|
2
|
+
const { Hono } = require('hono');
|
|
3
|
+
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
|
|
6
|
+
// 简单路由
|
|
7
|
+
app.get('/test', (c) => {
|
|
8
|
+
console.log('Handler called');
|
|
9
|
+
return c.json({ message: 'Hello World' });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const server = serve({
|
|
13
|
+
fetch: app.fetch,
|
|
14
|
+
port: 3001
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
server.on('request', (req) => {
|
|
18
|
+
console.log('Request:', req.method, req.url);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
server.on('error', (err) => {
|
|
22
|
+
console.error('Server error:', err);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log('Server started on port 3001');
|
package/test.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Hello from static resource test!
|
|
2
|
-
This is a test file for verifying static resource serving.
|
|
3
|
-
Timestamp: 2026-03-24
|
|
1
|
+
Hello from static resource test!
|
|
2
|
+
This is a test file for verifying static resource serving.
|
|
3
|
+
Timestamp: 2026-03-24
|