@cicctencent/agent-core 0.1.25 → 0.1.27

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.
Files changed (35) hide show
  1. package/dist/cjs/agent/llm-caller.d.ts +0 -2
  2. package/dist/cjs/agent/llm-caller.d.ts.map +1 -1
  3. package/dist/cjs/agent/llm-caller.js +50 -105
  4. package/dist/cjs/agent/llm-caller.js.map +1 -1
  5. package/dist/cjs/agent/tool-executor.d.ts.map +1 -1
  6. package/dist/cjs/agent/tool-executor.js +3 -1
  7. package/dist/cjs/agent/tool-executor.js.map +1 -1
  8. package/dist/cjs/index.d.ts +2 -0
  9. package/dist/cjs/index.d.ts.map +1 -1
  10. package/dist/cjs/index.js +5 -1
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/runtime/run-registry-interface.d.ts +5 -5
  13. package/dist/cjs/runtime/run-registry-interface.d.ts.map +1 -1
  14. package/dist/cjs/utils/dsml-parser.d.ts +89 -0
  15. package/dist/cjs/utils/dsml-parser.d.ts.map +1 -0
  16. package/dist/cjs/utils/dsml-parser.js +289 -0
  17. package/dist/cjs/utils/dsml-parser.js.map +1 -0
  18. package/dist/esm/agent/llm-caller.d.ts +0 -2
  19. package/dist/esm/agent/llm-caller.d.ts.map +1 -1
  20. package/dist/esm/agent/llm-caller.js +50 -105
  21. package/dist/esm/agent/llm-caller.js.map +1 -1
  22. package/dist/esm/agent/tool-executor.d.ts.map +1 -1
  23. package/dist/esm/agent/tool-executor.js +3 -1
  24. package/dist/esm/agent/tool-executor.js.map +1 -1
  25. package/dist/esm/index.d.ts +2 -0
  26. package/dist/esm/index.d.ts.map +1 -1
  27. package/dist/esm/index.js +1 -0
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/runtime/run-registry-interface.d.ts +5 -5
  30. package/dist/esm/runtime/run-registry-interface.d.ts.map +1 -1
  31. package/dist/esm/utils/dsml-parser.d.ts +89 -0
  32. package/dist/esm/utils/dsml-parser.d.ts.map +1 -0
  33. package/dist/esm/utils/dsml-parser.js +283 -0
  34. package/dist/esm/utils/dsml-parser.js.map +1 -0
  35. package/package.json +2 -1
