zen-gitsync 2.17.5 → 2.17.7
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 +35 -2
- package/package.json +1 -1
- package/scripts/release.js +139 -48
- package/src/aiCommit.js +6 -3
- package/src/cli/ai/agent.js +106 -311
- package/src/cli/ai/cli.integration.test.js +99 -0
- package/src/cli/ai/context.js +90 -0
- package/src/cli/ai/modelSetup.js +44 -8
- package/src/cli/ai/runtime.test.js +152 -0
- package/src/cli/ai/safety.js +27 -4
- package/src/cli/ai/safety.test.js +13 -0
- package/src/cli/ai/telemetry.js +43 -0
- package/src/cli/ai/termui.js +154 -41
- package/src/cli/ai/termui.test.js +63 -14
- package/src/cli/ai/tools.js +27 -5
- package/src/cli/ai/tools.test.js +23 -0
- package/src/cli/ai/transport.js +89 -0
- package/src/cli/ai/turn.js +126 -0
- package/src/config.js +88 -5
- package/src/ui/public/assets/{AgentView-DJOdvJEV.js → AgentView-CZc191Xm.js} +3 -3
- package/src/ui/public/assets/AgentView-CzUeyQfR.css +1 -0
- package/src/ui/public/assets/{AppVersionBadge-D2Li5_R-.js → AppVersionBadge-Gs5lUl7P.js} +2 -2
- package/src/ui/public/assets/{BranchSelector-CSd5uaVm.js → BranchSelector-IjaFk0S-.js} +1 -1
- package/src/ui/public/assets/{CommitForm-Ckg9-zay.js → CommitForm-C-sowOXH.js} +3 -3
- package/src/ui/public/assets/{CommonDialog-B8XOXtz_.js → CommonDialog-QyptFD1R.js} +1 -1
- package/src/ui/public/assets/{EditorView-Crj8S2TI.js → EditorView-CvvluFCY.js} +1 -1
- package/src/ui/public/assets/{FileDiffViewer-DL07IMz5.js → FileDiffViewer-BXVsdCxE.js} +1 -1
- package/src/ui/public/assets/{FlowExecutionViewer-DvVT19Qv.js → FlowExecutionViewer-CRxipXru.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-kRhK9IyL.js → FlowOrchestrationWorkspace-AE1ZHTKF.js} +1 -1
- package/src/ui/public/assets/{IconButton-pqSYKRT8.js → IconButton-BmSueF_H.js} +1 -1
- package/src/ui/public/assets/{LogList-BrN6qtFb.js → LogList-iaVA-_aC.js} +1 -1
- package/src/ui/public/assets/{MindmapView-CSsT0NDZ.js → MindmapView-DcG4jpCh.js} +1 -1
- package/src/ui/public/assets/{MonitorView-edRyBybQ.js → MonitorView-DO59syvl.js} +1 -1
- package/src/ui/public/assets/{ProjectStartupButton-DZ8VgU79.js → ProjectStartupButton-DYppceyw.js} +1 -1
- package/src/ui/public/assets/{RemoteManagerDialog-pu9szuYB.js → RemoteManagerDialog-CDbxG6pZ.js} +1 -1
- package/src/ui/public/assets/{RemoteRepoCard-BSFehgY5.js → RemoteRepoCard-Cko2KB8w.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-DbEydLSU.js → SourceMapView-CwWPVdz-.js} +1 -1
- package/src/ui/public/assets/{SvgIcon-BVCbrPws.js → SvgIcon-Cf7nOKc8.js} +1 -1
- package/src/ui/public/assets/{TemplateManager-DPyYaGCE.js → TemplateManager-DWPDjWxM.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-DXHeCjzG.js → UserInputNode-YOziN-5A.js} +1 -1
- package/src/ui/public/assets/WorkbenchView-DNnEpIYy.js +13 -0
- package/src/ui/public/assets/WorkbenchView-v3QFn6Ln.css +1 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-CQjg8MXm.js +19 -0
- package/src/ui/public/assets/configStore-C7HOx5S1.js +1 -0
- package/src/ui/public/assets/{gitStore-BJbNtjxA.js → gitStore-DIsBd6n4.js} +1 -1
- package/src/ui/public/assets/index-FOe1cHkh.css +1 -0
- package/src/ui/public/assets/index-MmcnT2Dn.js +24 -0
- package/src/ui/public/assets/{previewSandbox-BZV6Eqhv.js → previewSandbox-TKucqLxt.js} +0 -0
- package/src/ui/public/assets/{toolsStore-Bu8Ujw_A.js → toolsStore-BiC2mmC3.js} +1 -1
- package/src/ui/public/assets/{vendor-CwZs6S_i.js → vendor-92fKgtnz.js} +7 -7
- package/src/ui/public/assets/{vendor-_6pgW9Z2.css → vendor-B1UrVTTI.css} +1 -1
- package/src/ui/public/index.html +14 -14
- package/src/ui/server/routes/codeAnalysis.js +6 -3
- package/src/ui/server/routes/config.js +40 -9
- package/src/ui/server/routes/fileOpen.js +48 -2
- package/src/ui/server/routes/gAiLauncher.test.js +70 -0
- package/src/ui/server/routes/workbench/agentChat.js +34 -8
- package/src/ui/server/routes/workbench/index.js +37 -6
- package/src/ui/server/routes/workbench/jobStore.js +4 -0
- package/src/ui/server/routes/workbench/llmClient.js +15 -8
- package/src/ui/server/routes/workbench/taskRunner.js +276 -96
- package/src/ui/server/routes/workbench/taskRunner.opencode.test.js +126 -0
- package/src/utils/aiEndpoint.js +231 -0
- package/src/utils/aiEndpoint.test.js +175 -0
- package/src/ui/public/assets/AgentView-BsN9y4Kg.css +0 -1
- package/src/ui/public/assets/WorkbenchView-CKXtZAJn.css +0 -1
- package/src/ui/public/assets/WorkbenchView-DGcirla3.js +0 -13
- package/src/ui/public/assets/_plugin-vue_export-helper-C-YBnnP5.js +0 -19
- package/src/ui/public/assets/configStore-DoobmT7N.js +0 -1
- package/src/ui/public/assets/index-ClqvC4kJ.css +0 -1
- package/src/ui/public/assets/index-ndDaGoRO.js +0 -24
|
@@ -35,10 +35,27 @@ import { agentSessionStore } from './agentSessionStore.js';
|
|
|
35
35
|
import { TOOL_DEFINITIONS, executeTool } from '../../../../cli/ai/tools.js';
|
|
36
36
|
import { checkDangerousCommand } from '../../../../cli/ai/safety.js';
|
|
37
37
|
import { guardCommand } from '../../../../cli/ai/platformGuard.js';
|
|
38
|
+
import configManager from '../../../../config.js';
|
|
39
|
+
import { buildAiChatRequest, describeAiHttpError } from '../../../../utils/aiEndpoint.js';
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// 单轮工具调用循环数的兜底值(防失控);实际值取全局配置 aiMaxToolIterations,
|
|
42
|
+
// 与 CLI 侧 src/cli/ai/agent.js 共用同一个配置项。
|
|
43
|
+
const DEFAULT_MAX_TOOL_ITERATIONS = 200;
|
|
40
44
|
const LLM_TIMEOUT_MS = 300000; // 5 分钟
|
|
41
45
|
|
|
46
|
+
// 读取全局配置里的单轮工具调用上限。
|
|
47
|
+
// 读配置失败不该把整轮对话打挂 —— 退回默认值继续跑,比用户消息直接发不出去好。
|
|
48
|
+
async function resolveMaxToolIterations() {
|
|
49
|
+
try {
|
|
50
|
+
const cfg = await configManager.loadConfig();
|
|
51
|
+
const n = Number(cfg?.aiMaxToolIterations);
|
|
52
|
+
if (Number.isFinite(n) && n > 0) return Math.floor(n);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
logger.warn(`[agentChat] 读取 aiMaxToolIterations 失败,回退默认值: ${err?.message || err}`);
|
|
55
|
+
}
|
|
56
|
+
return DEFAULT_MAX_TOOL_ITERATIONS;
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
// ── 系统提示词构建 ──────────────────────────────────────────
|
|
43
60
|
// 与 CLI agent.js 的 buildSystemPrompt 保持一致,但标注来源为 Web 端
|
|
44
61
|
function buildWebSystemPrompt({ cwd, locale }) {
|
|
@@ -138,10 +155,14 @@ ${isWin ? `- This is Windows. The following Unix commands do NOT exist here:
|
|
|
138
155
|
|
|
139
156
|
// ── LLM 流式调用(OpenAI 兼容 + function calling) ──────────
|
|
140
157
|
// 返回 { content, toolCalls, aborted }
|
|
141
|
-
async function streamChatOnce({ model, messages, signal, onToken }) {
|
|
142
|
-
|
|
143
|
-
const headers = {
|
|
144
|
-
|
|
158
|
+
async function streamChatOnce({ model, messages, signal, onToken, sessionId }) {
|
|
159
|
+
// sessionId = 对话 ID:OpenCode 网关靠它做路由与提示缓存,同一轮对话的所有请求要复用
|
|
160
|
+
const { url, headers } = buildAiChatRequest({
|
|
161
|
+
baseURL: model.baseURL,
|
|
162
|
+
model: model.model,
|
|
163
|
+
apiKey: model.apiKey,
|
|
164
|
+
sessionId,
|
|
165
|
+
});
|
|
145
166
|
|
|
146
167
|
const body = JSON.stringify({
|
|
147
168
|
model: model.model,
|
|
@@ -167,7 +188,8 @@ async function streamChatOnce({ model, messages, signal, onToken }) {
|
|
|
167
188
|
const resp = await fetch(url, { method: 'POST', headers, body, signal: controller.signal });
|
|
168
189
|
if (!resp.ok || !resp.body) {
|
|
169
190
|
const errText = await resp.text().catch(() => '');
|
|
170
|
-
|
|
191
|
+
// 抽 error.message:网关/provider 的说明都在里面,整坨 JSON 甩到聊天窗没法看
|
|
192
|
+
const snippet = describeAiHttpError(errText, resp.status);
|
|
171
193
|
if (resp.status === 400 && /tool|function/i.test(snippet)) {
|
|
172
194
|
throw new Error(`HTTP 400: 当前模型可能不支持 function calling(${snippet})。请在设置中换用支持工具调用的模型。`);
|
|
173
195
|
}
|
|
@@ -342,7 +364,9 @@ export async function runAgentTurn({ session, model, userMessage, images = [], c
|
|
|
342
364
|
// 旧图片降级
|
|
343
365
|
stripStaleImages(session.messages);
|
|
344
366
|
|
|
345
|
-
|
|
367
|
+
const maxIterations = await resolveMaxToolIterations();
|
|
368
|
+
|
|
369
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
346
370
|
trimHistory(session.messages);
|
|
347
371
|
sanitizeMessages(session.messages);
|
|
348
372
|
|
|
@@ -352,6 +376,8 @@ export async function runAgentTurn({ session, model, userMessage, images = [], c
|
|
|
352
376
|
model,
|
|
353
377
|
messages: session.messages,
|
|
354
378
|
signal,
|
|
379
|
+
// 整轮对话(含后续工具调用产生的每一轮请求)复用同一个会话 ID
|
|
380
|
+
sessionId: session.sessionId,
|
|
355
381
|
onToken: ({ thinking, content }) => {
|
|
356
382
|
if (thinking) send({ type: 'thinking', delta: thinking });
|
|
357
383
|
if (content) send({ type: 'content', delta: content });
|
|
@@ -418,7 +444,7 @@ export async function runAgentTurn({ session, model, userMessage, images = [], c
|
|
|
418
444
|
}
|
|
419
445
|
|
|
420
446
|
// 达到最大迭代次数
|
|
421
|
-
send({ type: 'done', content: `已达单轮最大工具调用次数(${
|
|
447
|
+
send({ type: 'done', content: `已达单轮最大工具调用次数(${maxIterations}),本轮结束。如需继续请再发一条消息。` });
|
|
422
448
|
return { aborted: false };
|
|
423
449
|
}
|
|
424
450
|
|
|
@@ -105,6 +105,7 @@ import {
|
|
|
105
105
|
} from './jobStore.js';
|
|
106
106
|
import {
|
|
107
107
|
launchClaudeInNewWindow,
|
|
108
|
+
normalizeTaskExecutor,
|
|
108
109
|
runTaskQueue,
|
|
109
110
|
runSingleSubtask,
|
|
110
111
|
syncSubToCancelled,
|
|
@@ -138,6 +139,25 @@ import {
|
|
|
138
139
|
|
|
139
140
|
const { genSessionId, read: readSessionFile, write: writeSessionFile, delete: deleteSessionFile, listMeta: listSessionsMeta, enforceRetention: enforceSessionsRetention } = sessionStore;
|
|
140
141
|
|
|
142
|
+
/**
|
|
143
|
+
* 解析本次执行用哪个执行器:body.executor 显式指定 > 配置里的全局默认 > 'claude'。
|
|
144
|
+
* 配置读失败一律回落 'claude' —— 执行器选不出来不该挡住任务本身能跑。
|
|
145
|
+
*/
|
|
146
|
+
async function resolveExecutor(requested) {
|
|
147
|
+
const explicit = normalizeTaskExecutor(requested);
|
|
148
|
+
if (explicit) return explicit;
|
|
149
|
+
try {
|
|
150
|
+
if (configManager) {
|
|
151
|
+
const cfg = await configManager.loadConfig();
|
|
152
|
+
const fallback = normalizeTaskExecutor(cfg?.taskExecutor);
|
|
153
|
+
if (fallback) return fallback;
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.warn('[workbench] 读取 taskExecutor 配置失败,回落 claude:', err && err.message || err);
|
|
157
|
+
}
|
|
158
|
+
return 'claude';
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
/**
|
|
142
162
|
* 注册所有 workbench 路由(共 45 个端点)。
|
|
143
163
|
* @param {Object} deps
|
|
@@ -1059,9 +1079,10 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1059
1079
|
throw new HttpError(400, '任务没有子任务');
|
|
1060
1080
|
}
|
|
1061
1081
|
const repoPath = resolveTaskRepoPath(task, typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '');
|
|
1082
|
+
const executor = await resolveExecutor(req.body?.executor);
|
|
1062
1083
|
// 异步执行,立即返回
|
|
1063
1084
|
res.json({ success: true, message: '已开始执行' });
|
|
1064
|
-
runTaskQueue(task, repoPath, '').catch(err => {
|
|
1085
|
+
runTaskQueue(task, repoPath, '', { executor }).catch(err => {
|
|
1065
1086
|
publish('task:error', { taskId: task.id, error: err.message });
|
|
1066
1087
|
});
|
|
1067
1088
|
}));
|
|
@@ -1078,8 +1099,9 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1078
1099
|
throw new HttpError(400, 'startSubIndex 越界');
|
|
1079
1100
|
}
|
|
1080
1101
|
const repoPath = resolveTaskRepoPath(task, typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '');
|
|
1102
|
+
const executor = await resolveExecutor(req.body?.executor);
|
|
1081
1103
|
res.json({ success: true, message: `已从第 ${startSubIndex + 1} 个子任务开始执行` });
|
|
1082
|
-
runTaskQueue(task, repoPath, '', { fromIndex: startSubIndex }).catch(err => {
|
|
1104
|
+
runTaskQueue(task, repoPath, '', { fromIndex: startSubIndex, executor }).catch(err => {
|
|
1083
1105
|
publish('task:error', { taskId: task.id, error: err.message });
|
|
1084
1106
|
});
|
|
1085
1107
|
}));
|
|
@@ -1092,6 +1114,7 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1092
1114
|
throw new HttpError(400, '该任务不是简单任务,请使用普通执行接口');
|
|
1093
1115
|
}
|
|
1094
1116
|
const repoPath = resolveTaskRepoPath(task, typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '');
|
|
1117
|
+
const executor = await resolveExecutor(req.body?.executor);
|
|
1095
1118
|
const virtualSub = {
|
|
1096
1119
|
id: `${task.id}__simple`,
|
|
1097
1120
|
title: task.title,
|
|
@@ -1101,7 +1124,7 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1101
1124
|
attachments: Array.isArray(task.attachments) ? task.attachments : []
|
|
1102
1125
|
};
|
|
1103
1126
|
res.json({ success: true, message: '已开始执行简单任务' });
|
|
1104
|
-
runSingleSubtask(task, virtualSub, repoPath, '', []).catch(err => {
|
|
1127
|
+
runSingleSubtask(task, virtualSub, repoPath, '', [], { executor }).catch(err => {
|
|
1105
1128
|
publish('task:error', { taskId: task.id, error: err.message });
|
|
1106
1129
|
});
|
|
1107
1130
|
}));
|
|
@@ -1124,11 +1147,12 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1124
1147
|
const liveJob = snapshotJobs().find(j => j.subId === subId && (j.status === 'running' || j.status === 'pending'));
|
|
1125
1148
|
if (liveJob) throw new HttpError(400, '该子任务已有正在执行的 job');
|
|
1126
1149
|
const repoPath = resolveTaskRepoPath(foundTask, typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '');
|
|
1150
|
+
const executor = await resolveExecutor(req.body?.executor);
|
|
1127
1151
|
res.json({ success: true, message: `已开始执行子任务:${foundSub.title || subId}` });
|
|
1128
1152
|
(async () => {
|
|
1129
1153
|
try {
|
|
1130
1154
|
const priorOutputs = await collectPriorOutputs(foundTask, foundSub);
|
|
1131
|
-
await runSingleSubtask(foundTask, foundSub, repoPath, '', priorOutputs);
|
|
1155
|
+
await runSingleSubtask(foundTask, foundSub, repoPath, '', priorOutputs, { executor });
|
|
1132
1156
|
await persistTaskAfterRun(foundTask);
|
|
1133
1157
|
} catch (err) {
|
|
1134
1158
|
publish('task:error', { taskId: foundTask.id, error: err.message });
|
|
@@ -1240,7 +1264,12 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
1240
1264
|
};
|
|
1241
1265
|
const repoPath = typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : '';
|
|
1242
1266
|
res.json({ success: true, message: '已加入续接队列' });
|
|
1243
|
-
|
|
1267
|
+
// 执行器必须沿用上一轮 job 的 agent:claude 的 --resume 和 opencode 的 --session
|
|
1268
|
+
// 互不认对方的会话 id,串了执行器续接必然失败。
|
|
1269
|
+
runSingleSubtask(task, virtualSub, repoPath, '', [], {
|
|
1270
|
+
resumeSessionId: prevJob.claudeSessionId,
|
|
1271
|
+
executor: normalizeTaskExecutor(prevJob.agent)
|
|
1272
|
+
})
|
|
1244
1273
|
.catch(err => {
|
|
1245
1274
|
publish('task:error', { taskId: task.id, error: err.message });
|
|
1246
1275
|
});
|
|
@@ -2113,7 +2142,9 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2113
2142
|
promptOverride: task.simpleOverride || '',
|
|
2114
2143
|
attachments: [],
|
|
2115
2144
|
};
|
|
2116
|
-
|
|
2145
|
+
// 派发控制台可以显式指定执行器;不传则用配置里的全局默认(resolveExecutor 走回落链)
|
|
2146
|
+
const executor = await resolveExecutor(req.body?.executor);
|
|
2147
|
+
runSingleSubtask(task, virtualSub, targetPath, '', [], { executor }).catch(err => {
|
|
2117
2148
|
publish('task:error', { taskId: task.id, error: err.message });
|
|
2118
2149
|
});
|
|
2119
2150
|
}
|
|
@@ -309,11 +309,15 @@ export function snapshotJobs() {
|
|
|
309
309
|
status: j.status,
|
|
310
310
|
prompt: j.prompt || '',
|
|
311
311
|
output: j.output || '',
|
|
312
|
+
thinking: j.thinking || '',
|
|
312
313
|
pid: j.pid || null,
|
|
313
314
|
startedAt: j.startedAt || null,
|
|
314
315
|
endedAt: j.endedAt || null,
|
|
315
316
|
exitCode: typeof j.exitCode === 'number' ? j.exitCode : null,
|
|
316
317
|
error: j.error || null,
|
|
318
|
+
// 本轮用的执行器(claude | opencode)。前端对话区助手名 / 日志详情按它显示;
|
|
319
|
+
// 加字段时记得同步这里 —— 白名单投影会把没列出的字段静默剥掉。
|
|
320
|
+
agent: j.agent || null,
|
|
317
321
|
// 续接对话用:claude --output-format stream-json 的 system.init 事件捕获到的 session_id
|
|
318
322
|
claudeSessionId: j.claudeSessionId || null
|
|
319
323
|
}));
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// - cloneMsgContent 深拷贝消息 content(避免外部 mutation 污染请求)
|
|
24
24
|
|
|
25
25
|
import { stripThinkingBlocks, extractFirstJsonObject } from './jsonParse.js';
|
|
26
|
+
import { buildAiChatRequest, describeAiHttpError } from '../../../../utils/aiEndpoint.js';
|
|
26
27
|
|
|
27
28
|
// 非流式调用 LLM,要求返回 JSON 对象。
|
|
28
29
|
// 失败时抛错;成功时返回已 parse 的对象。
|
|
@@ -32,9 +33,11 @@ import { stripThinkingBlocks, extractFirstJsonObject } from './jsonParse.js';
|
|
|
32
33
|
export async function callLlmJson(model, prompt, opts = {}) {
|
|
33
34
|
const { timeoutMs = 60000, images = [] } = opts;
|
|
34
35
|
const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }));
|
|
35
|
-
const url
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const { url, headers } = buildAiChatRequest({
|
|
37
|
+
baseURL: model.baseURL,
|
|
38
|
+
model: model.model,
|
|
39
|
+
apiKey: model.apiKey,
|
|
40
|
+
});
|
|
38
41
|
|
|
39
42
|
let userContent;
|
|
40
43
|
if (Array.isArray(images) && images.length > 0) {
|
|
@@ -115,11 +118,14 @@ export function cloneMsgContent(c) {
|
|
|
115
118
|
* 函数返回 { aborted: true },不抛错——上层决定怎么处理。
|
|
116
119
|
*/
|
|
117
120
|
export async function callLlmStream(model, input, onDelta, opts = {}) {
|
|
118
|
-
const { maxTokens = 2000, timeoutMs = 600000, signal, images = [], systemPrompt } = opts;
|
|
121
|
+
const { maxTokens = 2000, timeoutMs = 600000, signal, images = [], systemPrompt, sessionId } = opts;
|
|
119
122
|
const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }));
|
|
120
|
-
const url
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
const { url, headers } = buildAiChatRequest({
|
|
124
|
+
baseURL: model.baseURL,
|
|
125
|
+
model: model.model,
|
|
126
|
+
apiKey: model.apiKey,
|
|
127
|
+
sessionId,
|
|
128
|
+
});
|
|
123
129
|
|
|
124
130
|
// 判别 input: string(旧,直接拆分)或 messages 数组(新,对话拆分)
|
|
125
131
|
let messages;
|
|
@@ -167,7 +173,8 @@ export async function callLlmStream(model, input, onDelta, opts = {}) {
|
|
|
167
173
|
const resp = await fetch(url, { method: 'POST', headers, body, signal: controller.signal });
|
|
168
174
|
if (!resp.ok || !resp.body) {
|
|
169
175
|
const errText = await resp.text().catch(() => '');
|
|
170
|
-
|
|
176
|
+
// 抽 error.message,别把整坨 JSON 甩给用户(网关的报错说明都在 message 里)
|
|
177
|
+
throw new Error(describeAiHttpError(errText, resp.status));
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
// SSE 格式:每行 "data: {...}",最后 "data: [DONE]"
|