deepfish-ai 1.0.28 → 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 +50 -70
- 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 -263
- 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 -343
- 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,257 +0,0 @@
|
|
|
1
|
-
const axios = require('axios')
|
|
2
|
-
const cheerio = require('cheerio')
|
|
3
|
-
const puppeteer = require('puppeteer')
|
|
4
|
-
|
|
5
|
-
function ok(data = null) {
|
|
6
|
-
return { success: true, data }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function fail(error, data = null) {
|
|
10
|
-
return { success: false, error: error?.message || String(error), data }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function normalizeWhitespace(text = '') {
|
|
14
|
-
return String(text || '').replace(/\s+/g, ' ').trim()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function absoluteUrl(baseUrl, href = '') {
|
|
18
|
-
try {
|
|
19
|
-
return new URL(href, baseUrl).toString()
|
|
20
|
-
} catch {
|
|
21
|
-
return href
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function extractByCheerio(html = '', url = '') {
|
|
26
|
-
const $ = cheerio.load(html)
|
|
27
|
-
$('script, style, noscript').remove()
|
|
28
|
-
|
|
29
|
-
const title = normalizeWhitespace($('title').first().text())
|
|
30
|
-
const bodyText = normalizeWhitespace($('body').text())
|
|
31
|
-
const metaDescription = normalizeWhitespace(
|
|
32
|
-
$('meta[name="description"]').attr('content') ||
|
|
33
|
-
$('meta[property="og:description"]').attr('content') ||
|
|
34
|
-
'',
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
const links = []
|
|
38
|
-
$('a[href]').each((_, el) => {
|
|
39
|
-
const href = $(el).attr('href') || ''
|
|
40
|
-
const text = normalizeWhitespace($(el).text())
|
|
41
|
-
if (!href) return
|
|
42
|
-
links.push({
|
|
43
|
-
href: absoluteUrl(url, href),
|
|
44
|
-
text,
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
title,
|
|
50
|
-
description: metaDescription,
|
|
51
|
-
content: bodyText,
|
|
52
|
-
links,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function fetchStatic(url, timeout = 15000) {
|
|
57
|
-
const response = await axios.get(url, {
|
|
58
|
-
timeout,
|
|
59
|
-
headers: {
|
|
60
|
-
'User-Agent':
|
|
61
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130 Safari/537.36 DeepFish-MCP-Web/1.0',
|
|
62
|
-
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
63
|
-
},
|
|
64
|
-
})
|
|
65
|
-
return response.data
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function fetchDynamic(url, waitUntil = 'networkidle2', timeout = 30000) {
|
|
69
|
-
const browser = await puppeteer.launch({
|
|
70
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
71
|
-
})
|
|
72
|
-
try {
|
|
73
|
-
const page = await browser.newPage()
|
|
74
|
-
await page.goto(url, { waitUntil, timeout })
|
|
75
|
-
return await page.content()
|
|
76
|
-
} finally {
|
|
77
|
-
await browser.close()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function mcpBrowseWebpage(url, mode = 'auto', maxChars = 4000) {
|
|
82
|
-
try {
|
|
83
|
-
if (!url) {
|
|
84
|
-
return fail('url is required')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let html = ''
|
|
88
|
-
let resolvedMode = mode
|
|
89
|
-
|
|
90
|
-
if (mode === 'dynamic') {
|
|
91
|
-
html = await fetchDynamic(url)
|
|
92
|
-
} else if (mode === 'static') {
|
|
93
|
-
html = await fetchStatic(url)
|
|
94
|
-
} else {
|
|
95
|
-
try {
|
|
96
|
-
html = await fetchStatic(url)
|
|
97
|
-
resolvedMode = 'static'
|
|
98
|
-
} catch {
|
|
99
|
-
html = await fetchDynamic(url)
|
|
100
|
-
resolvedMode = 'dynamic'
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const parsed = extractByCheerio(html, url)
|
|
105
|
-
const content = parsed.content.slice(0, Number(maxChars) > 0 ? Number(maxChars) : 4000)
|
|
106
|
-
const links = parsed.links.slice(0, 50)
|
|
107
|
-
|
|
108
|
-
return ok({
|
|
109
|
-
url,
|
|
110
|
-
mode: resolvedMode,
|
|
111
|
-
title: parsed.title,
|
|
112
|
-
description: parsed.description,
|
|
113
|
-
content,
|
|
114
|
-
contentLength: parsed.content.length,
|
|
115
|
-
links,
|
|
116
|
-
linkCount: parsed.links.length,
|
|
117
|
-
})
|
|
118
|
-
} catch (error) {
|
|
119
|
-
return fail(error, { url, mode, maxChars })
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function mcpFetchWebpageByQuery(url, query, mode = 'auto', limit = 20) {
|
|
124
|
-
try {
|
|
125
|
-
if (!url) {
|
|
126
|
-
return fail('url is required')
|
|
127
|
-
}
|
|
128
|
-
if (!query) {
|
|
129
|
-
return fail('query is required')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const browseResult = await mcpBrowseWebpage(url, mode, 200000)
|
|
133
|
-
if (!browseResult.success) {
|
|
134
|
-
return browseResult
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const keyword = String(query).toLowerCase().trim()
|
|
138
|
-
const rawContent = browseResult.data.content || ''
|
|
139
|
-
const sentences = rawContent
|
|
140
|
-
.split(/[。!?.!?\n]/)
|
|
141
|
-
.map((item) => normalizeWhitespace(item))
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
|
|
144
|
-
const matchedSentences = sentences
|
|
145
|
-
.map((text) => {
|
|
146
|
-
const lower = text.toLowerCase()
|
|
147
|
-
let score = 0
|
|
148
|
-
let fromIndex = 0
|
|
149
|
-
while (true) {
|
|
150
|
-
const idx = lower.indexOf(keyword, fromIndex)
|
|
151
|
-
if (idx < 0) break
|
|
152
|
-
score += 1
|
|
153
|
-
fromIndex = idx + keyword.length
|
|
154
|
-
}
|
|
155
|
-
return { text, score }
|
|
156
|
-
})
|
|
157
|
-
.filter((item) => item.score > 0)
|
|
158
|
-
.sort((a, b) => b.score - a.score)
|
|
159
|
-
.slice(0, Number(limit) > 0 ? Number(limit) : 20)
|
|
160
|
-
|
|
161
|
-
const matchedLinks = (browseResult.data.links || [])
|
|
162
|
-
.filter((item) => {
|
|
163
|
-
const href = String(item.href || '').toLowerCase()
|
|
164
|
-
const text = String(item.text || '').toLowerCase()
|
|
165
|
-
return href.includes(keyword) || text.includes(keyword)
|
|
166
|
-
})
|
|
167
|
-
.slice(0, Number(limit) > 0 ? Number(limit) : 20)
|
|
168
|
-
|
|
169
|
-
return ok({
|
|
170
|
-
url,
|
|
171
|
-
query,
|
|
172
|
-
mode: browseResult.data.mode,
|
|
173
|
-
title: browseResult.data.title,
|
|
174
|
-
matchedSentenceCount: matchedSentences.length,
|
|
175
|
-
matchedLinkCount: matchedLinks.length,
|
|
176
|
-
matchedSentences,
|
|
177
|
-
matchedLinks,
|
|
178
|
-
})
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return fail(error, { url, query, mode, limit })
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const descriptions = [
|
|
185
|
-
{
|
|
186
|
-
type: 'function',
|
|
187
|
-
function: {
|
|
188
|
-
name: 'mcpBrowseWebpage',
|
|
189
|
-
description:
|
|
190
|
-
'MCP网页浏览: 打开并抓取任意网页的标题、摘要正文与链接列表。适合快速浏览网页内容。',
|
|
191
|
-
parameters: {
|
|
192
|
-
type: 'object',
|
|
193
|
-
properties: {
|
|
194
|
-
url: {
|
|
195
|
-
type: 'string',
|
|
196
|
-
description: '要浏览的网页地址(http或https)。',
|
|
197
|
-
},
|
|
198
|
-
mode: {
|
|
199
|
-
type: 'string',
|
|
200
|
-
description: '抓取模式:auto|static|dynamic。默认auto。',
|
|
201
|
-
},
|
|
202
|
-
maxChars: {
|
|
203
|
-
type: 'number',
|
|
204
|
-
description: '返回正文最大字符数,默认4000。',
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
required: ['url'],
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
type: 'function',
|
|
213
|
-
function: {
|
|
214
|
-
name: 'mcpFetchWebpageByQuery',
|
|
215
|
-
description:
|
|
216
|
-
'MCP网页抓取: 按关键词从网页正文和链接中提取匹配内容,返回高相关片段。',
|
|
217
|
-
parameters: {
|
|
218
|
-
type: 'object',
|
|
219
|
-
properties: {
|
|
220
|
-
url: {
|
|
221
|
-
type: 'string',
|
|
222
|
-
description: '目标网页地址(http或https)。',
|
|
223
|
-
},
|
|
224
|
-
query: {
|
|
225
|
-
type: 'string',
|
|
226
|
-
description: '要匹配的关键词。',
|
|
227
|
-
},
|
|
228
|
-
mode: {
|
|
229
|
-
type: 'string',
|
|
230
|
-
description: '抓取模式:auto|static|dynamic。默认auto。',
|
|
231
|
-
},
|
|
232
|
-
limit: {
|
|
233
|
-
type: 'number',
|
|
234
|
-
description: '返回匹配结果上限,默认20。',
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
required: ['url', 'query'],
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
]
|
|
242
|
-
|
|
243
|
-
const functions = {
|
|
244
|
-
mcpBrowseWebpage,
|
|
245
|
-
mcpFetchWebpageByQuery,
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const MCPWebTool = {
|
|
249
|
-
name: 'MCPWebTool',
|
|
250
|
-
description: '提供网页浏览与内容抓取能力,支持任意网页的读取与关键词提取。',
|
|
251
|
-
platform: 'all',
|
|
252
|
-
descriptions,
|
|
253
|
-
functions,
|
|
254
|
-
isSystem: true
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
module.exports = MCPWebTool
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
const { OpenAI } = require('openai')
|
|
2
|
-
const {
|
|
3
|
-
refreshGithubModelsTokenIfNeeded,
|
|
4
|
-
buildDefaultHeaders,
|
|
5
|
-
} = require('./copilot.js')
|
|
6
|
-
|
|
7
|
-
function normalizeAiRequestConfig(aiConfig = {}) {
|
|
8
|
-
const max_tokens = (!aiConfig.maxTokens || aiConfig.maxTokens === -1)
|
|
9
|
-
? undefined
|
|
10
|
-
: aiConfig.maxTokens * 1024 // 兼容旧配置,按 1KB ~= 1024 tokens 估算
|
|
11
|
-
|
|
12
|
-
const requestConfig = {
|
|
13
|
-
model: aiConfig.model,
|
|
14
|
-
temperature: aiConfig.temperature,
|
|
15
|
-
stream: aiConfig.stream,
|
|
16
|
-
max_tokens,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 仅 DeepSeek 兼容端注入扩展字段,避免其它网关因未知参数报错
|
|
20
|
-
if (aiConfig.type === 'deepseek') {
|
|
21
|
-
requestConfig.extra_body = { reasoning_split: true }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return requestConfig
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function creatClient(aiConfig) {
|
|
28
|
-
if (aiConfig.type === 'github-models') {
|
|
29
|
-
if (!aiConfig.apiKey || !String(aiConfig.apiKey).trim()) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
'GitHub Models requires apiKey. Please set a GitHub token with models:read permission in your current AI config.',
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (aiConfig.type === 'copilot') {
|
|
36
|
-
if (!aiConfig.apiKey || !String(aiConfig.apiKey).trim()) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
'GitHub Copilot requires apiKey. Please set a GitHub Copilot access token in your current AI config.',
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return new OpenAI({
|
|
44
|
-
baseURL: aiConfig.baseUrl,
|
|
45
|
-
apiKey: aiConfig.apiKey || '',
|
|
46
|
-
defaultHeaders: buildDefaultHeaders(aiConfig),
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Ai单轮问答
|
|
52
|
-
* @param {*} openAiClient OpenAI客户端
|
|
53
|
-
* @param {*} aiConfig {model, temperature, maxTokens, stream}
|
|
54
|
-
* @param {*} systemDescription
|
|
55
|
-
* @param {*} prompt
|
|
56
|
-
* @param {*} temperature
|
|
57
|
-
* @returns
|
|
58
|
-
*/
|
|
59
|
-
async function think(
|
|
60
|
-
openAiClient,
|
|
61
|
-
aiConfig,
|
|
62
|
-
messages,
|
|
63
|
-
thinkBefore = () => {},
|
|
64
|
-
thinkAfter = () => {},
|
|
65
|
-
streamThinkOutput,
|
|
66
|
-
streamContentOutput,
|
|
67
|
-
streamToolCallsOutput,
|
|
68
|
-
streamEnd,
|
|
69
|
-
) {
|
|
70
|
-
try {
|
|
71
|
-
await refreshGithubModelsTokenIfNeeded(aiConfig)
|
|
72
|
-
const requestClient = creatClient(aiConfig)
|
|
73
|
-
const requestConfig = normalizeAiRequestConfig(aiConfig)
|
|
74
|
-
const opt = {
|
|
75
|
-
messages: messages,
|
|
76
|
-
...requestConfig,
|
|
77
|
-
}
|
|
78
|
-
await thinkBefore()
|
|
79
|
-
const extraHeaders = aiConfig.type === 'copilot' ? { 'x-initiator': 'user' } : undefined
|
|
80
|
-
const response = await requestClient.chat.completions.create(opt, extraHeaders ? { headers: extraHeaders } : undefined)
|
|
81
|
-
if (aiConfig.stream) {
|
|
82
|
-
const messageRes = await _streamToNonStream(
|
|
83
|
-
response,
|
|
84
|
-
streamThinkOutput,
|
|
85
|
-
streamContentOutput,
|
|
86
|
-
streamToolCallsOutput,
|
|
87
|
-
streamEnd,
|
|
88
|
-
)
|
|
89
|
-
await thinkAfter()
|
|
90
|
-
return clearThinkTag(messageRes.choices[0].message.content)
|
|
91
|
-
}
|
|
92
|
-
await thinkAfter()
|
|
93
|
-
return clearThinkTag(response.choices[0].message.content)
|
|
94
|
-
} catch (error) {
|
|
95
|
-
throw new Error(`AI response error: ${error.message}`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Ai携带工具请求
|
|
100
|
-
* @param {*} openAiClient
|
|
101
|
-
* @param {*} aiConfig {model, temperature, maxTokens}
|
|
102
|
-
* @param {*} messages
|
|
103
|
-
* @param {*} functionDescriptions
|
|
104
|
-
* @returns
|
|
105
|
-
*/
|
|
106
|
-
async function thinkByTool(
|
|
107
|
-
openAiClient,
|
|
108
|
-
aiConfig,
|
|
109
|
-
messages,
|
|
110
|
-
functionDescriptions,
|
|
111
|
-
thinkBefore = () => {},
|
|
112
|
-
thinkAfter = () => {},
|
|
113
|
-
streamThinkOutput,
|
|
114
|
-
streamContentOutput,
|
|
115
|
-
streamToolCallsOutput,
|
|
116
|
-
streamEnd,
|
|
117
|
-
) {
|
|
118
|
-
try {
|
|
119
|
-
await refreshGithubModelsTokenIfNeeded(aiConfig)
|
|
120
|
-
const requestClient = creatClient(aiConfig)
|
|
121
|
-
const requestConfig = normalizeAiRequestConfig(aiConfig)
|
|
122
|
-
await thinkBefore()
|
|
123
|
-
const extraHeaders = aiConfig.type === 'copilot' ? { 'x-initiator': 'agent' } : undefined
|
|
124
|
-
const response = await requestClient.chat.completions.create(
|
|
125
|
-
{
|
|
126
|
-
messages: messages,
|
|
127
|
-
tools: functionDescriptions,
|
|
128
|
-
tool_choice: 'auto',
|
|
129
|
-
...requestConfig,
|
|
130
|
-
},
|
|
131
|
-
extraHeaders ? { headers: extraHeaders } : undefined,
|
|
132
|
-
)
|
|
133
|
-
if (aiConfig.stream) {
|
|
134
|
-
const messageRes = await _streamToNonStream(
|
|
135
|
-
response,
|
|
136
|
-
streamThinkOutput,
|
|
137
|
-
streamContentOutput,
|
|
138
|
-
streamToolCallsOutput,
|
|
139
|
-
streamEnd,
|
|
140
|
-
)
|
|
141
|
-
await thinkAfter()
|
|
142
|
-
messageRes.choices[0].message.content = clearThinkTag(messageRes.choices[0].message.content)
|
|
143
|
-
return {
|
|
144
|
-
content: messageRes.choices[0].message.content,
|
|
145
|
-
tool_calls: messageRes.choices[0].message.tool_calls,
|
|
146
|
-
message: messageRes.choices[0].message,
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
await thinkAfter()
|
|
150
|
-
response.choices[0].message.content = clearThinkTag(response.choices[0].message.content)
|
|
151
|
-
return {
|
|
152
|
-
content: response.choices[0].message.content,
|
|
153
|
-
tool_calls: response.choices[0].message.tool_calls,
|
|
154
|
-
message: response.choices[0].message,
|
|
155
|
-
}
|
|
156
|
-
} catch (error) {
|
|
157
|
-
throw new Error(`AI response error: ${error.message}`)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 清除字符串中<think></think>标签以及之间的内容
|
|
162
|
-
function clearThinkTag(content) {
|
|
163
|
-
return content.replace(/<think>[\s\S]*?<\/think>/g, '')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 流式输出结果转非流式输出
|
|
167
|
-
async function _streamToNonStream(
|
|
168
|
-
stream,
|
|
169
|
-
streamThinkOutput = () => {},
|
|
170
|
-
streamContentOutput = () => {},
|
|
171
|
-
streamToolCallsOutput = () => {},
|
|
172
|
-
streamEnd = () => {},
|
|
173
|
-
) {
|
|
174
|
-
// 初始化最终响应结构(对齐 OpenAI 非流式响应格式)
|
|
175
|
-
const finalResponse = {
|
|
176
|
-
id: '',
|
|
177
|
-
object: 'chat.completion',
|
|
178
|
-
created: Math.floor(Date.now() / 1000), // 生成时间戳
|
|
179
|
-
model: '',
|
|
180
|
-
choices: [
|
|
181
|
-
{
|
|
182
|
-
index: 0,
|
|
183
|
-
message: {
|
|
184
|
-
role: 'assistant',
|
|
185
|
-
content: '',
|
|
186
|
-
reasoning_content: '',
|
|
187
|
-
tool_calls: [], // 存储完整的工具调用列表
|
|
188
|
-
},
|
|
189
|
-
finish_reason: null,
|
|
190
|
-
logprobs: null,
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
usage: {
|
|
194
|
-
prompt_tokens: 0,
|
|
195
|
-
completion_tokens: 0,
|
|
196
|
-
total_tokens: 0,
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 工具调用缓冲区:处理多工具调用 + 分段参数拼接
|
|
201
|
-
const toolCallBuffers = new Map() // key: tool_call_id, value: toolCall object
|
|
202
|
-
const toolCallIndexMap = new Map() // key: index, value: tool_call_id
|
|
203
|
-
try {
|
|
204
|
-
// 遍历所有流式数据块
|
|
205
|
-
for await (const chunk of stream) {
|
|
206
|
-
// 1. 填充全局信息(仅首次获取)
|
|
207
|
-
if (!finalResponse.id) {
|
|
208
|
-
finalResponse.id = chunk.id || `chatcmpl-${Date.now()}`
|
|
209
|
-
}
|
|
210
|
-
if (!finalResponse.model) {
|
|
211
|
-
finalResponse.model = chunk.model || 'deepseek-reasoner'
|
|
212
|
-
}
|
|
213
|
-
const choice = chunk.choices[0]
|
|
214
|
-
if (!choice) {
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
217
|
-
const delta = choice.delta
|
|
218
|
-
if (!delta) {
|
|
219
|
-
continue
|
|
220
|
-
}
|
|
221
|
-
// 2. 处理普通文本内容
|
|
222
|
-
const reasoning_content = delta.reasoning_content
|
|
223
|
-
if (reasoning_content) {
|
|
224
|
-
if (finalResponse.choices[0].message.reasoning_content.length === 0) {
|
|
225
|
-
streamThinkOutput('[Thinking...]')
|
|
226
|
-
}
|
|
227
|
-
finalResponse.choices[0].message.reasoning_content += reasoning_content
|
|
228
|
-
// 流式输出
|
|
229
|
-
streamThinkOutput(reasoning_content)
|
|
230
|
-
}
|
|
231
|
-
const content = delta.content
|
|
232
|
-
if (content) {
|
|
233
|
-
if (finalResponse.choices[0].message.content.length === 0) {
|
|
234
|
-
streamContentOutput('\r\n')
|
|
235
|
-
}
|
|
236
|
-
finalResponse.choices[0].message.content += content
|
|
237
|
-
// 流式输出
|
|
238
|
-
streamContentOutput(content)
|
|
239
|
-
}
|
|
240
|
-
// 3. 处理工具调用(核心逻辑)
|
|
241
|
-
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
242
|
-
delta.tool_calls.forEach((toolCallChunk) => {
|
|
243
|
-
const index = toolCallChunk.index
|
|
244
|
-
if (toolCallChunk.id) {
|
|
245
|
-
const id = toolCallChunk.id
|
|
246
|
-
toolCallIndexMap.set(index, id)
|
|
247
|
-
let toolCall = toolCallBuffers.get(id)
|
|
248
|
-
if (!toolCall) {
|
|
249
|
-
toolCall = {
|
|
250
|
-
id: id,
|
|
251
|
-
type: toolCallChunk.type || 'function',
|
|
252
|
-
function: {
|
|
253
|
-
name: toolCallChunk.function.name,
|
|
254
|
-
arguments: '',
|
|
255
|
-
},
|
|
256
|
-
}
|
|
257
|
-
toolCallBuffers.set(id, toolCall)
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
const id = toolCallIndexMap.get(index)
|
|
261
|
-
const toolCall = toolCallBuffers.get(id)
|
|
262
|
-
if (toolCall && toolCallChunk.function?.arguments) {
|
|
263
|
-
if (toolCall.function.arguments.length === 0) {
|
|
264
|
-
streamToolCallsOutput(`\r\n[ToolCalls...][${toolCall.function.name}]`)
|
|
265
|
-
}
|
|
266
|
-
toolCall.function.arguments += toolCallChunk.function.arguments
|
|
267
|
-
streamToolCallsOutput(toolCallChunk.function.arguments)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// 4. 处理结束标记
|
|
274
|
-
if (choice.finish_reason) {
|
|
275
|
-
finalResponse.choices[0].finish_reason = choice.finish_reason
|
|
276
|
-
|
|
277
|
-
// 工具调用结束:将缓冲区数据写入最终响应
|
|
278
|
-
if (choice.finish_reason === 'tool_calls' && toolCallBuffers.size > 0) {
|
|
279
|
-
finalResponse.choices[0].message.content = '' // 工具调用时 content 为 null
|
|
280
|
-
finalResponse.choices[0].message.tool_calls = Array.from(
|
|
281
|
-
toolCallBuffers.values(),
|
|
282
|
-
)
|
|
283
|
-
} else {
|
|
284
|
-
finalResponse.choices[0].message.tool_calls = undefined
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
streamEnd()
|
|
289
|
-
return finalResponse
|
|
290
|
-
} catch (error) {
|
|
291
|
-
console.error('流式数据转换失败:', error.message)
|
|
292
|
-
throw error // 抛出错误让上层处理
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
module.exports = { creatClient, think, thinkByTool }
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const os = require('os')
|
|
3
|
-
const fs = require('fs-extra')
|
|
4
|
-
const dayjs = require('dayjs')
|
|
5
|
-
const axios = require('axios')
|
|
6
|
-
const echarts = require('echarts')
|
|
7
|
-
const canvas = require('canvas')
|
|
8
|
-
const cheerio = require('cheerio')
|
|
9
|
-
const puppeteer = require('puppeteer')
|
|
10
|
-
const lodash = require('lodash')
|
|
11
|
-
const { AttachmentToolScanner } = require('./AttachmentToolScanner')
|
|
12
|
-
|
|
13
|
-
class AIToolManager {
|
|
14
|
-
originalTools = null // 原装工具
|
|
15
|
-
attachTools = null // 附加工具, Agent后续安装的工具函数
|
|
16
|
-
functions = {} // key为函数名称,value为方法体
|
|
17
|
-
descriptions = [] // openai能识别的描述
|
|
18
|
-
tools = [] // 工具列表
|
|
19
|
-
toolCollection = null // 工具集合
|
|
20
|
-
clawSkillCollection = null // Claw技能集合
|
|
21
|
-
constructor(agentRobot) {
|
|
22
|
-
this.agentRobot = agentRobot
|
|
23
|
-
this.initTools(agentRobot.opt)
|
|
24
|
-
}
|
|
25
|
-
initTools(opt) {
|
|
26
|
-
this.originalTools = this._getOriginalTools() // 天赋技能
|
|
27
|
-
this.attachTools = opt.attachTools || [] // 附加工具, Agent后续安装的工具函数
|
|
28
|
-
const tools = [...this.originalTools, ...this.attachTools]
|
|
29
|
-
tools.forEach((tool) => {
|
|
30
|
-
this._addTool(tool)
|
|
31
|
-
})
|
|
32
|
-
Object.assign(this.functions, {
|
|
33
|
-
fs,
|
|
34
|
-
axios,
|
|
35
|
-
dayjs,
|
|
36
|
-
lodash,
|
|
37
|
-
canvas,
|
|
38
|
-
echarts,
|
|
39
|
-
cheerio,
|
|
40
|
-
puppeteer,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
this.functions.agentRobot = this.agentRobot
|
|
44
|
-
this.functions.Tools = this.functions
|
|
45
|
-
// 兼容老版本
|
|
46
|
-
this.functions.aiCli = {
|
|
47
|
-
Tools: this.functions,
|
|
48
|
-
}
|
|
49
|
-
// 外部工具扫描
|
|
50
|
-
this.toolCollection = AttachmentToolScanner.getToolCollection(
|
|
51
|
-
this.agentRobot.workspace,
|
|
52
|
-
this.agentRobot.basespace,
|
|
53
|
-
) // 加载工具集合
|
|
54
|
-
this.clawSkillCollection = AttachmentToolScanner.getClawSkillCollection(
|
|
55
|
-
this.agentRobot.basespace,
|
|
56
|
-
) // 加载Claw技能集合
|
|
57
|
-
this.agentRobot.toolCollection = this.toolCollection
|
|
58
|
-
this.agentRobot.clawSkillCollection = this.clawSkillCollection
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 动态添加工具
|
|
62
|
-
addTool(toolName) {
|
|
63
|
-
let tool = this.tools.find((t) => t.name === toolName)
|
|
64
|
-
if (!tool) {
|
|
65
|
-
tool = this.toolCollection.find((t) => t.name === toolName)
|
|
66
|
-
if (tool) {
|
|
67
|
-
this._addTool(tool)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
_addTool(tool) {
|
|
73
|
-
const platform = tool.platform || 'all'
|
|
74
|
-
if (platform === 'all' || platform === process.platform) {
|
|
75
|
-
tool.descriptions = tool.descriptions.map((item) => {
|
|
76
|
-
if (!item.type) {
|
|
77
|
-
item = {
|
|
78
|
-
type: 'function',
|
|
79
|
-
function: item,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return item
|
|
83
|
-
})
|
|
84
|
-
tool.isSystem = true
|
|
85
|
-
if (tool.name && !tool.isSystem) {
|
|
86
|
-
for (const funcName in tool.functions) {
|
|
87
|
-
if (!funcName.includes('_')) {
|
|
88
|
-
this.functions[`${tool.name}_${funcName}`] =
|
|
89
|
-
tool.functions[funcName]
|
|
90
|
-
} else {
|
|
91
|
-
this.functions[funcName] = tool.functions[funcName]
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const descriptions = tool.descriptions.map((item) => {
|
|
95
|
-
if (
|
|
96
|
-
tool.name &&
|
|
97
|
-
item.function.name &&
|
|
98
|
-
!item.function.name.includes('_')
|
|
99
|
-
) {
|
|
100
|
-
item.function.name = `${tool.name}_${item.function.name}`
|
|
101
|
-
return item
|
|
102
|
-
} else {
|
|
103
|
-
return item
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
this.descriptions.push(...descriptions)
|
|
107
|
-
} else {
|
|
108
|
-
Object.assign(this.functions, tool.functions)
|
|
109
|
-
this.descriptions.push(...tool.descriptions)
|
|
110
|
-
}
|
|
111
|
-
this.tools.push(tool)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 获取原装工具
|
|
116
|
-
_getOriginalTools() {
|
|
117
|
-
// 自动扫描tools目录
|
|
118
|
-
const toolsPath = path.join(__dirname, '../tools')
|
|
119
|
-
const toolFiles = fs.readdirSync(toolsPath).filter((file) => {
|
|
120
|
-
return file.endsWith('.js') || file.endsWith('.cjs')
|
|
121
|
-
})
|
|
122
|
-
const tools = []
|
|
123
|
-
toolFiles.forEach((file) => {
|
|
124
|
-
const tool = require(path.join(toolsPath, file))
|
|
125
|
-
tools.push(tool)
|
|
126
|
-
})
|
|
127
|
-
return tools
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
module.exports = AIToolManager
|