@myassis/gateway 1.0.56 → 1.0.58
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 +14 -1
- package/dist/main.js +5 -1
- package/dist/routes/agent.js +12 -4
- package/dist/routes/models.js +14 -0
- package/dist/routes/quota.js +104 -0
- package/dist/services/agent/Agent.js +1 -0
- package/dist/services/dataService.js +2 -0
- package/dist/services/llm/LLMClient.js +217 -147
- package/dist/services/models.js +20 -3
- package/dist/services/session/Session.js +36 -1
- package/dist/services/session/SessionManager.js +3 -0
- package/package.json +1 -1
package/dist/api/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
14
|
};
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.settingsApi = exports.tasksApi = exports.modelsApi = exports.skillHubApi = exports.skillsApi = exports.authApi = exports.ApiError = exports.runWithToken = void 0;
|
|
16
|
+
exports.settingsApi = exports.tasksApi = exports.modelsApi = exports.skillHubApi = exports.skillsApi = exports.authApi = exports.ApiError = exports.getServerBaseUrl = exports.getRequestToken = exports.runWithToken = void 0;
|
|
17
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
18
|
const async_hooks_1 = require("async_hooks");
|
|
19
19
|
// 多用户模式请求上下文:存储当前请求的 token
|
|
@@ -22,6 +22,16 @@ function runWithToken(token, fn) {
|
|
|
22
22
|
return requestContext.run({ token }, fn);
|
|
23
23
|
}
|
|
24
24
|
exports.runWithToken = runWithToken;
|
|
25
|
+
// 获取当前请求的 token(用于 LLMClient 系统模式调用 Server)
|
|
26
|
+
function getRequestToken() {
|
|
27
|
+
return requestContext.getStore()?.token;
|
|
28
|
+
}
|
|
29
|
+
exports.getRequestToken = getRequestToken;
|
|
30
|
+
// 获取 Server 基础 URL
|
|
31
|
+
function getServerBaseUrl() {
|
|
32
|
+
return SERVER_BASE_URL;
|
|
33
|
+
}
|
|
34
|
+
exports.getServerBaseUrl = getServerBaseUrl;
|
|
25
35
|
// Server 服务地址(从环境变量读取)
|
|
26
36
|
const SERVER_BASE_URL = process.env.SERVER_BASE_URL || 'http://localhost:3000';
|
|
27
37
|
// 签名密钥(用于请求签名)
|
|
@@ -107,6 +117,7 @@ function createRequest(prefix) {
|
|
|
107
117
|
const _authRequest = createRequest('/api/v1/auth');
|
|
108
118
|
const _dataRequest = createRequest('/api/v1/data');
|
|
109
119
|
const _skillHubRequest = createRequest('/api/v1/skill-hubs');
|
|
120
|
+
const _llmRequest = createRequest('/api/v1/llm');
|
|
110
121
|
function authRequest(path, options) {
|
|
111
122
|
return _authRequest(path, options || {});
|
|
112
123
|
}
|
|
@@ -171,6 +182,8 @@ exports.modelsApi = {
|
|
|
171
182
|
update: (modelId, data, token) => dataRequest(`/models/${modelId}`, { method: 'PUT', body: data, token }),
|
|
172
183
|
delete: (modelId, token) => dataRequest(`/models/${modelId}`, { method: 'DELETE', token }),
|
|
173
184
|
setPrimary: (modelId, token) => dataRequest(`/models/${modelId}/primary`, { method: 'POST', body: {}, token }),
|
|
185
|
+
/** 获取系统模型列表(服务端配置的外部模型) */
|
|
186
|
+
listSystem: () => _llmRequest('/models'),
|
|
174
187
|
};
|
|
175
188
|
// ============ 任务 API (data) ============
|
|
176
189
|
exports.tasksApi = {
|
package/dist/main.js
CHANGED
|
@@ -23,6 +23,7 @@ const service_js_1 = __importDefault(require("./routes/service.js"));
|
|
|
23
23
|
const tasks_js_1 = __importDefault(require("./routes/tasks.js"));
|
|
24
24
|
const upload_js_1 = __importDefault(require("./routes/upload.js"));
|
|
25
25
|
const version_js_1 = __importDefault(require("./routes/version.js"));
|
|
26
|
+
const quota_js_1 = __importDefault(require("./routes/quota.js"));
|
|
26
27
|
const errorHandler_js_1 = require("./middleware/errorHandler.js");
|
|
27
28
|
const index_js_2 = require("./stores/index.js");
|
|
28
29
|
const persistStore_js_1 = require("./stores/persistStore.js");
|
|
@@ -38,7 +39,9 @@ const cliCommand = process.argv[2];
|
|
|
38
39
|
// PowerShell 立即退出后这些句柄失效,任何 console.log 都会触发 EPIPE/EBADF,
|
|
39
40
|
// Node 若无捕获会直接崩溃 → 表现为「gateway 运行一段时间就停止了」。
|
|
40
41
|
// 因此在业务代码启动前把 stdout/stderr 重定向到日志文件,并挂全局兜底。
|
|
41
|
-
|
|
42
|
+
// 仅生产环境重定向;开发环境保留原始 stdout,方便调试。
|
|
43
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
44
|
+
if (!cliCommand && process.platform === 'win32' && isProduction) {
|
|
42
45
|
try {
|
|
43
46
|
// 使用 require 而非 import,避免打包器把 fs/path 提前解析导致副作用
|
|
44
47
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -262,6 +265,7 @@ else {
|
|
|
262
265
|
app.use('/api/v1/tasks', tasks_js_1.default);
|
|
263
266
|
app.use('/api/v1/upload', upload_js_1.default);
|
|
264
267
|
app.use('/api/v1/version', version_js_1.default);
|
|
268
|
+
app.use('/api/v1/quota', quota_js_1.default);
|
|
265
269
|
// Load auth from persistent storage on startup
|
|
266
270
|
index_js_2.authStore.load();
|
|
267
271
|
// Error handler
|
package/dist/routes/agent.js
CHANGED
|
@@ -252,10 +252,11 @@ router.post('/:id/sessions', ensureAgentManager, async (req, res) => {
|
|
|
252
252
|
if (!agent) {
|
|
253
253
|
return res.status(404).json({ success: false, error: 'Agent not found' });
|
|
254
254
|
}
|
|
255
|
-
const { title, selectModelId } = req.body;
|
|
255
|
+
const { title, selectModelId, useSystemMode } = req.body;
|
|
256
256
|
const session = agent.createSession({
|
|
257
257
|
title: title || '新会话',
|
|
258
258
|
selectModelId,
|
|
259
|
+
useSystemMode,
|
|
259
260
|
});
|
|
260
261
|
res.json({
|
|
261
262
|
success: true,
|
|
@@ -264,6 +265,7 @@ router.post('/:id/sessions', ensureAgentManager, async (req, res) => {
|
|
|
264
265
|
agentId: session.agentId,
|
|
265
266
|
title: session.title,
|
|
266
267
|
selectModelId: session.selectModelId,
|
|
268
|
+
useSystemMode: session.useSystemMode,
|
|
267
269
|
messageQueue: session.messageQueue,
|
|
268
270
|
messageQueueAutoExecute: session.messageQueueAutoExecute,
|
|
269
271
|
createdAt: session.createdAt,
|
|
@@ -288,8 +290,8 @@ router.put('/:id/sessions/:sessionId', ensureAgentManager, async (req, res) => {
|
|
|
288
290
|
if (!agent) {
|
|
289
291
|
return res.status(404).json({ success: false, error: 'Agent not found' });
|
|
290
292
|
}
|
|
291
|
-
const { title, selectModelId, unreadCount, messageQueue, messageQueueAutoExecute } = req.body;
|
|
292
|
-
const session = agent.updateSession(req.params.sessionId, { title, selectModelId, unreadCount, messageQueue, messageQueueAutoExecute });
|
|
293
|
+
const { title, selectModelId, unreadCount, messageQueue, messageQueueAutoExecute, useSystemMode } = req.body;
|
|
294
|
+
const session = agent.updateSession(req.params.sessionId, { title, selectModelId, unreadCount, messageQueue, messageQueueAutoExecute, useSystemMode });
|
|
293
295
|
res.json({
|
|
294
296
|
success: true,
|
|
295
297
|
data: {
|
|
@@ -297,6 +299,7 @@ router.put('/:id/sessions/:sessionId', ensureAgentManager, async (req, res) => {
|
|
|
297
299
|
agentId: session.agentId,
|
|
298
300
|
title: session.title,
|
|
299
301
|
selectModelId: session.selectModelId,
|
|
302
|
+
useSystemMode: session.useSystemMode,
|
|
300
303
|
messageQueue: session.messageQueue,
|
|
301
304
|
messageQueueAutoExecute: session.messageQueueAutoExecute,
|
|
302
305
|
createdAt: session.createdAt,
|
|
@@ -437,13 +440,18 @@ router.post('/sessions/:sessionId/stream', ensureAgentManager, async (req, res)
|
|
|
437
440
|
try {
|
|
438
441
|
const userId = req.userId;
|
|
439
442
|
const { sessionId } = req.params;
|
|
440
|
-
const { content, attachments, userMessageId, assistantMessageId } = req.body;
|
|
443
|
+
const { content, attachments, userMessageId, assistantMessageId, useSystemMode, selectModelId } = req.body;
|
|
441
444
|
if (!content && attachments.length === 0) {
|
|
442
445
|
return res.status(400).json({ success: false, error: 'Content or attachments are required' });
|
|
443
446
|
}
|
|
444
447
|
// Find session directly
|
|
445
448
|
const session = (0, index_js_2.getSessionManager)(userId).getSession(sessionId);
|
|
446
449
|
if (session) {
|
|
450
|
+
// 同步前端本次请求携带的模式设置,确保切换后立即生效(避免使用 session 中的旧值)
|
|
451
|
+
if (useSystemMode !== undefined)
|
|
452
|
+
session.useSystemMode = useSystemMode;
|
|
453
|
+
if (selectModelId !== undefined)
|
|
454
|
+
session.selectModelId = selectModelId;
|
|
447
455
|
// Set SSE headers
|
|
448
456
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
449
457
|
res.setHeader('Cache-Control', 'no-cache');
|
package/dist/routes/models.js
CHANGED
|
@@ -28,6 +28,20 @@ router.get('/', async (req, res) => {
|
|
|
28
28
|
res.status(500).json({ success: false, error: '获取模型列表失败' });
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
+
/**
|
|
32
|
+
* 获取系统模式可用模型
|
|
33
|
+
* GET /api/v1/models/system
|
|
34
|
+
*/
|
|
35
|
+
router.get('/system', async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const result = await index_js_1.modelsService.listSystem();
|
|
38
|
+
res.json(result);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger.error('获取系统模型失败:', error?.message);
|
|
42
|
+
res.status(500).json({ success: false, error: '获取系统模型失败' });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
31
45
|
/**
|
|
32
46
|
* 获取单个模型配置
|
|
33
47
|
* GET /api/v1/models/:id
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const shared_1 = require("@myassis/shared");
|
|
8
|
+
const auth_js_1 = require("../middleware/auth.js");
|
|
9
|
+
const index_js_1 = require("../api/index.js");
|
|
10
|
+
const logger = (0, shared_1.getLogger)('quota');
|
|
11
|
+
const router = express_1.default.Router();
|
|
12
|
+
// 所有路由都需要认证
|
|
13
|
+
router.use(auth_js_1.requireAuth);
|
|
14
|
+
/**
|
|
15
|
+
* 查询配额
|
|
16
|
+
* GET /api/v1/quota
|
|
17
|
+
* 转发到 Server GET /api/v1/llm/quota
|
|
18
|
+
*/
|
|
19
|
+
router.get('/', async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const serverUrl = (0, index_js_1.getServerBaseUrl)();
|
|
22
|
+
const token = req.token;
|
|
23
|
+
if (!token) {
|
|
24
|
+
return res.status(401).json({ success: false, error: '未登录' });
|
|
25
|
+
}
|
|
26
|
+
const response = await fetch(`${serverUrl}/api/v1/llm/quota`, {
|
|
27
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const errorText = await response.text();
|
|
31
|
+
logger.error('查询配额失败:', response.status, errorText);
|
|
32
|
+
return res.status(response.status).json({ success: false, error: `查询配额失败: ${response.status}` });
|
|
33
|
+
}
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
res.json(data);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
logger.error('查询配额异常:', error?.message);
|
|
39
|
+
res.status(500).json({ success: false, error: '查询配额失败' });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* 购买 Token(桌面端测试模式)
|
|
44
|
+
* POST /api/v1/quota/purchase
|
|
45
|
+
* 转发到 Server POST /api/v1/llm/purchase
|
|
46
|
+
*/
|
|
47
|
+
router.post('/purchase', async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const serverUrl = (0, index_js_1.getServerBaseUrl)();
|
|
50
|
+
const token = req.token;
|
|
51
|
+
if (!token) {
|
|
52
|
+
return res.status(401).json({ success: false, error: '未登录' });
|
|
53
|
+
}
|
|
54
|
+
const { productId } = req.body;
|
|
55
|
+
const response = await fetch(`${serverUrl}/api/v1/payments/iap/desktop-purchase`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Authorization': `Bearer ${token}`,
|
|
59
|
+
'Content-Type': 'application/json',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({ productId }),
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const errorText = await response.text();
|
|
65
|
+
logger.error('购买失败:', response.status, errorText);
|
|
66
|
+
return res.status(response.status).json({ success: false, error: `购买失败: ${response.status}` });
|
|
67
|
+
}
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
res.json(data);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
logger.error('购买异常:', error?.message);
|
|
73
|
+
res.status(500).json({ success: false, error: '购买失败' });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* 获取可购买的产品列表
|
|
78
|
+
* GET /api/v1/quota/products
|
|
79
|
+
* 转发到 Server GET /api/v1/payments/iap/products
|
|
80
|
+
*/
|
|
81
|
+
router.get('/products', async (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const serverUrl = (0, index_js_1.getServerBaseUrl)();
|
|
84
|
+
const token = req.token;
|
|
85
|
+
if (!token) {
|
|
86
|
+
return res.status(401).json({ success: false, error: '未登录' });
|
|
87
|
+
}
|
|
88
|
+
const response = await fetch(`${serverUrl}/api/v1/payments/iap/products`, {
|
|
89
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const errorText = await response.text();
|
|
93
|
+
logger.error('获取产品列表失败:', response.status, errorText);
|
|
94
|
+
return res.status(response.status).json({ success: false, error: `获取产品列表失败: ${response.status}` });
|
|
95
|
+
}
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
res.json(data);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.error('获取产品列表异常:', error?.message);
|
|
101
|
+
res.status(500).json({ success: false, error: '获取产品列表失败' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
exports.default = router;
|
|
@@ -166,6 +166,8 @@ exports.modelsService = {
|
|
|
166
166
|
update: (modelId, data, token) => index_js_1.modelsApi.update(modelId, data, token),
|
|
167
167
|
delete: (modelId, token) => index_js_1.modelsApi.delete(modelId, token),
|
|
168
168
|
setPrimary: (modelId, token) => index_js_1.modelsApi.setPrimary(modelId, token),
|
|
169
|
+
/** 获取服务端配置的外部模型列表 */
|
|
170
|
+
listSystem: () => index_js_1.modelsApi.listSystem(),
|
|
169
171
|
};
|
|
170
172
|
exports.tasksService = {
|
|
171
173
|
list: async (userId, params) => {
|
|
@@ -26,6 +26,11 @@ class LLMClient {
|
|
|
26
26
|
preferredModelId;
|
|
27
27
|
signal;
|
|
28
28
|
timeoutMs = 120000;
|
|
29
|
+
// 系统模式:转发到 Server LLM 代理
|
|
30
|
+
systemMode = false;
|
|
31
|
+
systemModelId;
|
|
32
|
+
serverUrl;
|
|
33
|
+
authToken;
|
|
29
34
|
constructor(models, messages, signal, tools, timeoutMs) {
|
|
30
35
|
this.models = models;
|
|
31
36
|
this.messages = messages;
|
|
@@ -45,6 +50,16 @@ class LLMClient {
|
|
|
45
50
|
setPreferredModel(modelId) {
|
|
46
51
|
this.preferredModelId = modelId;
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 设置系统模式
|
|
55
|
+
* 启用后,LLM 请求将转发到 Server 的 LLM 代理端点
|
|
56
|
+
*/
|
|
57
|
+
setSystemMode(enabled, modelId, serverUrl, authToken) {
|
|
58
|
+
this.systemMode = enabled;
|
|
59
|
+
this.systemModelId = modelId;
|
|
60
|
+
this.serverUrl = serverUrl;
|
|
61
|
+
this.authToken = authToken;
|
|
62
|
+
}
|
|
48
63
|
/**
|
|
49
64
|
* 非流式 Chat 调用
|
|
50
65
|
* 返回完整的 content、reasoningContent 和 toolCalls
|
|
@@ -63,7 +78,7 @@ class LLMClient {
|
|
|
63
78
|
const controller = new AbortController();
|
|
64
79
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
65
80
|
try {
|
|
66
|
-
const request = await this.buildRequest(
|
|
81
|
+
const request = await this.buildRequest(false, model);
|
|
67
82
|
// 组合外部 signal 和内部 controller.signal,任一 abort 都取消请求
|
|
68
83
|
const combinedSignal = this.signal
|
|
69
84
|
? (this.signal.aborted ? this.signal : AbortSignal.any([this.signal, controller.signal]))
|
|
@@ -85,6 +100,7 @@ class LLMClient {
|
|
|
85
100
|
return this.parseResponse(data, model.modelName);
|
|
86
101
|
}
|
|
87
102
|
catch (error) {
|
|
103
|
+
logger.error(error);
|
|
88
104
|
if (error.message === 'exceed max message tokens') {
|
|
89
105
|
throw error;
|
|
90
106
|
}
|
|
@@ -115,7 +131,10 @@ class LLMClient {
|
|
|
115
131
|
if (!model)
|
|
116
132
|
break;
|
|
117
133
|
try {
|
|
118
|
-
await this.streamRequest(model, callbacks);
|
|
134
|
+
const result = await this.streamRequest(model, callbacks);
|
|
135
|
+
if (result?.modelName) {
|
|
136
|
+
callbacks.onModelName?.(result.modelName);
|
|
137
|
+
}
|
|
119
138
|
return;
|
|
120
139
|
}
|
|
121
140
|
catch (error) {
|
|
@@ -133,183 +152,206 @@ class LLMClient {
|
|
|
133
152
|
return selector;
|
|
134
153
|
}
|
|
135
154
|
/**
|
|
136
|
-
*
|
|
155
|
+
* 统一的消息映射函数
|
|
156
|
+
* systemMode 和非 systemMode 共用,保证消息处理逻辑一致
|
|
137
157
|
*/
|
|
138
|
-
|
|
139
|
-
const apiKey = index_js_1.persistStore.getModelApiKey(model.id);
|
|
140
|
-
if (!apiKey) {
|
|
141
|
-
throw new Error('No API Key');
|
|
142
|
-
}
|
|
143
|
-
// OpenAI compatible API
|
|
158
|
+
mapMessages(messages) {
|
|
144
159
|
let existAttachments = false;
|
|
145
160
|
let existScreenShot = false;
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}]
|
|
169
|
-
}];
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
return [{
|
|
173
|
-
role: 'tool',
|
|
174
|
-
tool_call_id: m.tool_call_id,
|
|
175
|
-
content: m.content.substring(0, 100) + '...(内容过长已截断)',
|
|
176
|
-
}];
|
|
177
|
-
}
|
|
161
|
+
const mapped = messages.map((m, index) => {
|
|
162
|
+
if (m.role === 'tool') {
|
|
163
|
+
if (m.tool_call_name == 'screenshot') {
|
|
164
|
+
if (index === messages.length - 1) {
|
|
165
|
+
existScreenShot = true;
|
|
166
|
+
const contentObj = JSON.parse(m.content);
|
|
167
|
+
return [{
|
|
168
|
+
role: 'tool',
|
|
169
|
+
tool_call_id: m.tool_call_id,
|
|
170
|
+
content: '内容在user里面返回',
|
|
171
|
+
}, {
|
|
172
|
+
role: 'user',
|
|
173
|
+
content: [{
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: '请识别这张屏幕截图,然后继续当前任务'
|
|
176
|
+
}, {
|
|
177
|
+
type: 'image_url',
|
|
178
|
+
image_url: {
|
|
179
|
+
url: `data:image/png;base64,${contentObj.output}`
|
|
180
|
+
}
|
|
181
|
+
}]
|
|
182
|
+
}];
|
|
178
183
|
}
|
|
179
184
|
else {
|
|
180
185
|
return [{
|
|
181
186
|
role: 'tool',
|
|
182
187
|
tool_call_id: m.tool_call_id,
|
|
183
|
-
content: m.content,
|
|
188
|
+
content: m.content.substring(0, 100) + '...(内容过长已截断)',
|
|
184
189
|
}];
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
|
-
|
|
188
|
-
if (m.role === 'assistant' && m.tool_calls) {
|
|
192
|
+
else {
|
|
189
193
|
return [{
|
|
190
|
-
role: '
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
id: m.id,
|
|
195
|
-
function: {
|
|
196
|
-
name: m.toolName,
|
|
197
|
-
arguments: m.input
|
|
198
|
-
},
|
|
199
|
-
type: 'function'
|
|
200
|
-
};
|
|
201
|
-
}),
|
|
194
|
+
role: 'tool',
|
|
195
|
+
tool_call_id: m.tool_call_id,
|
|
196
|
+
content: m.content,
|
|
202
197
|
}];
|
|
203
198
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
199
|
+
}
|
|
200
|
+
// 助手调用工具的消息:必须保留 tool_calls!!
|
|
201
|
+
if (m.role === 'assistant' && m.tool_calls) {
|
|
202
|
+
return [{
|
|
203
|
+
role: 'assistant',
|
|
204
|
+
content: m.content || null,
|
|
205
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
206
|
+
id: tc.id,
|
|
207
|
+
function: {
|
|
208
|
+
name: tc.toolName,
|
|
209
|
+
arguments: tc.input
|
|
210
|
+
},
|
|
211
|
+
type: 'function'
|
|
212
|
+
})),
|
|
213
|
+
}];
|
|
214
|
+
}
|
|
215
|
+
if (m.role === 'assistant' && m.toolCalls) {
|
|
216
|
+
let items = [];
|
|
217
|
+
for (let toolCall of m.toolCalls) {
|
|
218
|
+
const item = {
|
|
219
|
+
role: 'assistant',
|
|
220
|
+
content: toolCall.content,
|
|
221
|
+
reasoning_content: toolCall.reasoningContent,
|
|
222
|
+
tool_calls: []
|
|
223
|
+
};
|
|
224
|
+
const tools = [];
|
|
225
|
+
for (let i = 0; i < toolCall.toolCalls.length; i++) {
|
|
226
|
+
const toolCallItem = toolCall.toolCalls[i];
|
|
227
|
+
if (toolCallItem.output != null) {
|
|
228
|
+
item.tool_calls.push({
|
|
229
|
+
id: toolCallItem.id,
|
|
230
|
+
function: {
|
|
231
|
+
name: toolCallItem.toolName,
|
|
232
|
+
arguments: toolCallItem.input
|
|
233
|
+
},
|
|
234
|
+
type: 'function'
|
|
235
|
+
});
|
|
236
|
+
tools.push({
|
|
237
|
+
role: 'tool',
|
|
238
|
+
tool_call_id: toolCallItem.id,
|
|
239
|
+
content: toolCallItem.output.substring(0, 100) + '...' + '(内容过长已截断)'
|
|
240
|
+
});
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
|
-
if (
|
|
238
|
-
items.push(
|
|
239
|
-
|
|
240
|
-
content: m.content
|
|
241
|
-
});
|
|
243
|
+
if (item.tool_calls.length > 0) {
|
|
244
|
+
items.push(item);
|
|
245
|
+
items = items.concat(tools);
|
|
242
246
|
}
|
|
243
|
-
return items;
|
|
244
247
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
248
|
+
if (m.content) {
|
|
249
|
+
items.push({
|
|
250
|
+
role: 'assistant',
|
|
251
|
+
content: m.content
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return items;
|
|
255
|
+
}
|
|
256
|
+
// 处理用户消息中的附件
|
|
257
|
+
if (m.role === 'user' && m.attachments && m.attachments.length > 0) {
|
|
258
|
+
existAttachments = true;
|
|
259
|
+
const contentArray = [];
|
|
260
|
+
for (const attachment of m.attachments) {
|
|
261
|
+
if (attachment.type === 'image') {
|
|
262
|
+
contentArray.push({
|
|
263
|
+
type: 'image_url',
|
|
264
|
+
image_url: {
|
|
265
|
+
url: attachment.url,
|
|
266
|
+
detail: 'high',
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (attachment.type === 'audio') {
|
|
271
|
+
contentArray.push({
|
|
272
|
+
type: 'input_audio',
|
|
273
|
+
input_audio: {
|
|
274
|
+
data: attachment.url,
|
|
275
|
+
format: attachment.mimeType?.split('/')[1] || 'wav',
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const fileInfo = `[附件: ${attachment.name}](${attachment.url})`;
|
|
281
|
+
if (contentArray.length === 0) {
|
|
263
282
|
contentArray.push({
|
|
264
|
-
type: '
|
|
265
|
-
|
|
266
|
-
data: attachment.url, // 如果是 base64 格式
|
|
267
|
-
format: attachment.mimeType?.split('/')[1] || 'wav',
|
|
268
|
-
},
|
|
283
|
+
type: 'text',
|
|
284
|
+
text: fileInfo,
|
|
269
285
|
});
|
|
270
286
|
}
|
|
271
287
|
else {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
288
|
+
const lastItem = contentArray[contentArray.length - 1];
|
|
289
|
+
if (lastItem.type === 'text') {
|
|
290
|
+
lastItem.text += `\n${fileInfo}`;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
275
293
|
contentArray.push({
|
|
276
294
|
type: 'text',
|
|
277
295
|
text: fileInfo,
|
|
278
296
|
});
|
|
279
297
|
}
|
|
280
|
-
else {
|
|
281
|
-
// 如果已经有文本,将文件信息追加到文本中
|
|
282
|
-
const lastItem = contentArray[contentArray.length - 1];
|
|
283
|
-
if (lastItem.type === 'text') {
|
|
284
|
-
lastItem.text += `\n${fileInfo}`;
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
contentArray.push({
|
|
288
|
-
type: 'text',
|
|
289
|
-
text: fileInfo,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
298
|
}
|
|
294
299
|
}
|
|
295
|
-
// 如果有文本内容,添加文本
|
|
296
|
-
if (m.content && m.content.trim()) {
|
|
297
|
-
contentArray.push({
|
|
298
|
-
type: 'text',
|
|
299
|
-
text: m.content,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
return [{
|
|
303
|
-
role: m.role,
|
|
304
|
-
content: contentArray,
|
|
305
|
-
}];
|
|
306
300
|
}
|
|
307
|
-
|
|
301
|
+
if (m.content && m.content.trim()) {
|
|
302
|
+
contentArray.push({
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: m.content,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
308
307
|
return [{
|
|
309
308
|
role: m.role,
|
|
310
|
-
content:
|
|
309
|
+
content: contentArray,
|
|
311
310
|
}];
|
|
312
|
-
}
|
|
311
|
+
}
|
|
312
|
+
// 普通消息(无附件)
|
|
313
|
+
return [{
|
|
314
|
+
role: m.role,
|
|
315
|
+
content: m.content,
|
|
316
|
+
}];
|
|
317
|
+
}).flatMap(x => x);
|
|
318
|
+
return { mapped, existAttachments, existScreenShot };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* 构建请求
|
|
322
|
+
*/
|
|
323
|
+
async buildRequest(stream, model) {
|
|
324
|
+
// 系统模式:转发到 Server LLM 代理
|
|
325
|
+
if (this.systemMode) {
|
|
326
|
+
const { mapped } = this.mapMessages(this.messages);
|
|
327
|
+
return {
|
|
328
|
+
url: `${this.serverUrl}/api/v1/llm/${stream ? 'chat' : 'invoke'}`,
|
|
329
|
+
headers: {
|
|
330
|
+
'Content-Type': 'application/json',
|
|
331
|
+
'Authorization': `Bearer ${this.authToken}`,
|
|
332
|
+
},
|
|
333
|
+
body: {
|
|
334
|
+
modelId: model.modelId,
|
|
335
|
+
messages: mapped,
|
|
336
|
+
temperature: 0.7,
|
|
337
|
+
tools: this.tools,
|
|
338
|
+
},
|
|
339
|
+
isSystemMode: true,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
// 自定义模式:直接调用 LLM API
|
|
343
|
+
if (!model) {
|
|
344
|
+
throw new Error('No available model');
|
|
345
|
+
}
|
|
346
|
+
const apiKey = index_js_1.persistStore.getModelApiKey(model.id);
|
|
347
|
+
if (!apiKey) {
|
|
348
|
+
throw new Error('No API Key');
|
|
349
|
+
}
|
|
350
|
+
// 使用统一的消息映射
|
|
351
|
+
const { mapped: mappedMessages, existAttachments, existScreenShot } = this.mapMessages(this.messages);
|
|
352
|
+
const body = {
|
|
353
|
+
model: model.modelId,
|
|
354
|
+
messages: mappedMessages,
|
|
313
355
|
stream,
|
|
314
356
|
enable_thinking: true,
|
|
315
357
|
};
|
|
@@ -328,13 +370,14 @@ class LLMClient {
|
|
|
328
370
|
'Authorization': `Bearer ${apiKey}`,
|
|
329
371
|
},
|
|
330
372
|
body,
|
|
373
|
+
isSystemMode: false,
|
|
331
374
|
};
|
|
332
375
|
}
|
|
333
376
|
/**
|
|
334
377
|
* 执行流式请求
|
|
335
378
|
*/
|
|
336
379
|
async streamRequest(model, callbacks) {
|
|
337
|
-
const request = await this.buildRequest(
|
|
380
|
+
const request = await this.buildRequest(true, model);
|
|
338
381
|
const response = await fetch(request.url, {
|
|
339
382
|
method: 'POST',
|
|
340
383
|
headers: request.headers,
|
|
@@ -361,6 +404,13 @@ class LLMClient {
|
|
|
361
404
|
};
|
|
362
405
|
// 解析增量内容
|
|
363
406
|
const parseDelta = (data) => {
|
|
407
|
+
// 系统模式 /api/v1/llm/chat 流式格式
|
|
408
|
+
if (data.content !== undefined && typeof data.content === 'string') {
|
|
409
|
+
return { content: data.content };
|
|
410
|
+
}
|
|
411
|
+
if (data.quota?.modelName) {
|
|
412
|
+
return { modelName: data.quota.modelName };
|
|
413
|
+
}
|
|
364
414
|
// Coze 格式
|
|
365
415
|
if (data.type === 'conversation_message.delta') {
|
|
366
416
|
return { content: data.message?.content?.[0]?.text || '' };
|
|
@@ -425,6 +475,7 @@ class LLMClient {
|
|
|
425
475
|
}
|
|
426
476
|
return {};
|
|
427
477
|
};
|
|
478
|
+
let resolvedModelName;
|
|
428
479
|
try {
|
|
429
480
|
while (true) {
|
|
430
481
|
const { done, value } = await reader.read();
|
|
@@ -440,6 +491,9 @@ class LLMClient {
|
|
|
440
491
|
try {
|
|
441
492
|
const data = JSON.parse(parsed.data);
|
|
442
493
|
const result = parseDelta(data);
|
|
494
|
+
if (result.modelName) {
|
|
495
|
+
resolvedModelName = result.modelName;
|
|
496
|
+
}
|
|
443
497
|
if (result.content) {
|
|
444
498
|
callbacks.onContent?.(result.content);
|
|
445
499
|
}
|
|
@@ -462,11 +516,27 @@ class LLMClient {
|
|
|
462
516
|
finally {
|
|
463
517
|
reader.releaseLock();
|
|
464
518
|
}
|
|
519
|
+
return { modelName: resolvedModelName };
|
|
465
520
|
}
|
|
466
521
|
/**
|
|
467
522
|
* 解析非流式响应
|
|
468
523
|
*/
|
|
469
524
|
parseResponse(data, modelName) {
|
|
525
|
+
// 系统模式 /api/v1/llm/invoke 返回格式
|
|
526
|
+
if (data.success === true && data.data) {
|
|
527
|
+
const d = data.data;
|
|
528
|
+
const toolCalls = d.toolCalls?.map((tc) => ({
|
|
529
|
+
id: tc.id || '',
|
|
530
|
+
toolName: tc.function?.name || '',
|
|
531
|
+
input: tc.function?.arguments || '',
|
|
532
|
+
actionName: this.getActionName(tc.function?.arguments || ''),
|
|
533
|
+
}));
|
|
534
|
+
return {
|
|
535
|
+
content: d.content || '',
|
|
536
|
+
toolCalls: toolCalls?.length ? toolCalls : undefined,
|
|
537
|
+
modelName: data.quota?.modelName || modelName,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
470
540
|
// Coze 格式
|
|
471
541
|
if (data.type === 'conversation_message.completed' || data.message) {
|
|
472
542
|
const messageData = data.message || {};
|
package/dist/services/models.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toModel = void 0;
|
|
3
|
+
exports.toSystemModel = exports.toModel = void 0;
|
|
4
4
|
function toModel(m) {
|
|
5
5
|
return {
|
|
6
6
|
id: String(m.id),
|
|
7
|
-
modelName: m.modelName,
|
|
8
|
-
modelId: m.modelId,
|
|
7
|
+
modelName: m.modelName || m.name,
|
|
8
|
+
modelId: m.modelId || m.id,
|
|
9
9
|
provider: m.provider,
|
|
10
10
|
type: m.type || 'chat',
|
|
11
11
|
description: m.description || '',
|
|
@@ -18,3 +18,20 @@ function toModel(m) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
exports.toModel = toModel;
|
|
21
|
+
function toSystemModel(m) {
|
|
22
|
+
return {
|
|
23
|
+
id: String(m.id),
|
|
24
|
+
modelName: m.modelName || m.name,
|
|
25
|
+
modelId: m.modelId || m.id,
|
|
26
|
+
provider: m.provider,
|
|
27
|
+
type: m.type || 'chat',
|
|
28
|
+
description: m.description || '',
|
|
29
|
+
capabilities: m.capabilities || [],
|
|
30
|
+
maxTokens: m.maxTokens || 0,
|
|
31
|
+
isActive: 1,
|
|
32
|
+
baseUrl: m.baseUrl,
|
|
33
|
+
score: m.score || 0,
|
|
34
|
+
isPrimary: m.isPrimary
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
exports.toSystemModel = toSystemModel;
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.Session = exports.handleApprovalResponse = exports.registerApprovalWaiter = void 0;
|
|
4
27
|
const uuid_1 = require("uuid");
|
|
@@ -72,6 +95,7 @@ class Session {
|
|
|
72
95
|
agentId;
|
|
73
96
|
title;
|
|
74
97
|
selectModelId;
|
|
98
|
+
useSystemMode = false;
|
|
75
99
|
messages;
|
|
76
100
|
messageQueue;
|
|
77
101
|
messageQueueAutoExecute;
|
|
@@ -93,6 +117,7 @@ class Session {
|
|
|
93
117
|
this.agentId = data.agentId;
|
|
94
118
|
this.title = data.title || 'New Chat';
|
|
95
119
|
this.selectModelId = data.selectModelId;
|
|
120
|
+
this.useSystemMode = data.useSystemMode ?? false;
|
|
96
121
|
this.messages = [];
|
|
97
122
|
this.messageQueue = data.messageQueue || [];
|
|
98
123
|
this.messageQueueAutoExecute = data.messageQueueAutoExecute ?? true;
|
|
@@ -396,6 +421,7 @@ class Session {
|
|
|
396
421
|
agentId: this.agentId,
|
|
397
422
|
title: this.title,
|
|
398
423
|
selectModelId: this.selectModelId,
|
|
424
|
+
useSystemMode: this.useSystemMode,
|
|
399
425
|
voiceState: this.voiceState,
|
|
400
426
|
createdAt: this.createdAt,
|
|
401
427
|
updatedAt: this.updatedAt,
|
|
@@ -422,6 +448,7 @@ class Session {
|
|
|
422
448
|
agentId: this.agentId,
|
|
423
449
|
title: this.title,
|
|
424
450
|
selectModelId: this.selectModelId || '',
|
|
451
|
+
useSystemMode: this.useSystemMode,
|
|
425
452
|
voiceState: this.voiceState,
|
|
426
453
|
createdAt: this.createdAt,
|
|
427
454
|
updatedAt: this.updatedAt,
|
|
@@ -450,6 +477,7 @@ class Session {
|
|
|
450
477
|
*/
|
|
451
478
|
async streamChat(content, attachments = [], res, userMessageId, assistantMessageId, childAgent = false) {
|
|
452
479
|
// 如果正在生成,等待当前生成完成(最多等待 2 秒)
|
|
480
|
+
console.log('------------------------------');
|
|
453
481
|
if (this.isGenerating) {
|
|
454
482
|
const maxWait = 2000;
|
|
455
483
|
const startTime = Date.now();
|
|
@@ -557,7 +585,9 @@ class Session {
|
|
|
557
585
|
const toolCalls = [];
|
|
558
586
|
// 获取本地工具定义
|
|
559
587
|
const tools = (0, index_js_1.getToolDefinitions)();
|
|
560
|
-
const models =
|
|
588
|
+
const models = this.useSystemMode
|
|
589
|
+
? (await dataService_js_1.modelsService.listSystem()).data.map(x => (0, models_js_1.toSystemModel)(x))
|
|
590
|
+
: (await dataService_js_1.modelsService.list(token)).data.map(x => (0, models_js_1.toModel)(x));
|
|
561
591
|
// 递归处理函数(支持多轮工具调用)
|
|
562
592
|
const processModelResponse = async () => {
|
|
563
593
|
if (!this.abortController || !this.abortController.signal || this.abortController.signal.aborted) {
|
|
@@ -610,6 +640,11 @@ class Session {
|
|
|
610
640
|
}
|
|
611
641
|
const llmClient = new LLMClient_js_1.LLMClient(models, messages, this.abortController.signal, tools);
|
|
612
642
|
llmClient.setPreferredModel(this.selectModelId);
|
|
643
|
+
// 系统模式:转发到 Server LLM 代理
|
|
644
|
+
if (this.useSystemMode) {
|
|
645
|
+
const { getRequestToken, getServerBaseUrl } = await Promise.resolve().then(() => __importStar(require('../../api/index.js')));
|
|
646
|
+
llmClient.setSystemMode(true, this.selectModelId, getServerBaseUrl(), getRequestToken());
|
|
647
|
+
}
|
|
613
648
|
// 使用 streamChat 调用模型
|
|
614
649
|
const llmResult = await llmClient.Chat();
|
|
615
650
|
// 验证 LLM 结果
|
|
@@ -88,6 +88,7 @@ class SessionManager {
|
|
|
88
88
|
agentId: config.agentId,
|
|
89
89
|
title: config.title || 'New Chat',
|
|
90
90
|
selectModelId: config.selectModelId,
|
|
91
|
+
useSystemMode: config.useSystemMode ?? false,
|
|
91
92
|
voiceState: { isRecording: false, isPlaying: false },
|
|
92
93
|
createdAt: Date.now(),
|
|
93
94
|
updatedAt: Date.now(),
|
|
@@ -177,6 +178,8 @@ class SessionManager {
|
|
|
177
178
|
session.messageQueue = updates.messageQueue;
|
|
178
179
|
if (updates.messageQueueAutoExecute !== undefined)
|
|
179
180
|
session.messageQueueAutoExecute = updates.messageQueueAutoExecute;
|
|
181
|
+
if (updates.useSystemMode !== undefined)
|
|
182
|
+
session.useSystemMode = updates.useSystemMode;
|
|
180
183
|
session.updatedAt = Date.now();
|
|
181
184
|
session.save();
|
|
182
185
|
return session;
|