@@ -0,0 +1,89 @@
1
+ /**
2
+ * DSML Parser — 解析 DeepSeek 的 DSML 格式工具调用
3
+ *
4
+ * DeepSeek-V4-Flash 等模型在未走标准 OpenAI tool_calls 通道时,会以 DSML
5
+ * (DeepSeek Markup Language)格式在正文流中输出工具调用。DSML 使用全角竖线
6
+ * `|`(U+FF5C)作为标记分隔,本解析器同时容错半角竖线 `|`(U+007C)。
7
+ *
8
+ * 格式示例:
9
+ * <|DSML|tool_calls>
10
+ * <|DSML|invoke name="create_report">
11
+ * <|DSML|parameter name="title" string="true">国泰海通...</|DSML|parameter>
12
+ * ...
13
+ * </|DSML|invoke>
14
+ * </|DSML|tool_calls>
15
+ *
16
+ * 实现要点:
17
+ * - 用状态机扫描而非正则,支持参数值中包含 `<`、换行、普通 XML 标签等内容。
18
+ * - 参数 `string="false"` 时尝试 JSON.parse,失败则保留原始字符串并告警。
19
+ * - 工具调用 id 使用全局单调计数器 + crypto.randomUUID,避免流式场景重复。
20
+ *
21
+ * 限制:DSML 格式本身无转义机制,参数值不能包含完整的闭合标记字面量
22
+ * (如 `</|DSML|parameter>`、`</|DSML|invoke>`),否则会与真闭合标记歧义。
23
+ *
24
+ * @see https://blog.csdn.net/gitblog_00855/article/details/152146045 DSML 格式说明
25
+ */
26
+ import type { ToolCall } from '../types.js';
27
+ /** DSML 工具调用解析结果 */
28
+ export interface DSMLParseResult {
29
+ toolCalls: ToolCall[];
30
+ /** 解析后的剩余文本(非工具调用部分) */
31
+ remainingText: string;
32
+ }
33
+ /**
34
+ * 解析 DSML 格式的工具调用
35
+ *
36
+ * @param text 可能包含 DSML 格式的文本
37
+ * @returns 解析结果,包含工具调用列表和剩余文本
38
+ */
39
+ export declare function parseDSML(text: string): DSMLParseResult;
40
+ /**
41
+ * 流式 DSML 解析器状态
42
+ *
43
+ * 用于处理流式输出,累积文本并检测 DSML 工具调用的开始/结束。
44
+ * 关键设计:
45
+ * - 始终先累积到 buffer,再尝试提取完整 DSML 块。
46
+ * - 当 buffer 末尾是某个 DSML 标记的可能前缀(如 `<`、`<|DSML|tool`)时,
47
+ * 暂留该前缀不作为文本增量发出,等待后续 chunk 确认。
48
+ * - 仅当检测到完整的 `<|DSML|tool_calls>...</|DSML|tool_calls>` 块时才解析。
49
+ */
50
+ export declare class StreamingDSMLParser {
51
+ private buffer;
52
+ /**
53
+ * 计算 buffer 末尾「是某个 DSML 标记前缀」的长度。
54
+ * 例如 buffer 末尾为 `<|DSML|tool`,它是 `<|DSML|tool_calls>` 的前缀,
55
+ * 返回该前缀长度,调用方据此暂留尾部、只发出前段文本。
56
+ */
57
+ private trailingMarkerPrefixLen;
58
+ /**
59
+ * 从 buffer 中提取完整 DSML 块并解析,返回可安全发出的文本增量与已完成的工具调用。
60
+ */
61
+ private drain;
62
+ /**
63
+ * 处理流式文本片段
64
+ *
65
+ * @param chunk 流式文本片段
66
+ * @returns 解析结果,包含增量文本和已完成的工具调用
67
+ */
68
+ process(chunk: string): {
69
+ textDelta: string;
70
+ completedToolCalls: ToolCall[];
71
+ };
72
+ /** 获取当前累积的缓冲区内容 */
73
+ getBuffer(): string;
74
+ /** 重置解析器状态 */
75
+ reset(): void;
76
+ /**
77
+ * 强制结束解析(用于流结束时处理未完成的 DSML)。
78
+ * 对残留 buffer 调用 parseDSML:完整块会被解析,未闭合块原样作为文本返回。
79
+ */
80
+ flush(): {
81
+ textDelta: string;
82
+ completedToolCalls: ToolCall[];
83
+ };
84
+ }
85
+ /**
86
+ * 检查文本是否包含 DSML 工具调用标记
87
+ */
88
+ export declare function containsDSML(text: string): boolean;
89
+ //# sourceMappingURL=dsml-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsml-parser.d.ts","sourceRoot":"","sources":["../../../src/utils/dsml-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,oBAAoB;AACpB,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,wBAAwB;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAyHD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CA4BvD;AAED;;;;;;;;;GASG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAc;IAE5B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAe/B;;OAEG;IACH,OAAO,CAAC,KAAK;IAgCb;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,QAAQ,EAAE,CAAA;KAAE;IAK7E,mBAAmB;IACnB,SAAS,IAAI,MAAM;IAInB,cAAc;IACd,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACH,KAAK,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,QAAQ,EAAE,CAAA;KAAE;CAS/D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD"}
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ /**
3
+ * DSML Parser — 解析 DeepSeek 的 DSML 格式工具调用
4
+ *
5
+ * DeepSeek-V4-Flash 等模型在未走标准 OpenAI tool_calls 通道时,会以 DSML
6
+ * (DeepSeek Markup Language)格式在正文流中输出工具调用。DSML 使用全角竖线
7
+ * `|`(U+FF5C)作为标记分隔,本解析器同时容错半角竖线 `|`(U+007C)。
8
+ *
9
+ * 格式示例:
10
+ * <|DSML|tool_calls>
11
+ * <|DSML|invoke name="create_report">
12
+ * <|DSML|parameter name="title" string="true">国泰海通...</|DSML|parameter>
13
+ * ...
14
+ * </|DSML|invoke>
15
+ * </|DSML|tool_calls>
16
+ *
17
+ * 实现要点:
18
+ * - 用状态机扫描而非正则,支持参数值中包含 `<`、换行、普通 XML 标签等内容。
19
+ * - 参数 `string="false"` 时尝试 JSON.parse,失败则保留原始字符串并告警。
20
+ * - 工具调用 id 使用全局单调计数器 + crypto.randomUUID,避免流式场景重复。
21
+ *
22
+ * 限制:DSML 格式本身无转义机制,参数值不能包含完整的闭合标记字面量
23
+ * (如 `</|DSML|parameter>`、`</|DSML|invoke>`),否则会与真闭合标记歧义。
24
+ *
25
+ * @see https://blog.csdn.net/gitblog_00855/article/details/152146045 DSML 格式说明
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.StreamingDSMLParser = void 0;
29
+ exports.parseDSML = parseDSML;
30
+ exports.containsDSML = containsDSML;
31
+ // ---- DSML 标记常量(统一使用全角竖线 U+FF5C)----
32
+ const OPEN_TOOL_CALLS = '<|DSML|tool_calls>';
33
+ const CLOSE_TOOL_CALLS = '</|DSML|tool_calls>';
34
+ const OPEN_INVOKE = '<|DSML|invoke';
35
+ const CLOSE_INVOKE = '</|DSML|invoke>';
36
+ const OPEN_PARAMETER = '<|DSML|parameter';
37
+ const CLOSE_PARAMETER = '</|DSML|parameter>';
38
+ /** 所有 DSML 标记(含开/闭),用于流式前缀保留判断 */
39
+ const ALL_MARKERS = [
40
+ OPEN_TOOL_CALLS, CLOSE_TOOL_CALLS,
41
+ OPEN_INVOKE, CLOSE_INVOKE,
42
+ OPEN_PARAMETER, CLOSE_PARAMETER,
43
+ ];
44
+ /** 全角竖线 U+FF5C 与半角竖线 U+007C 的匹配正则 */
45
+ const BAR_RE = /[||]/g;
46
+ /**
47
+ * 在 text 中查找 marker 的位置。
48
+ * marker 使用全角竖线;text 中的竖线可为全角或半角,均能命中。
49
+ * 返回原始 text 中的索引(非归一化后),未命中返回 -1。
50
+ */
51
+ function indexOfMarker(text, marker, from = 0) {
52
+ // 1:1 替换竖线为 \x00,长度不变,索引保持一致
53
+ const nt = text.replace(BAR_RE, '\x00');
54
+ const nm = marker.replace(BAR_RE, '\x00');
55
+ return nt.indexOf(nm, from);
56
+ }
57
+ /** 全局单调计数器,保证同毫秒内 id 不重复 */
58
+ let dsmlIdCounter = 0;
59
+ /** 生成唯一工具调用 id */
60
+ function nextDSMLId() {
61
+ const g = globalThis;
62
+ if (g.crypto?.randomUUID) {
63
+ return `dsml_${g.crypto.randomUUID()}`;
64
+ }
65
+ dsmlIdCounter = (dsmlIdCounter + 1) % Number.MAX_SAFE_INTEGER;
66
+ return `dsml_${Date.now()}_${dsmlIdCounter}`;
67
+ }
68
+ /** 解析单个参数值:string=true 原样返回,否则尝试 JSON.parse */
69
+ function decodeValue(raw, isString, paramName) {
70
+ if (isString)
71
+ return raw;
72
+ const trimmed = raw.trim();
73
+ if (trimmed === '')
74
+ return raw;
75
+ try {
76
+ return JSON.parse(trimmed);
77
+ }
78
+ catch {
79
+ console.warn(`[DSML] Failed to JSON.parse parameter "${paramName}", keeping raw string`);
80
+ return raw;
81
+ }
82
+ }
83
+ /** 从开标签文本中提取属性值,如 name="x" → x;属性顺序无关 */
84
+ function getAttr(openTag, attr) {
85
+ const re = new RegExp(`${attr}\\s*=\\s*"([^"]*)"`);
86
+ const m = openTag.match(re);
87
+ return m ? m[1] : null;
88
+ }
89
+ /**
90
+ * 解析 invoke 块内容,提取参数并构造 ToolCall,追加到 out。
91
+ * @param block invoke 标签之间的内容(不含 invoke 开闭标签本身)
92
+ */
93
+ function parseInvokesInBlock(block, out) {
94
+ let i = 0;
95
+ while (i < block.length) {
96
+ const s = indexOfMarker(block, OPEN_INVOKE, i);
97
+ if (s === -1)
98
+ break;
99
+ // invoke 开标签可能带属性,找到其结束 '>'
100
+ const tagEnd = block.indexOf('>', s);
101
+ if (tagEnd === -1)
102
+ break;
103
+ const openTag = block.slice(s, tagEnd + 1);
104
+ const toolName = getAttr(openTag, 'name') ?? '';
105
+ const afterOpen = tagEnd + 1;
106
+ const e = indexOfMarker(block, CLOSE_INVOKE, afterOpen);
107
+ if (e === -1)
108
+ break; // 未闭合 invoke,跳过
109
+ const paramsBlock = block.slice(afterOpen, e);
110
+ const args = parseParamsInBlock(paramsBlock);
111
+ out.push({
112
+ id: nextDSMLId(),
113
+ type: 'function',
114
+ function: { name: toolName, arguments: JSON.stringify(args) },
115
+ });
116
+ i = e + CLOSE_INVOKE.length;
117
+ }
118
+ }
119
+ /**
120
+ * 解析 parameter 块内容,返回参数键值对。
121
+ * 参数值用精确闭合标记切片,可包含 `<`、换行、子串等任意内容。
122
+ */
123
+ function parseParamsInBlock(paramsBlock) {
124
+ const args = {};
125
+ let i = 0;
126
+ while (i < paramsBlock.length) {
127
+ const s = indexOfMarker(paramsBlock, OPEN_PARAMETER, i);
128
+ if (s === -1)
129
+ break;
130
+ const tagEnd = paramsBlock.indexOf('>', s);
131
+ if (tagEnd === -1)
132
+ break;
133
+ const openTag = paramsBlock.slice(s, tagEnd + 1);
134
+ const paramName = getAttr(openTag, 'name') ?? '';
135
+ const stringAttr = getAttr(openTag, 'string');
136
+ const isString = stringAttr === null ? true : stringAttr === 'true';
137
+ const afterOpen = tagEnd + 1;
138
+ const e = indexOfMarker(paramsBlock, CLOSE_PARAMETER, afterOpen);
139
+ if (e === -1)
140
+ break; // 未闭合 parameter,跳过
141
+ const rawValue = paramsBlock.slice(afterOpen, e);
142
+ if (paramName) {
143
+ args[paramName] = decodeValue(rawValue, isString, paramName);
144
+ }
145
+ i = e + CLOSE_PARAMETER.length;
146
+ }
147
+ return args;
148
+ }
149
+ /**
150
+ * 解析 DSML 格式的工具调用
151
+ *
152
+ * @param text 可能包含 DSML 格式的文本
153
+ * @returns 解析结果,包含工具调用列表和剩余文本
154
+ */
155
+ function parseDSML(text) {
156
+ const toolCalls = [];
157
+ const remainingPieces = [];
158
+ let i = 0;
159
+ while (i < text.length) {
160
+ const openIdx = indexOfMarker(text, OPEN_TOOL_CALLS, i);
161
+ if (openIdx === -1) {
162
+ // 剩余无 DSML 工具调用块,全部作为剩余文本
163
+ remainingPieces.push(text.slice(i));
164
+ break;
165
+ }
166
+ // 开标记之前的文本保留
167
+ if (openIdx > i)
168
+ remainingPieces.push(text.slice(i, openIdx));
169
+ const afterOpen = openIdx + OPEN_TOOL_CALLS.length;
170
+ const closeIdx = indexOfMarker(text, CLOSE_TOOL_CALLS, afterOpen);
171
+ if (closeIdx === -1) {
172
+ // 未闭合的 tool_calls 块:保留原样作为剩余文本(避免丢字,由调用方决定处理)
173
+ remainingPieces.push(text.slice(openIdx));
174
+ break;
175
+ }
176
+ const block = text.slice(afterOpen, closeIdx);
177
+ parseInvokesInBlock(block, toolCalls);
178
+ i = closeIdx + CLOSE_TOOL_CALLS.length;
179
+ }
180
+ const remainingText = remainingPieces.join('').trim();
181
+ return { toolCalls, remainingText };
182
+ }
183
+ /**
184
+ * 流式 DSML 解析器状态
185
+ *
186
+ * 用于处理流式输出,累积文本并检测 DSML 工具调用的开始/结束。
187
+ * 关键设计:
188
+ * - 始终先累积到 buffer,再尝试提取完整 DSML 块。
189
+ * - 当 buffer 末尾是某个 DSML 标记的可能前缀(如 `<`、`<|DSML|tool`)时,
190
+ * 暂留该前缀不作为文本增量发出,等待后续 chunk 确认。
191
+ * - 仅当检测到完整的 `<|DSML|tool_calls>...</|DSML|tool_calls>` 块时才解析。
192
+ */
193
+ class StreamingDSMLParser {
194
+ buffer = '';
195
+ /**
196
+ * 计算 buffer 末尾「是某个 DSML 标记前缀」的长度。
197
+ * 例如 buffer 末尾为 `<|DSML|tool`,它是 `<|DSML|tool_calls>` 的前缀,
198
+ * 返回该前缀长度,调用方据此暂留尾部、只发出前段文本。
199
+ */
200
+ trailingMarkerPrefixLen(buf) {
201
+ const lastLt = buf.lastIndexOf('<');
202
+ if (lastLt === -1)
203
+ return 0;
204
+ const tail = buf.slice(lastLt);
205
+ const normTail = tail.replace(BAR_RE, '|');
206
+ let best = 0;
207
+ for (const m of ALL_MARKERS) {
208
+ // normTail 必须严格短于 m,且是 m 的前缀
209
+ if (normTail.length < m.length && m.startsWith(normTail)) {
210
+ best = Math.max(best, tail.length);
211
+ }
212
+ }
213
+ return best;
214
+ }
215
+ /**
216
+ * 从 buffer 中提取完整 DSML 块并解析,返回可安全发出的文本增量与已完成的工具调用。
217
+ */
218
+ drain() {
219
+ const completedToolCalls = [];
220
+ const textPieces = [];
221
+ let buf = this.buffer;
222
+ for (;;) {
223
+ const openIdx = indexOfMarker(buf, OPEN_TOOL_CALLS);
224
+ if (openIdx === -1) {
225
+ // 无 DSML 开始标记:保留末尾可能的标记前缀,其余作为文本发出
226
+ const keep = this.trailingMarkerPrefixLen(buf);
227
+ const emitLen = buf.length - keep;
228
+ if (emitLen > 0)
229
+ textPieces.push(buf.slice(0, emitLen));
230
+ this.buffer = keep > 0 ? buf.slice(emitLen) : '';
231
+ return { textDelta: textPieces.join(''), completedToolCalls };
232
+ }
233
+ // 开标记之前的文本(确定非 DSML)直接发出
234
+ if (openIdx > 0)
235
+ textPieces.push(buf.slice(0, openIdx));
236
+ const afterOpen = openIdx + OPEN_TOOL_CALLS.length;
237
+ const closeIdx = indexOfMarker(buf, CLOSE_TOOL_CALLS, afterOpen);
238
+ if (closeIdx === -1) {
239
+ // 开始标记已出现但未闭合:从开标记起暂留 buffer,等待后续 chunk
240
+ this.buffer = buf.slice(openIdx);
241
+ return { textDelta: textPieces.join(''), completedToolCalls };
242
+ }
243
+ // 完整块:解析其中的 invoke
244
+ const block = buf.slice(afterOpen, closeIdx);
245
+ parseInvokesInBlock(block, completedToolCalls);
246
+ buf = buf.slice(closeIdx + CLOSE_TOOL_CALLS.length);
247
+ // 继续循环,处理 buf 中可能存在的下一个 DSML 块
248
+ }
249
+ }
250
+ /**
251
+ * 处理流式文本片段
252
+ *
253
+ * @param chunk 流式文本片段
254
+ * @returns 解析结果,包含增量文本和已完成的工具调用
255
+ */
256
+ process(chunk) {
257
+ this.buffer += chunk;
258
+ return this.drain();
259
+ }
260
+ /** 获取当前累积的缓冲区内容 */
261
+ getBuffer() {
262
+ return this.buffer;
263
+ }
264
+ /** 重置解析器状态 */
265
+ reset() {
266
+ this.buffer = '';
267
+ }
268
+ /**
269
+ * 强制结束解析(用于流结束时处理未完成的 DSML)。
270
+ * 对残留 buffer 调用 parseDSML:完整块会被解析,未闭合块原样作为文本返回。
271
+ */
272
+ flush() {
273
+ if (this.buffer.length === 0) {
274
+ this.reset();
275
+ return { textDelta: '', completedToolCalls: [] };
276
+ }
277
+ const result = parseDSML(this.buffer);
278
+ this.reset();
279
+ return { textDelta: result.remainingText, completedToolCalls: result.toolCalls };
280
+ }
281
+ }
282
+ exports.StreamingDSMLParser = StreamingDSMLParser;
283
+ /**
284
+ * 检查文本是否包含 DSML 工具调用标记
285
+ */
286
+ function containsDSML(text) {
287
+ return indexOfMarker(text, OPEN_TOOL_CALLS) !== -1;
288
+ }
289
+ //# sourceMappingURL=dsml-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsml-parser.js","sourceRoot":"","sources":["../../../src/utils/dsml-parser.ts"],"names":[],"mappings":"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"}
@@ -66,7 +66,5 @@ export declare class LLMCaller {
66
66
  private isContextTooLongError;
67
67
  /** 单次 LLM 调用(流式),带诊断信息 */
68
68
  private callOnceWithDiag;
69
- /** 单次 LLM 调用(流式),返回 assistant message 或 null(保留向后兼容) */
70
- private callOnce;
71
69
  }
