@myassis/gateway 1.0.0 → 1.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/dist/api/index.js +69 -59
- package/dist/config/index.js +2 -2
- package/dist/index.js +85 -11
- package/dist/middleware/auth.js +9 -25
- package/dist/middleware/errorHandler.js +1 -1
- package/dist/routes/agent.js +30 -19
- package/dist/routes/auth.js +35 -26
- package/dist/routes/chat.js +1 -1
- package/dist/routes/models.js +11 -14
- package/dist/routes/service.js +3 -6
- package/dist/routes/settings.js +11 -9
- package/dist/routes/skillHub.js +10 -6
- package/dist/routes/skills.js +13 -10
- package/dist/routes/tasks.js +6 -3
- package/dist/routes/upload.js +5 -2
- package/dist/routes/version.js +4 -4
- package/dist/services/LocalTaskService.js +1 -1
- package/dist/services/NotificationService.js +1 -1
- package/dist/services/ServiceManager.js +12 -12
- package/dist/services/TaskSchedulerService.js +18 -9
- package/dist/services/TaskService.js +23 -20
- package/dist/services/WebSocketService.js +16 -6
- package/dist/services/agent/Agent.js +7 -7
- package/dist/services/agent/AgentManager.js +25 -17
- package/dist/services/agent/AgentStore.js +1 -1
- package/dist/services/dataService.js +60 -70
- package/dist/services/index.js +1 -1
- package/dist/services/llm/LLMClient.js +11 -9
- package/dist/services/memory/MemoryManager.js +177 -14
- package/dist/services/session/MigrationManager.js +1 -1
- package/dist/services/session/Session.js +70 -46
- package/dist/services/session/SessionManager.js +12 -5
- package/dist/services/session/SessionStore.js +1 -27
- package/dist/services/session/index.js +1 -1
- package/dist/services/systemPrompt.js +8 -4
- package/dist/services/task/PushTokenStore.js +1 -19
- package/dist/services/task/TaskStore.js +1 -20
- package/dist/services/tools/edit.js +122 -148
- package/dist/services/tools/exec.js +1 -1
- package/dist/services/tools/fetch.js +1 -1
- package/dist/services/tools/file.js +4 -9
- package/dist/services/tools/index.js +4 -3
- package/dist/services/tools/model.js +8 -6
- package/dist/services/tools/sessionsSpawn.js +54 -0
- package/dist/services/tools/skill.js +13 -12
- package/dist/services/tools/task.js +2 -4
- package/dist/stores/authStore.js +52 -66
- package/dist/stores/persistStore.js +37 -3
- package/package.json +8 -4
- package/scripts/postbuild.js +39 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { persistStore } from "@/stores";
|
|
2
|
-
import { getLogger } from '@
|
|
2
|
+
import { getLogger } from '@myassis/shared';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
const logger = getLogger('LLMClient');
|
|
5
5
|
/**
|
|
@@ -249,6 +249,7 @@ export class LLMClient {
|
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
251
|
// OpenAI compatible API
|
|
252
|
+
let existAttachments = false;
|
|
252
253
|
const body = {
|
|
253
254
|
model: model.modelId,
|
|
254
255
|
messages: this.messages.map((m) => {
|
|
@@ -319,14 +320,8 @@ export class LLMClient {
|
|
|
319
320
|
}
|
|
320
321
|
// 处理用户消息中的附件
|
|
321
322
|
if (m.role === 'user' && m.attachments && m.attachments.length > 0) {
|
|
323
|
+
existAttachments = true;
|
|
322
324
|
const contentArray = [];
|
|
323
|
-
// 如果有文本内容,先添加文本
|
|
324
|
-
if (m.content && m.content.trim()) {
|
|
325
|
-
contentArray.push({
|
|
326
|
-
type: 'text',
|
|
327
|
-
text: m.content,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
325
|
// 添加附件
|
|
331
326
|
for (const attachment of m.attachments) {
|
|
332
327
|
if (attachment.type === 'image') {
|
|
@@ -373,6 +368,13 @@ export class LLMClient {
|
|
|
373
368
|
}
|
|
374
369
|
}
|
|
375
370
|
}
|
|
371
|
+
// 如果有文本内容,添加文本
|
|
372
|
+
if (m.content && m.content.trim()) {
|
|
373
|
+
contentArray.push({
|
|
374
|
+
type: 'text',
|
|
375
|
+
text: m.content,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
376
378
|
return [{
|
|
377
379
|
role: m.role,
|
|
378
380
|
content: contentArray,
|
|
@@ -392,7 +394,7 @@ export class LLMClient {
|
|
|
392
394
|
}
|
|
393
395
|
const tokens = JSON.stringify(body).length;
|
|
394
396
|
logger.debug('tokens', tokens);
|
|
395
|
-
if (tokens >
|
|
397
|
+
if (tokens > 200000) {
|
|
396
398
|
logger.warn('message is too large and has writen to the file debug.json');
|
|
397
399
|
await fs.writeFile('debug.json', JSON.stringify(body.messages), 'utf-8');
|
|
398
400
|
throw Error('exceed max message tokens');
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { getLogger } from '@
|
|
1
|
+
import { getLogger } from '@myassis/shared';
|
|
2
2
|
const logger = getLogger('MemoryManager');
|
|
3
3
|
import { toModel } from '../models';
|
|
4
4
|
import { modelsService } from '../dataService';
|
|
5
|
+
import { authStore } from '@/stores';
|
|
5
6
|
import { LLMClient } from '../llm/LLMClient';
|
|
6
7
|
import { appConfig } from '@/config';
|
|
7
8
|
const DEFAULT_CONFIG = {
|
|
@@ -16,9 +17,11 @@ export class MemoryManager {
|
|
|
16
17
|
session;
|
|
17
18
|
signal;
|
|
18
19
|
config = DEFAULT_CONFIG;
|
|
19
|
-
|
|
20
|
+
childAgent;
|
|
21
|
+
constructor(session, signal, childAgent) {
|
|
20
22
|
this.session = session;
|
|
21
23
|
this.signal = signal;
|
|
24
|
+
this.childAgent = childAgent;
|
|
22
25
|
}
|
|
23
26
|
toSummaryMessage(summary, createdAt, modelName) {
|
|
24
27
|
return {
|
|
@@ -32,7 +35,11 @@ export class MemoryManager {
|
|
|
32
35
|
}
|
|
33
36
|
async getHistoryMessagesAsync() {
|
|
34
37
|
// 1. 获取所有消息
|
|
35
|
-
|
|
38
|
+
let messages = this.session.getMessages();
|
|
39
|
+
if (this.childAgent) {
|
|
40
|
+
messages.splice(-1);
|
|
41
|
+
logger.debug('history', messages);
|
|
42
|
+
}
|
|
36
43
|
// 2. 消息少于摘要配置的阈值数,直接返回
|
|
37
44
|
logger.debug('message length:', messages.length);
|
|
38
45
|
if (messages.length < this.config.summaryThreshold) {
|
|
@@ -77,32 +84,120 @@ export class MemoryManager {
|
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 格式化工具调用内容,提取关键信息供摘要模型理解
|
|
89
|
+
*/
|
|
90
|
+
formatToolCalls(toolCalls) {
|
|
91
|
+
if (!toolCalls || toolCalls.length === 0)
|
|
92
|
+
return '';
|
|
93
|
+
return toolCalls.map(tc => {
|
|
94
|
+
const parts = [];
|
|
95
|
+
// 外层 toolCall 节点
|
|
96
|
+
if (tc.content)
|
|
97
|
+
parts.push(`思路: ${tc.content}`);
|
|
98
|
+
if (tc.reasoningContent)
|
|
99
|
+
parts.push(`推理: ${tc.reasoningContent}`);
|
|
100
|
+
// 内层工具调用详情
|
|
101
|
+
if (tc.toolCalls && tc.toolCalls.length > 0) {
|
|
102
|
+
const callDetails = tc.toolCalls.map((call) => {
|
|
103
|
+
const name = `${call.toolName}${call.actionName ? '.' + call.actionName : ''}`;
|
|
104
|
+
let detail = name;
|
|
105
|
+
if (call.input) {
|
|
106
|
+
try {
|
|
107
|
+
const input = typeof call.input === 'string' ? JSON.parse(call.input) : call.input;
|
|
108
|
+
// 提取关键输入信息,避免过长
|
|
109
|
+
const inputStr = JSON.stringify(input).slice(0, 500);
|
|
110
|
+
detail += ` 输入: ${inputStr}`;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
detail += ` 输入: ${String(call.input).slice(0, 500)}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (call.output) {
|
|
117
|
+
const outputStr = typeof call.output === 'string' ? call.output : JSON.stringify(call.output);
|
|
118
|
+
detail += ` 结果: ${outputStr.slice(0, 500)}`;
|
|
119
|
+
}
|
|
120
|
+
if (call.status === 'error')
|
|
121
|
+
detail += ' [失败]';
|
|
122
|
+
return detail;
|
|
123
|
+
}).join('; ');
|
|
124
|
+
parts.push(`工具调用: ${callDetails}`);
|
|
125
|
+
}
|
|
126
|
+
return parts.join('\n');
|
|
127
|
+
}).join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 格式化单条消息,提取有意义的内容
|
|
131
|
+
*/
|
|
132
|
+
formatMessage(m) {
|
|
133
|
+
const role = m.role === 'user' ? '用户' : m.role === 'assistant' ? '助手' : '系统';
|
|
134
|
+
let content = m.content || '';
|
|
135
|
+
// 附加工具调用信息
|
|
136
|
+
const toolCallInfo = this.formatToolCalls(m.toolCalls);
|
|
137
|
+
if (toolCallInfo) {
|
|
138
|
+
content += `\n[${toolCallInfo}]`;
|
|
139
|
+
}
|
|
140
|
+
// 截断过长内容
|
|
141
|
+
if (content.length > 2000) {
|
|
142
|
+
content = content.slice(0, 2000) + '...(内容过长已截断)';
|
|
143
|
+
}
|
|
144
|
+
return `${role}: ${content}`;
|
|
145
|
+
}
|
|
80
146
|
/**
|
|
81
147
|
* 生成摘要提示词
|
|
82
148
|
*/
|
|
83
149
|
buildSummaryPrompt(messages, lastSummary) {
|
|
84
150
|
const conversation = messages
|
|
85
151
|
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
86
|
-
.map((m) =>
|
|
87
|
-
.join('\n');
|
|
88
|
-
return
|
|
152
|
+
.map((m) => this.formatMessage(m))
|
|
153
|
+
.join('\n\n');
|
|
154
|
+
return `你是一个对话摘要专家。请分析以下对话内容,生成一份结构化的摘要。
|
|
155
|
+
|
|
156
|
+
重要规则:
|
|
157
|
+
1. 如果对话涉及代码开发/项目工作,必须详细总结项目结构,包括:技术栈、目录结构、关键文件及其职责、模块间关系
|
|
158
|
+
2. 项目结构描述不限字数,务必完整清晰
|
|
159
|
+
3. 保留所有关键决策、修改内容和上下文
|
|
160
|
+
4. 保留错误信息和解决方案,避免重复犯错
|
|
161
|
+
5. 如果有上一轮摘要,合并其中的关键信息
|
|
89
162
|
|
|
90
|
-
|
|
163
|
+
${lastSummary ? '=== 上一轮摘要 ===\n' + lastSummary + '\n\n' : ''}=== 对话内容 ===
|
|
91
164
|
${conversation}
|
|
92
165
|
|
|
93
|
-
|
|
166
|
+
=== 输出格式 ===
|
|
167
|
+
请按以下格式输出(各部分字数不限,以完整准确为优先):
|
|
168
|
+
|
|
169
|
+
【摘要】简要概括对话主题和关键进展
|
|
170
|
+
|
|
171
|
+
【项目结构】(如涉及项目开发)详细描述:
|
|
172
|
+
- 技术栈与框架
|
|
173
|
+
- 目录结构与各目录职责
|
|
174
|
+
- 关键文件及其作用
|
|
175
|
+
- 模块间依赖关系
|
|
176
|
+
- 已完成和待完成的功能
|
|
94
177
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
178
|
+
【关键决策】列出重要决策及原因
|
|
179
|
+
|
|
180
|
+
【要点】列出3-8个关键要点
|
|
181
|
+
|
|
182
|
+
【错误与修复】列出遇到的错误及解决方案,标注"⚠️"避免重复`;
|
|
99
183
|
}
|
|
100
184
|
/**
|
|
101
|
-
*
|
|
185
|
+
* 生成对话摘要(支持分层摘要)
|
|
102
186
|
*/
|
|
103
187
|
async generateSummaryAsync(messages, lastSummary) {
|
|
188
|
+
const MAX_INPUT_CHARS = 30000; // 单次摘要最大输入字符数
|
|
189
|
+
// 格式化所有消息用于估算长度
|
|
190
|
+
const formattedMessages = messages
|
|
191
|
+
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
192
|
+
.map((m) => this.formatMessage(m));
|
|
193
|
+
const totalChars = formattedMessages.join('').length;
|
|
194
|
+
if (totalChars > MAX_INPUT_CHARS && formattedMessages.length > 20) {
|
|
195
|
+
// 分层摘要:分批生成子摘要,再合并
|
|
196
|
+
return await this.hierarchicalSummary(formattedMessages, lastSummary);
|
|
197
|
+
}
|
|
104
198
|
const summaryPrompt = this.buildSummaryPrompt(messages, lastSummary);
|
|
105
|
-
const
|
|
199
|
+
const token = authStore.get(this.session.userId).accessToken;
|
|
200
|
+
const llmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: summaryPrompt }], this.signal, []);
|
|
106
201
|
const response = await llmClient.Chat();
|
|
107
202
|
const content = (response.content || '').trim();
|
|
108
203
|
if (!content) {
|
|
@@ -114,4 +209,72 @@ ${lastSummary ? '上一次内容摘要:\n' + lastSummary : ''}
|
|
|
114
209
|
}
|
|
115
210
|
return content;
|
|
116
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* 分层摘要:将消息分批生成子摘要,再合并为最终摘要
|
|
214
|
+
*/
|
|
215
|
+
async hierarchicalSummary(formattedMessages, lastSummary) {
|
|
216
|
+
const BATCH_SIZE = 15; // 每批消息数
|
|
217
|
+
const subSummaries = [];
|
|
218
|
+
for (let i = 0; i < formattedMessages.length; i += BATCH_SIZE) {
|
|
219
|
+
if (this.signal.aborted)
|
|
220
|
+
break;
|
|
221
|
+
const batch = formattedMessages.slice(i, i + BATCH_SIZE);
|
|
222
|
+
const batchPrompt = `请简洁总结以下对话片段的关键信息,特别关注:项目结构、关键决策、错误及修复。\n\n${batch.join('\n\n')}`;
|
|
223
|
+
const token = authStore.get(this.session.userId).accessToken;
|
|
224
|
+
const llmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: batchPrompt }], this.signal, []);
|
|
225
|
+
const response = await llmClient.Chat();
|
|
226
|
+
const subContent = (response.content || '').trim();
|
|
227
|
+
if (subContent) {
|
|
228
|
+
subSummaries.push(subContent);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// 合并所有子摘要
|
|
232
|
+
if (subSummaries.length === 0) {
|
|
233
|
+
return lastSummary || '(摘要生成失败)';
|
|
234
|
+
}
|
|
235
|
+
if (subSummaries.length === 1) {
|
|
236
|
+
return subSummaries[0];
|
|
237
|
+
}
|
|
238
|
+
// 合并子摘要为最终摘要
|
|
239
|
+
const mergePrompt = this.buildMergePrompt(subSummaries, lastSummary);
|
|
240
|
+
const token = authStore.get(this.session.userId).accessToken;
|
|
241
|
+
const mergeLlmClient = new LLMClient((await modelsService.list(token)).data.map((x) => toModel(x)), [{ role: 'user', content: mergePrompt }], this.signal, []);
|
|
242
|
+
const mergeResponse = await mergeLlmClient.Chat();
|
|
243
|
+
const mergedContent = (mergeResponse.content || '').trim();
|
|
244
|
+
return mergedContent || subSummaries.join('\n\n');
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 构建合并摘要提示词
|
|
248
|
+
*/
|
|
249
|
+
buildMergePrompt(subSummaries, lastSummary) {
|
|
250
|
+
const summaries = subSummaries
|
|
251
|
+
.map((s, i) => `--- 片段 ${i + 1} ---\n${s}`)
|
|
252
|
+
.join('\n\n');
|
|
253
|
+
return `你是一个对话摘要专家。以下是多个对话片段的摘要,请合并为一份完整的结构化摘要。
|
|
254
|
+
|
|
255
|
+
重要规则:
|
|
256
|
+
1. 合并重复信息,去重项目结构描述
|
|
257
|
+
2. 如果涉及项目开发,务必完整保留项目结构信息(技术栈、目录结构、关键文件、模块关系)
|
|
258
|
+
3. 按时间顺序整理决策和进展
|
|
259
|
+
4. 保留所有错误信息及解决方案
|
|
260
|
+
|
|
261
|
+
${lastSummary ? '=== 上一轮摘要 ===\n' + lastSummary + '\n\n' : ''}=== 分片段摘要 ===
|
|
262
|
+
${summaries}
|
|
263
|
+
|
|
264
|
+
=== 输出格式 ===
|
|
265
|
+
【摘要】简要概括对话主题和关键进展
|
|
266
|
+
|
|
267
|
+
【项目结构】(如涉及项目开发)详细描述:
|
|
268
|
+
- 技术栈与框架
|
|
269
|
+
- 目录结构与各目录职责
|
|
270
|
+
- 关键文件及其作用
|
|
271
|
+
- 模块间依赖关系
|
|
272
|
+
- 已完成和待完成的功能
|
|
273
|
+
|
|
274
|
+
【关键决策】列出重要决策及原因
|
|
275
|
+
|
|
276
|
+
【要点】列出3-8个关键要点
|
|
277
|
+
|
|
278
|
+
【错误与修复】列出遇到的错误及解决方案,标注"⚠️"避免重复`;
|
|
279
|
+
}
|
|
117
280
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import { getLogger } from '@
|
|
2
|
+
import { getLogger } from '@myassis/shared';
|
|
3
3
|
import { SessionStore } from './SessionStore';
|
|
4
4
|
import { AgentStore } from '@/services/agent/AgentStore';
|
|
5
|
+
import { authStore } from '@/stores/authStore';
|
|
5
6
|
import { toModel } from '@/services/models';
|
|
6
7
|
import { executeTool, getToolDefinitions } from '@/services/tools';
|
|
7
8
|
import { getSystemPromptAsync } from '../systemPrompt';
|
|
8
9
|
import { LLMClient, parseAruments } from '../llm/LLMClient';
|
|
9
10
|
import { modelsService, settingsService } from '@/services/dataService';
|
|
10
11
|
import { MemoryManager } from '../memory/MemoryManager';
|
|
11
|
-
import { sessionManager } from './SessionManager';
|
|
12
12
|
import { appConfig } from '@/config';
|
|
13
|
+
import { getSessionManager } from './SessionManager';
|
|
13
14
|
const logger = getLogger('Session');
|
|
14
15
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
15
16
|
// AgentStore 单例 - 需要通过 SessionStore 获取数据库实例
|
|
@@ -46,7 +47,6 @@ export class Session {
|
|
|
46
47
|
unreadCount = 0;
|
|
47
48
|
currentMessageId = null;
|
|
48
49
|
abortController = null;
|
|
49
|
-
toolCalls;
|
|
50
50
|
toInsertMessages;
|
|
51
51
|
constructor(data) {
|
|
52
52
|
this.id = data.id;
|
|
@@ -103,16 +103,13 @@ export class Session {
|
|
|
103
103
|
return this.messages.reduce((total, msg) => total + msg.content.length, 0);
|
|
104
104
|
}
|
|
105
105
|
// ========== Message Operations ==========
|
|
106
|
-
|
|
107
|
-
* Add user message
|
|
108
|
-
*/
|
|
109
|
-
addUserMessage(content, userMessageId, attachments) {
|
|
106
|
+
getUserMessage(content, userMessageId, attachments) {
|
|
110
107
|
// 将附件统一为 Attachment 对象格式
|
|
111
108
|
const normalizedAttachments = (attachments || []).map((att) => {
|
|
112
109
|
if (typeof att === 'string') {
|
|
113
110
|
// URL 字符串格式,转换为对象
|
|
114
111
|
const url = att;
|
|
115
|
-
const name = url.split('/').pop() || 'attachment';
|
|
112
|
+
const name = url.split('/').pop().split('?')[0] || 'attachment';
|
|
116
113
|
const ext = name.split('.').pop()?.toLowerCase() || '';
|
|
117
114
|
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
|
|
118
115
|
const videoExts = ['mp4', 'avi', 'mov', 'mkv', 'webm'];
|
|
@@ -145,6 +142,13 @@ export class Session {
|
|
|
145
142
|
createdAt: Date.now(),
|
|
146
143
|
modelName: ''
|
|
147
144
|
};
|
|
145
|
+
return message;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Add user message
|
|
149
|
+
*/
|
|
150
|
+
addUserMessage(content, userMessageId, attachments) {
|
|
151
|
+
const message = this.getUserMessage(content, userMessageId, attachments);
|
|
148
152
|
this.messages.push(message);
|
|
149
153
|
this.toInsertMessages.push(message);
|
|
150
154
|
return message;
|
|
@@ -375,15 +379,27 @@ export class Session {
|
|
|
375
379
|
* Stream chat response (SSE) - 使用 ModelSelector 根据选择的模型调用
|
|
376
380
|
* 支持工具调用流程
|
|
377
381
|
*/
|
|
378
|
-
async streamChat(content, attachments = [], res, userMessageId, assistantMessageId) {
|
|
382
|
+
async streamChat(content, attachments = [], res, userMessageId, assistantMessageId, childAgent = false) {
|
|
383
|
+
// 如果正在生成,等待当前生成完成(最多等待 2 秒)
|
|
384
|
+
if (this.isGenerating) {
|
|
385
|
+
const maxWait = 2000;
|
|
386
|
+
const startTime = Date.now();
|
|
387
|
+
while (this.isGenerating && Date.now() - startTime < maxWait) {
|
|
388
|
+
await sleep(100);
|
|
389
|
+
}
|
|
390
|
+
// 超时后强制中断
|
|
391
|
+
if (this.isGenerating) {
|
|
392
|
+
this.stopGenerating();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
379
395
|
// Add user message
|
|
380
396
|
this.getAbortController();
|
|
381
397
|
this.isGenerating = true;
|
|
382
|
-
|
|
383
|
-
const settings = await settingsService.get();
|
|
398
|
+
const token = authStore.get(this.userId).accessToken;
|
|
399
|
+
const settings = await settingsService.get(token);
|
|
384
400
|
const streamDelay = this.getStreamDelay(settings.streamSpeed);
|
|
385
401
|
this.currentMessageId = assistantMessageId;
|
|
386
|
-
const memoryManager = new MemoryManager(this, this.abortController.signal);
|
|
402
|
+
const memoryManager = new MemoryManager(this, this.abortController.signal, childAgent);
|
|
387
403
|
const historyMessages = await memoryManager.getHistoryMessagesAsync();
|
|
388
404
|
// SSE 辅助方法:res 为 null 时跳过写入(本地执行模式)
|
|
389
405
|
const sendSSE = (res, data) => {
|
|
@@ -433,8 +449,13 @@ export class Session {
|
|
|
433
449
|
messages.unshift({ role: 'system', content: systemPrompt, attachments: [] });
|
|
434
450
|
}
|
|
435
451
|
//插入用户消息
|
|
436
|
-
|
|
437
|
-
|
|
452
|
+
if (!childAgent) {
|
|
453
|
+
this.addUserMessage(content, userMessageId, attachments);
|
|
454
|
+
messages.push(this.messages.at(-1));
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
messages.push(this.getUserMessage(content, userMessageId, attachments));
|
|
458
|
+
}
|
|
438
459
|
let toolCall = null;
|
|
439
460
|
let modelNames = [];
|
|
440
461
|
//用于后续的消息滑动处理
|
|
@@ -450,6 +471,7 @@ export class Session {
|
|
|
450
471
|
message.reasoning_content = message.reasoning_content.substring(0, 100) + '...(内容过长已截断)';
|
|
451
472
|
}
|
|
452
473
|
};
|
|
474
|
+
const toolCalls = [];
|
|
453
475
|
// 递归处理函数(支持多轮工具调用)
|
|
454
476
|
const processModelResponse = async () => {
|
|
455
477
|
if (!this.abortController || !this.abortController.signal || this.abortController.signal.aborted) {
|
|
@@ -460,7 +482,7 @@ export class Session {
|
|
|
460
482
|
// 清理旧工具调用:只保留最近 keep 轮的完整上下文
|
|
461
483
|
{
|
|
462
484
|
const MAX_TOOL_PAIRS = keep;
|
|
463
|
-
const allToolCallItems =
|
|
485
|
+
const allToolCallItems = toolCalls.flatMap(x => x.toolCalls);
|
|
464
486
|
// 1) 清理 this.toolCalls 中超出 keep 的 output(供前端展示用)
|
|
465
487
|
allToolCallItems.forEach((item, index) => {
|
|
466
488
|
if (index < allToolCallItems.length - MAX_TOOL_PAIRS) {
|
|
@@ -498,7 +520,7 @@ export class Session {
|
|
|
498
520
|
}
|
|
499
521
|
// 获取本地工具定义
|
|
500
522
|
const tools = getToolDefinitions();
|
|
501
|
-
const models = (await modelsService.list()).data.map(x => toModel(x));
|
|
523
|
+
const models = (await modelsService.list(token)).data.map(x => toModel(x));
|
|
502
524
|
// 使用 LLMClient 进行流式调用
|
|
503
525
|
if (!this.abortController || !this.abortController.signal || this.abortController.signal.aborted) {
|
|
504
526
|
throw Error('aborted');
|
|
@@ -568,7 +590,7 @@ export class Session {
|
|
|
568
590
|
}
|
|
569
591
|
// 执行工具调用并收集结果
|
|
570
592
|
const toolResults = [];
|
|
571
|
-
|
|
593
|
+
const startToolCall = async (pendingToolCall) => {
|
|
572
594
|
// 发送工具开始执行事件
|
|
573
595
|
const toolStartEvent = {
|
|
574
596
|
type: 'tool_call_execute',
|
|
@@ -581,7 +603,7 @@ export class Session {
|
|
|
581
603
|
};
|
|
582
604
|
sendSSE(res, toolStartEvent);
|
|
583
605
|
try {
|
|
584
|
-
const result = await executeTool(pendingToolCall.toolName, parseAruments(pendingToolCall.input), this.id, this.currentMessageId);
|
|
606
|
+
const result = await executeTool(pendingToolCall.toolName, parseAruments(pendingToolCall.input), this.id, this.currentMessageId, this.userId);
|
|
585
607
|
toolResults.push({
|
|
586
608
|
id: pendingToolCall.id,
|
|
587
609
|
name: pendingToolCall.toolName,
|
|
@@ -638,8 +660,9 @@ export class Session {
|
|
|
638
660
|
};
|
|
639
661
|
sendSSE(res, errorEvent);
|
|
640
662
|
}
|
|
641
|
-
}
|
|
642
|
-
|
|
663
|
+
};
|
|
664
|
+
await Promise.all(llmResult.toolCalls.map(x => startToolCall(x)));
|
|
665
|
+
toolCalls.push(toolCall);
|
|
643
666
|
// 将工具结果添加到消息历史(格式化为 tool 角色的消息)
|
|
644
667
|
for (const result of toolResults) {
|
|
645
668
|
const toolResultMessage = {
|
|
@@ -655,7 +678,7 @@ export class Session {
|
|
|
655
678
|
messages.push(toolResultMessage);
|
|
656
679
|
}
|
|
657
680
|
// 继续递归处理(带上工具结果继续调用模型)
|
|
658
|
-
await processModelResponse();
|
|
681
|
+
return await processModelResponse();
|
|
659
682
|
}
|
|
660
683
|
else {
|
|
661
684
|
// ========== 没有工具调用,保存消息并结束 ==========
|
|
@@ -672,34 +695,43 @@ export class Session {
|
|
|
672
695
|
await sleep(streamDelay);
|
|
673
696
|
}
|
|
674
697
|
}
|
|
675
|
-
|
|
698
|
+
if (!childAgent) {
|
|
699
|
+
this.addAssistantMessage(llmResult.content || llmResult.reasoningContent, toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
|
|
700
|
+
}
|
|
676
701
|
sendSSE(res, { type: 'complete', modelName: [...new Set(modelNames)].join(",") });
|
|
677
702
|
sendSSE(res, { type: '[DONE]' });
|
|
703
|
+
return llmResult.content;
|
|
678
704
|
}
|
|
679
705
|
};
|
|
680
706
|
try {
|
|
681
|
-
await processModelResponse();
|
|
707
|
+
const result = await processModelResponse();
|
|
708
|
+
return { success: true, data: result };
|
|
682
709
|
}
|
|
683
710
|
catch (error) {
|
|
684
711
|
logger.error('stream chat ', error);
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
712
|
+
if (!childAgent) {
|
|
713
|
+
if (toolCalls) {
|
|
714
|
+
this.addAssistantMessage('', toolCalls, [...new Set(modelNames)].join(','), this.currentMessageId);
|
|
715
|
+
}
|
|
716
|
+
if (error.message !== 'aborted') {
|
|
717
|
+
sendSSE(res, { type: 'error', error: error.message });
|
|
718
|
+
}
|
|
690
719
|
}
|
|
720
|
+
return { success: false, error: error.message };
|
|
691
721
|
}
|
|
692
722
|
finally {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
res
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
this.
|
|
723
|
+
if (!childAgent) {
|
|
724
|
+
this.isGenerating = false;
|
|
725
|
+
this.abortController = null;
|
|
726
|
+
if (res) {
|
|
727
|
+
res.end();
|
|
728
|
+
}
|
|
729
|
+
// 非当前查看的 session,助手回复完成后增加未读计数
|
|
730
|
+
getSessionManager(this.userId).onMessageComplete(this.id);
|
|
731
|
+
// 只在非 abort 的情况下保存(abort 由 stopGenerating 负责保存)
|
|
732
|
+
if (this.currentMessageId !== null) {
|
|
733
|
+
this.saveMessage();
|
|
734
|
+
}
|
|
703
735
|
}
|
|
704
736
|
}
|
|
705
737
|
}
|
|
@@ -712,14 +744,6 @@ export class Session {
|
|
|
712
744
|
this.abortController.abort();
|
|
713
745
|
this.abortController = null;
|
|
714
746
|
}
|
|
715
|
-
// stopGenerating 负责保存中断的消息,streamChat 的 finally 不再重复保存
|
|
716
|
-
if (this.currentMessageId) {
|
|
717
|
-
if (this.toolCalls && this.toolCalls.length > 0) {
|
|
718
|
-
this.addAssistantMessage('', this.toolCalls, this.currentMessageId);
|
|
719
|
-
}
|
|
720
|
-
this.saveMessage();
|
|
721
|
-
this.currentMessageId = null;
|
|
722
|
-
}
|
|
723
747
|
}
|
|
724
748
|
/**
|
|
725
749
|
* 获取 AbortController 用于中断请求
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
-
import { getLogger } from '@
|
|
2
|
+
import { getLogger } from '@myassis/shared';
|
|
3
3
|
const logger = getLogger('SessionManager');
|
|
4
4
|
import { sessionStore } from './SessionStore';
|
|
5
5
|
import { authStore } from '@/stores/authStore';
|
|
@@ -11,7 +11,9 @@ import { Session } from './Session';
|
|
|
11
11
|
export class SessionManager {
|
|
12
12
|
sessions = null;
|
|
13
13
|
initialized = false;
|
|
14
|
-
|
|
14
|
+
userId;
|
|
15
|
+
constructor(userId) {
|
|
16
|
+
this.userId = userId;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Initialize - Load all sessions from database on startup
|
|
@@ -40,7 +42,7 @@ export class SessionManager {
|
|
|
40
42
|
* Get current user ID
|
|
41
43
|
*/
|
|
42
44
|
getCurrentUserId() {
|
|
43
|
-
const user = authStore.getUser();
|
|
45
|
+
const user = authStore.getUser(this.userId);
|
|
44
46
|
return user ? String(user.id) : null;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -251,5 +253,10 @@ export class SessionManager {
|
|
|
251
253
|
return sessionStore.getMigrationStatus();
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
|
-
|
|
255
|
-
export
|
|
256
|
+
export let sessionManagerMap = new Map();
|
|
257
|
+
export function getSessionManager(userId) {
|
|
258
|
+
if (!sessionManagerMap.has(userId)) {
|
|
259
|
+
sessionManagerMap.set(userId, new SessionManager(userId));
|
|
260
|
+
}
|
|
261
|
+
return sessionManagerMap.get(userId);
|
|
262
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import { getLogger } from '@
|
|
4
|
+
import { getLogger } from '@myassis/shared';
|
|
5
5
|
import { MigrationManager } from './MigrationManager';
|
|
6
6
|
const logger = getLogger('SessionStore');
|
|
7
7
|
/**
|
|
@@ -22,36 +22,10 @@ export class SessionStore {
|
|
|
22
22
|
const migrationsDir = path.join(process.cwd(), 'migrations');
|
|
23
23
|
this.migrationManager = new MigrationManager(this.db, migrationsDir);
|
|
24
24
|
this.migrationManager.migrate();
|
|
25
|
-
this.ensureColumns();
|
|
26
25
|
logger.info(`Initialized at ${dbPath}`);
|
|
27
26
|
// 注册到全局供 Session 使用
|
|
28
27
|
global.__sessionStore = this;
|
|
29
28
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Ensure required columns exist
|
|
32
|
-
*/
|
|
33
|
-
ensureColumns() {
|
|
34
|
-
// Ensure agent_id column in sessions
|
|
35
|
-
const sessionTableInfo = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
36
|
-
const hasAgentId = sessionTableInfo.some(col => col.name === 'agent_id');
|
|
37
|
-
if (!hasAgentId) {
|
|
38
|
-
this.db.exec("ALTER TABLE sessions ADD COLUMN agent_id TEXT");
|
|
39
|
-
logger.info('Added agent_id column to sessions');
|
|
40
|
-
}
|
|
41
|
-
// Ensure tool_calls column in messages
|
|
42
|
-
const msgTableInfo = this.db.prepare("PRAGMA table_info(messages)").all();
|
|
43
|
-
const hasToolCalls = msgTableInfo.some(col => col.name === 'tool_calls');
|
|
44
|
-
if (!hasToolCalls) {
|
|
45
|
-
this.db.exec("ALTER TABLE messages ADD COLUMN tool_calls TEXT");
|
|
46
|
-
logger.info('Added tool_calls column to messages');
|
|
47
|
-
}
|
|
48
|
-
// Ensure feedback column in messages
|
|
49
|
-
const hasFeedback = msgTableInfo.some(col => col.name === 'feedback');
|
|
50
|
-
if (!hasFeedback) {
|
|
51
|
-
this.db.exec("ALTER TABLE messages ADD COLUMN feedback TEXT");
|
|
52
|
-
logger.info('Added feedback column to messages');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
29
|
/**
|
|
56
30
|
* Provide transaction method
|
|
57
31
|
*/
|