deepfish-ai 1.0.29 → 2.0.2
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/README.md +126 -368
- package/dist/index.js +23320 -0
- package/dist/serve/client/assets/index-DUHYC91l.js +1 -0
- package/dist/serve/client/assets/index-zcrdNLZs.css +1 -0
- package/dist/serve/client/index.html +14 -0
- package/dist/serve/pm2-server.js +21015 -0
- package/dist/serve/server/entry-server.mjs +224 -0
- package/package.json +61 -83
- package/LICENSE +0 -21
- package/README_CN.md +0 -460
- package/src/AgentRobot/AgentRobotFactory/MainAgentRobot.js +0 -88
- package/src/AgentRobot/AgentRobotFactory/SubAgentRobot.js +0 -45
- package/src/AgentRobot/AgentRobotFactory/SubSkillAgentRobot.js +0 -45
- package/src/AgentRobot/AgentRobotFactory/index.js +0 -48
- package/src/AgentRobot/BaseAgentRobot/Brain.js +0 -210
- package/src/AgentRobot/BaseAgentRobot/BrainEvent.js +0 -17
- package/src/AgentRobot/BaseAgentRobot/Hand.js +0 -119
- package/src/AgentRobot/BaseAgentRobot/Heart.js +0 -0
- package/src/AgentRobot/BaseAgentRobot/Logger.js +0 -114
- package/src/AgentRobot/BaseAgentRobot/ScreenPrinter.js +0 -49
- package/src/AgentRobot/BaseAgentRobot/index.js +0 -266
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/AliBailian.js +0 -997
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/doc-transform.js +0 -204
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/docx.js +0 -1286
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/img.js +0 -765
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/pdf.js +0 -736
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/pptx.js +0 -567
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/video.js +0 -36
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/xlsx.js +0 -611
- package/src/AgentRobot/BaseAgentRobot/tools/BaseTools.js +0 -13
- package/src/AgentRobot/BaseAgentRobot/tools/CreateAgentTools.js +0 -158
- package/src/AgentRobot/BaseAgentRobot/tools/FileTools.js +0 -768
- package/src/AgentRobot/BaseAgentRobot/tools/GenerateTools.js +0 -441
- package/src/AgentRobot/BaseAgentRobot/tools/InquirerTools.js +0 -244
- package/src/AgentRobot/BaseAgentRobot/tools/SystemTools.js +0 -424
- package/src/AgentRobot/BaseAgentRobot/tools/TaskTools.js +0 -211
- package/src/AgentRobot/BaseAgentRobot/tools/TestTools.js +0 -100
- package/src/AgentRobot/BaseAgentRobot/tools/UserTool.js +0 -87
- package/src/AgentRobot/BaseAgentRobot/tools/WebTools.js +0 -257
- package/src/AgentRobot/BaseAgentRobot/utils/AIRequest.js +0 -297
- package/src/AgentRobot/BaseAgentRobot/utils/AIToolManager.js +0 -131
- package/src/AgentRobot/BaseAgentRobot/utils/AgentTree.js +0 -66
- package/src/AgentRobot/BaseAgentRobot/utils/AttachmentToolScanner.js +0 -235
- package/src/AgentRobot/BaseAgentRobot/utils/MessageCompresser.js +0 -236
- package/src/AgentRobot/BaseAgentRobot/utils/aiConsole.js +0 -74
- package/src/AgentRobot/BaseAgentRobot/utils/aiInquirer.js +0 -78
- package/src/AgentRobot/BaseAgentRobot/utils/analyzeReturn.test.js +0 -104
- package/src/AgentRobot/BaseAgentRobot/utils/copilot.js +0 -117
- package/src/AgentRobot/BaseAgentRobot/utils/node-root.js +0 -138
- package/src/AgentRobot/BaseAgentRobot/utils/normal.js +0 -399
- package/src/cli/ConfigManager.js +0 -283
- package/src/cli/DefaultConfig.js +0 -98
- package/src/cli/GlobalVariable.js +0 -8
- package/src/cli/MemoryManager.js +0 -53
- package/src/cli/SkillConfigManager.js +0 -354
- package/src/cli/SkillParser.js +0 -58
- package/src/cli/ai-auth.js +0 -231
- package/src/cli/ai-config.js +0 -225
- package/src/cli/ai-memory.js +0 -22
- package/src/cli/ai-skill.js +0 -65
- package/src/cli/index.js +0 -77
- package/src/index.js +0 -54
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const { getGlobalNodeModulesPath } = require('./node-root')
|
|
4
|
-
|
|
5
|
-
class AttachmentToolType {
|
|
6
|
-
static BASE_SKILL = 'BaseSkill' // 基础技能,提供技能定义的基本结构
|
|
7
|
-
static CLAW_SKILL = 'ClawSkill' // 爪子技能,提供技能执行的基本结构
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
class AttachmentToolScanner {
|
|
11
|
-
// 获取附加工具
|
|
12
|
-
static getToolCollection(workspace, basespace) {
|
|
13
|
-
// 从文件中加载附加技能
|
|
14
|
-
// 动态加载这些文件,获取工具对象
|
|
15
|
-
const attachTools = []
|
|
16
|
-
// 先扫描懒加载目录
|
|
17
|
-
const lazyLoadDir = path.join(__dirname, '../lazy-tools')
|
|
18
|
-
const lazyFiles = fs.existsSync(lazyLoadDir) ? fs.readdirSync(lazyLoadDir) : []
|
|
19
|
-
for (const fileName of lazyFiles) {
|
|
20
|
-
try {
|
|
21
|
-
const filePath = path.join(lazyLoadDir, fileName)
|
|
22
|
-
const tool = require(path.resolve(lazyLoadDir, fileName))
|
|
23
|
-
if (tool) {
|
|
24
|
-
tool.type = AttachmentToolType.BASE_SKILL
|
|
25
|
-
tool.location = tool.location || path.dirname(filePath)
|
|
26
|
-
tool.filePath = tool.filePath || filePath
|
|
27
|
-
attachTools.push(tool)
|
|
28
|
-
}
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error(`加载附加工具失败: ${fileName}`, error)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// 1.搜索程序所在目录下的以deepfish-ai-开头的文件夹
|
|
36
|
-
// 2.搜索程序所在目录下以@deepfish-ai开头的文件夹里的目录
|
|
37
|
-
// 3.工作目录下node_modules目录下以deepfish-ai-开头的文件夹
|
|
38
|
-
// 4.工作目录下node_modules目录下以@deepfish-ai开头的文件夹里的目录
|
|
39
|
-
// 5.工作目录下以deepfish-ai-开头的文件夹
|
|
40
|
-
// 6.工作目录下以@deepfish-ai开头的文件夹里的目录
|
|
41
|
-
// 7.工作目录下的js文件
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 附加工具结构:
|
|
45
|
-
* name: 'BaseSkill',
|
|
46
|
-
* description: '基础扩展模板,提供扩展的基本结构定义',
|
|
47
|
-
* location: currentDir, // 扩展文件路径,默认为当前文件所在目录
|
|
48
|
-
* platform: 'all', // 扩展支持的平台(process.platform),all或空表示所有平台, win32表示仅支持 Windows, darwin表示仅支持MacOS, linux表示仅支持Linux
|
|
49
|
-
* descriptions,
|
|
50
|
-
* functions,
|
|
51
|
-
*/
|
|
52
|
-
// 1. 子agent创建时,不能拥有其他附加能力
|
|
53
|
-
// 2. 使用platform过滤
|
|
54
|
-
const dir1 = path.resolve(__dirname, '../../../../../') // 程序所在目录
|
|
55
|
-
const dir2 = path.resolve(workspace, './node_modules') // 工作目录下node_modules目录
|
|
56
|
-
const dir3 = path.resolve(workspace, './') // 工作目录
|
|
57
|
-
const dir4 = getGlobalNodeModulesPath()
|
|
58
|
-
const dir5 = path.resolve(basespace, 'skills') // 工作目录的父目录
|
|
59
|
-
const dir6 = path.resolve(basespace, 'clawSkills') // 工作目录的父目录
|
|
60
|
-
const result = []
|
|
61
|
-
const searchDirs = [...new Set([dir1, dir2, dir3, dir4, dir5, dir6])]
|
|
62
|
-
for (const dirPath of searchDirs) {
|
|
63
|
-
if (!fs.existsSync(dirPath)) {
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
const fileNames = fs.readdirSync(dirPath)
|
|
67
|
-
// 查询目录下是否有@deepfish-ai目录
|
|
68
|
-
fileNames.forEach((dirName) => {
|
|
69
|
-
if (dirName === '@deepfish-ai') {
|
|
70
|
-
const deepFishPath = path.resolve(dirPath, '@deepfish-ai')
|
|
71
|
-
const packageNames = fs.readdirSync(deepFishPath)
|
|
72
|
-
packageNames.forEach((packageName) => {
|
|
73
|
-
const mainFile = AttachmentToolScanner._scanDeepFishPackage(
|
|
74
|
-
deepFishPath,
|
|
75
|
-
packageName,
|
|
76
|
-
)
|
|
77
|
-
if (mainFile) {
|
|
78
|
-
result.push(mainFile)
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
} else if (
|
|
82
|
-
dirName.startsWith('deepfish-') &&
|
|
83
|
-
dirName !== 'deepfish-ai'
|
|
84
|
-
) {
|
|
85
|
-
const mainFile = AttachmentToolScanner._scanDeepFishPackage(dirPath, dirName)
|
|
86
|
-
if (mainFile) {
|
|
87
|
-
result.push(mainFile)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
const fileNames = fs.readdirSync(dir3)
|
|
93
|
-
fileNames.forEach((fileName) => {
|
|
94
|
-
if (fileName.endsWith('.js') || fileName.endsWith('.mjs')) {
|
|
95
|
-
const mainFile = AttachmentToolScanner._scanDeepFishJsFile(dir3, fileName)
|
|
96
|
-
if (mainFile) {
|
|
97
|
-
result.push(mainFile)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
for (const filePath of result) {
|
|
102
|
-
try {
|
|
103
|
-
const tool = require(filePath)
|
|
104
|
-
if (tool) {
|
|
105
|
-
tool.type = AttachmentToolType.BASE_SKILL
|
|
106
|
-
tool.location = tool.location || path.dirname(filePath)
|
|
107
|
-
tool.filePath = tool.filePath || filePath
|
|
108
|
-
attachTools.push(tool)
|
|
109
|
-
}
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.error(`加载附加工具失败: ${filePath}`, error)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return attachTools
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
static getClawSkillCollection(basespace) {
|
|
118
|
-
const skillFilePath = path.join(basespace, './clawSkills/clawSkills.json')
|
|
119
|
-
if (!fs.pathExistsSync(skillFilePath)) {
|
|
120
|
-
return []
|
|
121
|
-
} else {
|
|
122
|
-
const skillJson = fs.readJSONSync(skillFilePath)
|
|
123
|
-
return (skillJson.skills || []).filter(skill => skill.enable).map((skill) => {
|
|
124
|
-
return {
|
|
125
|
-
...skill,
|
|
126
|
-
type: AttachmentToolType.CLAW_SKILL
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
static getAttachToolPrompt(toolCollection, clawSkillCollection) {
|
|
133
|
-
const table = ([].concat(toolCollection).concat(clawSkillCollection))
|
|
134
|
-
.map(
|
|
135
|
-
(s) =>
|
|
136
|
-
`| ${s.name} | ${s.type} | ${s.description || s.extensionDescription} | ${s.location} | ${s.filePath || s.skillFilePath} |`,
|
|
137
|
-
)
|
|
138
|
-
.join('\n')
|
|
139
|
-
if (!table || !table.length) {
|
|
140
|
-
return '### 暂无可用的Skills'
|
|
141
|
-
}
|
|
142
|
-
return `
|
|
143
|
-
### 可以使用的Skills
|
|
144
|
-
可以调用以下Skill来完成用户的请求,Skill的调用方式:
|
|
145
|
-
- 使用用户请求匹配 skill description,
|
|
146
|
-
- 一次只加载一个Skill,优先匹配最具体的Skill
|
|
147
|
-
- 当用户请求不匹配任何Skill描述时,不加载任何Skill
|
|
148
|
-
- 调用 createSubSkillAgent 函数创建子Agent来执行任务
|
|
149
|
-
## Available Skills
|
|
150
|
-
|
|
151
|
-
| Skill | Type | Description | Location | FilePath |
|
|
152
|
-
|-------|------|-------------|----------|----------|
|
|
153
|
-
${table}
|
|
154
|
-
|-------|------|-------------|----------|----------|
|
|
155
|
-
`
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
static getClawSkillPrompt(clawSkills=[], toolCollection=[], clawSkillCollection=[]) {
|
|
159
|
-
const table1 = clawSkills
|
|
160
|
-
.map(
|
|
161
|
-
(s) =>
|
|
162
|
-
`| ${s.name} | ${s.type} | ${s.description} | ${s.location} | ${s.skillFilePath} |`,
|
|
163
|
-
)
|
|
164
|
-
.join('\n')
|
|
165
|
-
const table2 = ([].concat(toolCollection).concat(clawSkillCollection))
|
|
166
|
-
.map(
|
|
167
|
-
(s) =>
|
|
168
|
-
`| ${s.name} | ${s.type} | ${s.description || s.extensionDescription} | ${s.location} | ${s.filePath || s.skillFilePath} |`,
|
|
169
|
-
)
|
|
170
|
-
.join('\n')
|
|
171
|
-
let skills1 = clawSkills.length > 0 ? `
|
|
172
|
-
### 优先使用的Skills
|
|
173
|
-
可以优先调用以下Skill来完成用户的请求,Skill的调用方式:
|
|
174
|
-
- 使用用户请求匹配 skill description,
|
|
175
|
-
- 一次只加载一个Skill,优先匹配最具体的Skill
|
|
176
|
-
- 当用户请求不匹配任何Skill描述时,不加载任何Skill
|
|
177
|
-
- 使用Skill前先使用readFile函数读取SKILL.md文件获取调用说明,通过仔细阅读说明文件学习Skill的使用方法,直接完成任务,无需创建子Agent来完成任务
|
|
178
|
-
## Available Skills
|
|
179
|
-
|
|
180
|
-
| Skill | Type | Description | Location | SkillFilePath |
|
|
181
|
-
|-------|------|-------------|----------|---------------|
|
|
182
|
-
${table1}
|
|
183
|
-
|-------|------|-------------|----------|---------------|
|
|
184
|
-
` : '### 无优先使用的Skills'
|
|
185
|
-
let skills2 = toolCollection.length + clawSkillCollection.length > 0 ? `
|
|
186
|
-
### 其他可以使用的Skills
|
|
187
|
-
可以调用以下Skill来完成用户的请求,Skill的调用方式:
|
|
188
|
-
- 使用用户请求匹配 skill description,
|
|
189
|
-
- 一次只加载一个Skill,优先匹配最具体的Skill
|
|
190
|
-
- 当用户请求不匹配任何Skill描述时,不加载任何Skill
|
|
191
|
-
- Type类型为'ClawSkill'时,使用Skill前先使用readFile函数读取SKILL.md文件获取调用说明,通过仔细阅读说明文件学习Skill的使用方法,来完成任务
|
|
192
|
-
- Type类型为'BaseSkill'时,使用loadAttachTool函数加载Skill后,该技能中的工具函数会被添加到工具列表中,即可直接调用
|
|
193
|
-
## Available Skills
|
|
194
|
-
|
|
195
|
-
| Skill | Type | Description | Location | FilePath |
|
|
196
|
-
|-------|------|-------------|----------|----------|
|
|
197
|
-
${table2}
|
|
198
|
-
|-------|------|-------------|----------|----------|
|
|
199
|
-
`: '### 无其他可用的Skills'
|
|
200
|
-
return skills1 + '\n' + skills2
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 扫描包
|
|
204
|
-
static _scanDeepFishPackage(parentDir, packageName) {
|
|
205
|
-
const dirPath = path.resolve(parentDir, packageName)
|
|
206
|
-
const packageJsonPath = path.resolve(dirPath, 'package.json')
|
|
207
|
-
if (fs.pathExistsSync(packageJsonPath)) {
|
|
208
|
-
const packageJson = fs.readJsonSync(packageJsonPath)
|
|
209
|
-
if (packageJson.main) {
|
|
210
|
-
return AttachmentToolScanner._scanDeepFishJsFile(dirPath, packageJson.main)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return null
|
|
214
|
-
}
|
|
215
|
-
// 扫描文件
|
|
216
|
-
static _scanDeepFishJsFile(parentDir, fileName) {
|
|
217
|
-
const filePath = path.resolve(parentDir, fileName)
|
|
218
|
-
if (fs.pathExistsSync(filePath)) {
|
|
219
|
-
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
|
220
|
-
if (
|
|
221
|
-
fileContent.includes('module.exports') &&
|
|
222
|
-
fileContent.includes('descriptions') &&
|
|
223
|
-
fileContent.includes('functions')
|
|
224
|
-
) {
|
|
225
|
-
return filePath
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return null
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
module.exports = {
|
|
233
|
-
AttachmentToolScanner,
|
|
234
|
-
AttachmentToolType,
|
|
235
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @Author: Roman 306863030@qq.com
|
|
3
|
-
* @Date: 2026-03-16 09:18:05
|
|
4
|
-
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
-
* @LastEditTime: 2026-04-13 11:21:09
|
|
6
|
-
* @FilePath: \deepfish\src\AgentRobot\BaseAgentRobot\utils\MessageCompresser.js
|
|
7
|
-
* @Description: 上下文管理-添加、自动压缩
|
|
8
|
-
* @
|
|
9
|
-
*/
|
|
10
|
-
const BrainEvent = require('../BrainEvent.js')
|
|
11
|
-
|
|
12
|
-
class MessageCompresser {
|
|
13
|
-
constructor(robotBrain) {
|
|
14
|
-
this.robotBrain = robotBrain
|
|
15
|
-
// 最大上下文长度,留10%余量
|
|
16
|
-
this.maxContextLength = (this.robotBrain.maxContextLength || 32) * 1024 * 0.9
|
|
17
|
-
// 固定保留最新 X 条消息不压缩(行业最佳实践)
|
|
18
|
-
this.KEEP_LATEST_COUNT = 3
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 压缩消息:稳定版策略
|
|
23
|
-
* 保留:第一条system + 最新3条消息 + 中间压缩为摘要
|
|
24
|
-
*/
|
|
25
|
-
async compress(messages) {
|
|
26
|
-
// 空消息直接返回
|
|
27
|
-
if (!messages || messages.length === 0) return messages
|
|
28
|
-
|
|
29
|
-
// 先清理工具调用消息序列,避免历史坏数据触发 400
|
|
30
|
-
this._normalizeInPlace(messages)
|
|
31
|
-
|
|
32
|
-
const currentLength = this._getLength(messages)
|
|
33
|
-
// 不需要压缩直接返回
|
|
34
|
-
if (this.maxContextLength !== -1 && currentLength <= this.maxContextLength) {
|
|
35
|
-
return messages
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
this.robotBrain.emit(BrainEvent.COMPRESS_MESSAGES_BEFORE, messages, currentLength)
|
|
39
|
-
|
|
40
|
-
// ---------------------
|
|
41
|
-
// 核心:安全截取规则
|
|
42
|
-
// ---------------------
|
|
43
|
-
const newMessages = []
|
|
44
|
-
const totalMsg = messages.length
|
|
45
|
-
|
|
46
|
-
// 1. 永远保留第一条 system 消息
|
|
47
|
-
const firstSystem = messages[0]
|
|
48
|
-
newMessages.push(firstSystem)
|
|
49
|
-
|
|
50
|
-
// 2. 消息太少,不压缩
|
|
51
|
-
if (totalMsg <= this.KEEP_LATEST_COUNT + 1) {
|
|
52
|
-
this.robotBrain.emit(BrainEvent.COMPRESS_MESSAGES_AFTER, newMessages, this._getLength(newMessages))
|
|
53
|
-
return [...messages]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 3. 中间需要压缩的区间:第1条 ~ 安全保留尾部起点
|
|
57
|
-
const compressStart = 1
|
|
58
|
-
const minTailStart = Math.max(1, totalMsg - this.KEEP_LATEST_COUNT)
|
|
59
|
-
const safeTailStart = this._findSafeTailStart(messages, minTailStart)
|
|
60
|
-
const compressEnd = safeTailStart
|
|
61
|
-
const middleMessages = messages.slice(compressStart, compressEnd)
|
|
62
|
-
|
|
63
|
-
// 4. 压缩中间历史
|
|
64
|
-
if (middleMessages.length > 0) {
|
|
65
|
-
const summary = await this._getSummary(middleMessages)
|
|
66
|
-
newMessages.push(summary)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 5. 保留最新消息(不压缩),并保证不会截断 tool_calls 对应关系
|
|
70
|
-
const latestMessages = messages.slice(safeTailStart)
|
|
71
|
-
newMessages.push(...latestMessages)
|
|
72
|
-
|
|
73
|
-
// 再做一次序列归一化,避免压缩后出现非法 tool 消息
|
|
74
|
-
const normalized = this._normalizeToolMessageSequence(newMessages)
|
|
75
|
-
|
|
76
|
-
// 替换原数组
|
|
77
|
-
messages.splice(0, messages.length, ...normalized)
|
|
78
|
-
|
|
79
|
-
this.robotBrain.emit(BrainEvent.COMPRESS_MESSAGES_AFTER, messages, this._getLength(normalized))
|
|
80
|
-
return messages
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 计算安全尾部起点,避免产生孤立的 tool 消息:
|
|
85
|
-
* - 如果起点落在 tool 消息上,向前回退到触发该 tool 的 assistant(tool_calls)
|
|
86
|
-
* - 如果 assistant(tool_calls) 后存在任意 tool 消息被保留,则 assistant(tool_calls) 必须一并保留
|
|
87
|
-
*/
|
|
88
|
-
_findSafeTailStart(messages, minTailStart) {
|
|
89
|
-
let start = minTailStart
|
|
90
|
-
while (start > 1) {
|
|
91
|
-
const current = messages[start]
|
|
92
|
-
const prev = messages[start - 1]
|
|
93
|
-
|
|
94
|
-
if (current?.role === 'tool') {
|
|
95
|
-
start -= 1
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (current?.role !== 'tool' && prev?.role === 'tool') {
|
|
100
|
-
start -= 1
|
|
101
|
-
continue
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const hasToolMessagesAfter = messages
|
|
105
|
-
.slice(start + 1)
|
|
106
|
-
.some((msg) => msg?.role === 'tool')
|
|
107
|
-
if (current?.role === 'assistant' && current?.tool_calls && hasToolMessagesAfter) {
|
|
108
|
-
return start
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
break
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
while (start < messages.length) {
|
|
115
|
-
const msg = messages[start]
|
|
116
|
-
if (msg?.role !== 'tool') {
|
|
117
|
-
break
|
|
118
|
-
}
|
|
119
|
-
start += 1
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return start
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
_normalizeInPlace(messages) {
|
|
126
|
-
const normalized = this._normalizeToolMessageSequence(messages)
|
|
127
|
-
if (normalized.length === messages.length && normalized.every((m, i) => m === messages[i])) {
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
messages.splice(0, messages.length, ...normalized)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
_normalizeToolMessageSequence(messages) {
|
|
134
|
-
const normalized = []
|
|
135
|
-
let pending = null
|
|
136
|
-
|
|
137
|
-
const flushPending = () => {
|
|
138
|
-
if (!pending) return
|
|
139
|
-
const hasAllToolReports = pending.callIds.size > 0 && pending.respondedIds.size === pending.callIds.size
|
|
140
|
-
if (hasAllToolReports) {
|
|
141
|
-
normalized.push(pending.assistant)
|
|
142
|
-
normalized.push(...pending.toolReports)
|
|
143
|
-
}
|
|
144
|
-
pending = null
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
for (const msg of messages) {
|
|
148
|
-
if (msg?.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
149
|
-
flushPending()
|
|
150
|
-
pending = {
|
|
151
|
-
assistant: msg,
|
|
152
|
-
toolReports: [],
|
|
153
|
-
callIds: new Set(msg.tool_calls.map((item) => item?.id).filter(Boolean)),
|
|
154
|
-
respondedIds: new Set(),
|
|
155
|
-
}
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (msg?.role === 'tool') {
|
|
160
|
-
if (!pending) {
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
|
-
if (!msg.tool_call_id || !pending.callIds.has(msg.tool_call_id)) {
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
|
-
pending.toolReports.push(msg)
|
|
167
|
-
pending.respondedIds.add(msg.tool_call_id)
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
flushPending()
|
|
172
|
-
normalized.push(msg)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
flushPending()
|
|
176
|
-
return normalized
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 计算消息总长度(稳健版)
|
|
180
|
-
_getLength(messages) {
|
|
181
|
-
return messages.reduce((total, msg) => {
|
|
182
|
-
let len = 0
|
|
183
|
-
if (msg.content) {
|
|
184
|
-
len += typeof msg.content === 'string'
|
|
185
|
-
? msg.content.length
|
|
186
|
-
: JSON.stringify(msg.content).length
|
|
187
|
-
}
|
|
188
|
-
if (msg.tool_calls) {
|
|
189
|
-
len += JSON.stringify(msg.tool_calls).length
|
|
190
|
-
}
|
|
191
|
-
return total + len
|
|
192
|
-
}, 0)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 生成对话摘要(优化提示词 + 错误兜底)
|
|
196
|
-
async _getSummary(messages) {
|
|
197
|
-
const conversationText = messages
|
|
198
|
-
.map(m => {
|
|
199
|
-
const role = m.role.toUpperCase()
|
|
200
|
-
const content = m.content || (m.tool_calls ? '[工具调用]' : '')
|
|
201
|
-
return `[${role}]: ${content}`
|
|
202
|
-
})
|
|
203
|
-
.join('\n')
|
|
204
|
-
|
|
205
|
-
const summaryPrompt = `
|
|
206
|
-
请总结以下对话历史,严格遵守规则:
|
|
207
|
-
1. 只保留:用户任务目标、执行结果、关键上下文
|
|
208
|
-
2. 删除:报错、冗余描述、闲聊、过程细节
|
|
209
|
-
3. 摘要必须简洁、完整,保证后续任务能正常继续
|
|
210
|
-
4. 不要多余解释,只输出纯摘要文本
|
|
211
|
-
|
|
212
|
-
对话历史:
|
|
213
|
-
${conversationText}`
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const summary = await this.robotBrain.think(
|
|
217
|
-
'你是专业的对话摘要助手,只输出精简摘要',
|
|
218
|
-
summaryPrompt
|
|
219
|
-
)
|
|
220
|
-
return {
|
|
221
|
-
role: 'system',
|
|
222
|
-
content: `【历史对话摘要】:${summary}`,
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
// 降级方案:手动截取
|
|
226
|
-
let manual = '【历史摘要(降级)】:'
|
|
227
|
-
messages.slice(0, 4).forEach(m => {
|
|
228
|
-
const c = m.content || ''
|
|
229
|
-
manual += `${m.role}:${c.slice(0, 80)}... `
|
|
230
|
-
})
|
|
231
|
-
return { role: 'system', content: manual }
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
module.exports = MessageCompresser
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk')
|
|
2
|
-
|
|
3
|
-
const aiConsole = {
|
|
4
|
-
// 日志相关工具函数
|
|
5
|
-
logInfo(message) {
|
|
6
|
-
this.log(message, '#6dd2ea')
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
logSuccess(message) {
|
|
10
|
-
this.log(message, '#9bed7f')
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
logError(message) {
|
|
14
|
-
this.log(message, '#ed7f7f')
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
logWarning(message) {
|
|
18
|
-
this.log(message, '#f2c94c')
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
logDisabled(message) {
|
|
22
|
-
this.log(message, '#999999')
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
writeLine(msg1, msg2 = '', color = 'blue') {
|
|
26
|
-
if (color === 'blue') {
|
|
27
|
-
process.stdout.write('\r' + chalk.hex('#6dd2ea')(msg1) + ' ' + msg2)
|
|
28
|
-
} else if (color === 'green') {
|
|
29
|
-
process.stdout.write('\r' + chalk.hex('#9bed7f')(msg1) + ' ' + msg2)
|
|
30
|
-
} else if (color === 'red') {
|
|
31
|
-
process.stdout.write('\r' + chalk.hex('#ed7f7f')(msg1) + ' ' + msg2)
|
|
32
|
-
} else {
|
|
33
|
-
process.stdout.write('\r' + chalk.hex(color)(msg1) + ' ' + msg2)
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
// 流式输出
|
|
38
|
-
async streamOutput(text, color = '#9bed7f') {
|
|
39
|
-
process.stdout.write(chalk.hex(color)(text))
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
// 流式换行
|
|
43
|
-
async streamLineBreak() {
|
|
44
|
-
process.stdout.write('\n')
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
loading(label = 'Thinking...') {
|
|
48
|
-
let animationInterval
|
|
49
|
-
const spinners = ['|', '/', '-', '\\']
|
|
50
|
-
let spinnerIndex = 0
|
|
51
|
-
process.stdout.write('\r')
|
|
52
|
-
animationInterval = setInterval(() => {
|
|
53
|
-
this.writeLine(spinners[spinnerIndex], label)
|
|
54
|
-
spinnerIndex = (spinnerIndex + 1) % spinners.length
|
|
55
|
-
}, 200)
|
|
56
|
-
return (endLabel, isError = false) => {
|
|
57
|
-
clearInterval(animationInterval)
|
|
58
|
-
if (endLabel) {
|
|
59
|
-
this.writeLine(endLabel, '', isError ? 'red' : 'green')
|
|
60
|
-
}
|
|
61
|
-
process.stdout.write('\r\n')
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
log(msg, color) {
|
|
66
|
-
if (!color) {
|
|
67
|
-
console.log(msg)
|
|
68
|
-
} else {
|
|
69
|
-
console.log(chalk.hex(color)(msg))
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
module.exports = aiConsole
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const inquirer = require('inquirer').default
|
|
2
|
-
|
|
3
|
-
// 判断问答
|
|
4
|
-
async function askConfirm(message, defaultVal = true, opt = {}) {
|
|
5
|
-
const questions = [
|
|
6
|
-
{
|
|
7
|
-
type: 'confirm',
|
|
8
|
-
name: 'confirm',
|
|
9
|
-
message,
|
|
10
|
-
default: defaultVal,
|
|
11
|
-
...opt
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
const answers = await inquirer.prompt(questions)
|
|
15
|
-
return answers['confirm']
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// 选择问答
|
|
19
|
-
async function askList(message, choices, defaultVal = 0, opt = {}) {
|
|
20
|
-
const questions = [
|
|
21
|
-
{
|
|
22
|
-
type: 'list',
|
|
23
|
-
name: 'list',
|
|
24
|
-
message,
|
|
25
|
-
choices,
|
|
26
|
-
default: defaultVal,
|
|
27
|
-
...opt
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
const answers = await inquirer.prompt(questions)
|
|
31
|
-
return answers['list']
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 输入问答
|
|
35
|
-
async function askInput(message, defaultVal = '', opt = {}) {
|
|
36
|
-
const questions = [
|
|
37
|
-
{
|
|
38
|
-
type: 'input',
|
|
39
|
-
name: 'input',
|
|
40
|
-
message,
|
|
41
|
-
default: defaultVal,
|
|
42
|
-
...opt
|
|
43
|
-
},
|
|
44
|
-
]
|
|
45
|
-
const answers = await inquirer.prompt(questions)
|
|
46
|
-
return answers['input']
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 输入数字
|
|
50
|
-
async function askNumber(message, defaultVal = 0, opt = {}) {
|
|
51
|
-
const questions = [
|
|
52
|
-
{
|
|
53
|
-
type: 'number',
|
|
54
|
-
name: 'number',
|
|
55
|
-
message,
|
|
56
|
-
default: defaultVal,
|
|
57
|
-
...opt
|
|
58
|
-
},
|
|
59
|
-
]
|
|
60
|
-
const answers = await inquirer.prompt(questions)
|
|
61
|
-
return answers['number']
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 输入任何
|
|
65
|
-
function askAny(questions) {
|
|
66
|
-
return inquirer.prompt(questions)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const aiInquirer = {
|
|
70
|
-
askAny,
|
|
71
|
-
askConfirm,
|
|
72
|
-
askList,
|
|
73
|
-
askInput,
|
|
74
|
-
askNumber,
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
module.exports = aiInquirer
|