72
70
  //# sourceMappingURL=llm-caller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"llm-caller.d.ts","sourceRoot":"","sources":["../../../src/agent/llm-caller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAY,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,eAAe;AACf,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,WAAW;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,mBAAmB;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACxE,0BAA0B;IAC1B,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC;IAC/E,2BAA2B;IAC3B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,kBAAkB;AAClB,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,EAAE,eAAe;IAInC,0BAA0B;IAC1B,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAI3C;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;;;;OAMG;IACI,aAAa,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,cAAc,EAAE,EAC1B,WAAW,EAAE,WAAW,GACvB,cAAc,CAAC,UAAU,EAAE;QAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IA0FhF,mBAAmB;IACnB,OAAO,CAAC,qBAAqB;IAO7B,0BAA0B;YACX,gBAAgB;IAoJ/B,wDAAwD;YACzC,QAAQ;CAqHxB"}
1
+ {"version":3,"file":"llm-caller.d.ts","sourceRoot":"","sources":["../../../src/agent/llm-caller.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAY,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,eAAe;AACf,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,WAAW;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,mBAAmB;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACxE,0BAA0B;IAC1B,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC;IAC/E,2BAA2B;IAC3B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,kBAAkB;AAClB,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,EAAE,eAAe;IAInC,0BAA0B;IAC1B,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAI3C;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAa3B;;;;;;OAMG;IACI,aAAa,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,cAAc,EAAE,EAC1B,WAAW,EAAE,WAAW,GACvB,cAAc,CAAC,UAAU,EAAE;QAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IA0FhF,mBAAmB;IACnB,OAAO,CAAC,qBAAqB;IAO7B,0BAA0B;YACX,gBAAgB;CAqMhC"}
@@ -1,6 +1,7 @@
1
1
  // ============================================================
2
2
  // LLMCaller — LLM 调用 + 重试 + 降级
3
3
  // ============================================================
4
+ import { StreamingDSMLParser } from "../utils/dsml-parser.js";
4
5
  /**
5
6
  * LLM 调用管理器
6
7
  *
@@ -164,6 +165,38 @@ export class LLMCaller {
164
165
  let role = 'assistant';
165
166
  let toolStreamingLastEmit = 0;
166
167
  let finishReason;
168
+ // DSML 流式解析器(用于处理 DeepSeek 模型的 DSML 格式工具调用)。
169
+ // 解析器内部已处理「部分 DSML 标记前缀」的暂留,因此所有 content chunk
170
+ // 统一走 process(),由解析器决定哪些是文本增量、哪些暂留等待后续 chunk。
171
+ const dsmlParser = new StreamingDSMLParser();
172
+ // DSML 工具调用使用大偏移 index,避免与标准 delta.tool_calls 的 index 冲突
173
+ const DSML_INDEX_BASE = 1_000_000;
174
+ let dsmlToolCallIndex = 0;
175
+ /** 将 DSML 解析出的 ToolCall 写入 toolCallsMap,返回需发出的 tool_call 事件 */
176
+ const buildDSMLToolCallEvent = (tc) => {
177
+ const idx = DSML_INDEX_BASE + dsmlToolCallIndex++;
178
+ toolCallsMap.set(idx, {
179
+ id: tc.id,
180
+ name: tc.function.name,
181
+ args: tc.function.arguments,
182
+ });
183
+ // arguments 由 parseDSML 内部 JSON.stringify 生成,但仍兜底防非法 JSON
184
+ let input;
185
+ try {
186
+ input = JSON.parse(tc.function.arguments);
187
+ }
188
+ catch {
189
+ input = { _raw: tc.function.arguments };
190
+ }
191
+ return this.config.createEvent(sessionId, {
192
+ type: 'tool_call',
193
+ toolCallId: tc.id,
194
+ toolName: tc.function.name,
195
+ displayName: tc.function.name,
196
+ input,
197
+ startedAt: Date.now(),
198
+ });
199
+ };
167
200
  for await (const chunk of stream) {
168
201
  if (abortSignal.aborted)
169
202
  break;
@@ -181,8 +214,14 @@ export class LLMCaller {
181
214
  if (delta.content) {
182
215
  const text = typeof delta.content === 'string' ? delta.content : '';
183
216
  if (text) {
184
- contentParts.push(text);
185
- yield this.config.createEvent(sessionId, { type: 'message', content: text, delta: true });
217
+ const result = dsmlParser.process(text);
218
+ if (result.textDelta) {
219
+ contentParts.push(result.textDelta);
220
+ yield this.config.createEvent(sessionId, { type: 'message', content: result.textDelta, delta: true });
221
+ }
222
+ for (const tc of result.completedToolCalls) {
223
+ yield buildDSMLToolCallEvent(tc);
224
+ }
186
225
  }
187
226
  }
188
227
  if (delta.tool_calls) {
@@ -213,6 +252,15 @@ export class LLMCaller {
213
252
  if (delta.role)
214
253
  role = delta.role;
215
254
  }
255
+ // 流结束后,处理 DSML 解析器中可能残留的缓冲区(完整块解析 + 未闭合块兜底)
256
+ const flushResult = dsmlParser.flush();
257
+ if (flushResult.textDelta) {
258
+ contentParts.push(flushResult.textDelta);
259
+ yield this.config.createEvent(sessionId, { type: 'message', content: flushResult.textDelta, delta: true });
260
+ }
261
+ for (const tc of flushResult.completedToolCalls) {
262
+ yield buildDSMLToolCallEvent(tc);
263
+ }
216
264
  const fullContent = contentParts.join('') || null;
217
265
  const fullReasoning = reasoningParts.join('') || null;
218
266
  const toolCalls = [];
@@ -267,109 +315,6 @@ export class LLMCaller {
267
315
  return { result: null, contextTooLong, usage: chunkUsage, error: errMsg };
268
316
  }
269
317
  }
270
- /** 单次 LLM 调用(流式),返回 assistant message 或 null(保留向后兼容) */
271
- async *callOnce(sessionId, messages, toolDefs, abortSignal, provider, isFallback) {
272
- try {
273
- // Hook: before_ll_call(仅主 Provider 首次调用时触发)
274
- if (!isFallback) {
275
- await this.config.runHooks('before_ll_call', {
276
- sessionId,
277
- iteration: 0,
278
- messages,
279
- tools: toolDefs,
280
- });
281
- }
282
- // 使用流式 API
283
- const stream = provider.chatStream({
284
- messages,
285
- tools: toolDefs.length > 0 ? toolDefs : undefined,
286
- toolChoice: toolDefs.length > 0 ? 'auto' : undefined,
287
- abortSignal,
288
- });
289
- // 累积流式数据
290
- let contentParts = [];
291
- let reasoningParts = [];
292
- const toolCallsMap = new Map();
293
- let role = 'assistant';
294
- let chunkUsage;
295
- let toolStreamingLastEmit = 0; // 工具参数流式生成进度追踪
296
- for await (const chunk of stream) {
297
- if (abortSignal.aborted)
298
- break;
299
- if (chunk.usage) {
300
- chunkUsage = chunk.usage;
301
- }
302
- const delta = chunk.delta;
303
- if (delta.reasoning_content) {
304
- reasoningParts.push(delta.reasoning_content);
305
- yield this.config.createEvent(sessionId, { type: 'thinking', content: delta.reasoning_content });
306
- }
307
- if (delta.content) {
308
- const text = typeof delta.content === 'string' ? delta.content : '';
309
- if (text) {
310
- contentParts.push(text);
311
- yield this.config.createEvent(sessionId, { type: 'message', content: text, delta: true });
312
- }
313
- }
314
- if (delta.tool_calls) {
315
- for (const tc of delta.tool_calls) {
316
- const idx = tc.index ?? 0;
317
- if (!toolCallsMap.has(idx)) {
318
- toolCallsMap.set(idx, { id: '', name: '', args: '' });
319
- }
320
- const entry = toolCallsMap.get(idx);
321
- if (tc.id)
322
- entry.id = tc.id;
323
- if (tc.function?.name)
324
- entry.name = tc.function.name;
325
- if (tc.function?.arguments)
326
- entry.args += tc.function.arguments;
327
- }
328
- // 工具参数流式生成时,定期发出 thinking 事件让 UI 有可见进度
329
- const totalLen = Array.from(toolCallsMap.values()).reduce((s, e) => s + e.args.length, 0);
330
- const threshold = Math.max(toolStreamingLastEmit * 2, 500);
331
- if (totalLen - toolStreamingLastEmit >= threshold) {
332
- toolStreamingLastEmit = totalLen;
333
- const names = Array.from(toolCallsMap.values()).map(e => e.name || 'tool').join(', ');
334
- yield this.config.createEvent(sessionId, {
335
- type: 'thinking',
336
- content: `⏳ Generating ${names} parameters (${totalLen} chars)...`,
337
- });
338
- }
339
- }
340
- if (delta.role)
341
- role = delta.role;
342
- }
343
- // 组装完整 assistant message
344
- const fullContent = contentParts.join('') || null;
345
- const fullReasoning = reasoningParts.join('') || null;
346
- const toolCalls = [];
347
- const sortedEntries = Array.from(toolCallsMap.entries()).sort((a, b) => a[0] - b[0]);
348
- for (const [, entry] of sortedEntries) {
349
- if (entry.id && entry.name) {
350
- toolCalls.push({
351
- id: entry.id,
352
- type: 'function',
353
- function: { name: entry.name, arguments: entry.args },
354
- });
355
- }
356
- }
357
- const assistant = {
358
- role,
359
- content: fullContent,
360
- ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
361
- ...(fullReasoning ? { reasoning_content: fullReasoning } : {}),
362
- };
363
- if (!fullContent && toolCalls.length === 0) {
364
- return null; // 空响应,由重试逻辑处理
365
- }
366
- return assistant;
367
- }
368
- catch (err) {
369
- console.warn(`[LLMCaller] LLM call failed (${isFallback ? 'fallback' : 'primary'}):`, err?.message || err);
370
- return null;
371
- }
372
- }
373
318
  }
374
319
  /** 辅助:异步延迟 */
375
320
  function sleep(ms) {