@agentscope-ai/agentscope 0.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.
Files changed (136) hide show
  1. package/dist/agent/index.d.mts +234 -0
  2. package/dist/agent/index.d.ts +234 -0
  3. package/dist/agent/index.js +1412 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/agent/index.mjs +1375 -0
  6. package/dist/agent/index.mjs.map +1 -0
  7. package/dist/base-BOx3UzOl.d.mts +41 -0
  8. package/dist/base-BoIps2RL.d.ts +41 -0
  9. package/dist/base-C7jwyH4Z.d.mts +52 -0
  10. package/dist/base-Cwi4bjze.d.ts +127 -0
  11. package/dist/base-DYlBMCy_.d.mts +127 -0
  12. package/dist/base-NX-knWOv.d.ts +52 -0
  13. package/dist/block-VsnHrllL.d.mts +48 -0
  14. package/dist/block-VsnHrllL.d.ts +48 -0
  15. package/dist/event/index.d.mts +181 -0
  16. package/dist/event/index.d.ts +181 -0
  17. package/dist/event/index.js +58 -0
  18. package/dist/event/index.js.map +1 -0
  19. package/dist/event/index.mjs +33 -0
  20. package/dist/event/index.mjs.map +1 -0
  21. package/dist/formatter/index.d.mts +187 -0
  22. package/dist/formatter/index.d.ts +187 -0
  23. package/dist/formatter/index.js +647 -0
  24. package/dist/formatter/index.js.map +1 -0
  25. package/dist/formatter/index.mjs +616 -0
  26. package/dist/formatter/index.mjs.map +1 -0
  27. package/dist/index-BTJDlKvQ.d.mts +195 -0
  28. package/dist/index-BcatlwXQ.d.ts +195 -0
  29. package/dist/index-CAxQAkiP.d.mts +21 -0
  30. package/dist/index-CAxQAkiP.d.ts +21 -0
  31. package/dist/mcp/index.d.mts +9 -0
  32. package/dist/mcp/index.d.ts +9 -0
  33. package/dist/mcp/index.js +432 -0
  34. package/dist/mcp/index.js.map +1 -0
  35. package/dist/mcp/index.mjs +408 -0
  36. package/dist/mcp/index.mjs.map +1 -0
  37. package/dist/message/index.d.mts +10 -0
  38. package/dist/message/index.d.ts +10 -0
  39. package/dist/message/index.js +67 -0
  40. package/dist/message/index.js.map +1 -0
  41. package/dist/message/index.mjs +37 -0
  42. package/dist/message/index.mjs.map +1 -0
  43. package/dist/message-CkN21KaY.d.mts +99 -0
  44. package/dist/message-CzLeTlua.d.ts +99 -0
  45. package/dist/model/index.d.mts +377 -0
  46. package/dist/model/index.d.ts +377 -0
  47. package/dist/model/index.js +1880 -0
  48. package/dist/model/index.js.map +1 -0
  49. package/dist/model/index.mjs +1849 -0
  50. package/dist/model/index.mjs.map +1 -0
  51. package/dist/storage/index.d.mts +68 -0
  52. package/dist/storage/index.d.ts +68 -0
  53. package/dist/storage/index.js +250 -0
  54. package/dist/storage/index.js.map +1 -0
  55. package/dist/storage/index.mjs +212 -0
  56. package/dist/storage/index.mjs.map +1 -0
  57. package/dist/tool/index.d.mts +311 -0
  58. package/dist/tool/index.d.ts +311 -0
  59. package/dist/tool/index.js +1494 -0
  60. package/dist/tool/index.js.map +1 -0
  61. package/dist/tool/index.mjs +1447 -0
  62. package/dist/tool/index.mjs.map +1 -0
  63. package/dist/toolkit-CEpulFi0.d.ts +99 -0
  64. package/dist/toolkit-CGEZSZPa.d.mts +99 -0
  65. package/jest.config.js +11 -0
  66. package/package.json +92 -0
  67. package/src/_utils/common.ts +104 -0
  68. package/src/_utils/index.ts +1 -0
  69. package/src/agent/agent-base.ts +0 -0
  70. package/src/agent/agent.test.ts +1028 -0
  71. package/src/agent/agent.ts +1032 -0
  72. package/src/agent/index.ts +2 -0
  73. package/src/agent/interfaces.ts +23 -0
  74. package/src/agent/test-compression.ts +72 -0
  75. package/src/event/index.ts +250 -0
  76. package/src/formatter/base.ts +133 -0
  77. package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
  78. package/src/formatter/dashscope-chat-formatter.ts +163 -0
  79. package/src/formatter/deepseek-chat-formatter.ts +130 -0
  80. package/src/formatter/index.ts +5 -0
  81. package/src/formatter/ollama-chat-formatter.ts +67 -0
  82. package/src/formatter/openai-chat-formatter.test.ts +263 -0
  83. package/src/formatter/openai-chat-formatter.ts +301 -0
  84. package/src/formatter/openai.md +767 -0
  85. package/src/mcp/base.ts +114 -0
  86. package/src/mcp/http.test.ts +303 -0
  87. package/src/mcp/http.ts +224 -0
  88. package/src/mcp/index.ts +2 -0
  89. package/src/mcp/stdio.test.ts +91 -0
  90. package/src/mcp/stdio.ts +119 -0
  91. package/src/message/block.ts +60 -0
  92. package/src/message/enums.ts +4 -0
  93. package/src/message/index.ts +12 -0
  94. package/src/message/message.test.ts +80 -0
  95. package/src/message/message.ts +131 -0
  96. package/src/model/base.ts +226 -0
  97. package/src/model/dashscope-model.test.ts +335 -0
  98. package/src/model/dashscope-model.ts +441 -0
  99. package/src/model/deepseek-model.test.ts +279 -0
  100. package/src/model/deepseek-model.ts +401 -0
  101. package/src/model/index.ts +7 -0
  102. package/src/model/ollama-model.test.ts +307 -0
  103. package/src/model/ollama-model.ts +356 -0
  104. package/src/model/openai-model.ts +327 -0
  105. package/src/model/response.ts +22 -0
  106. package/src/model/usage.ts +12 -0
  107. package/src/storage/base.ts +52 -0
  108. package/src/storage/file-system.test.ts +587 -0
  109. package/src/storage/file-system.ts +269 -0
  110. package/src/storage/index.ts +2 -0
  111. package/src/tool/base.ts +23 -0
  112. package/src/tool/bash.test.ts +174 -0
  113. package/src/tool/bash.ts +152 -0
  114. package/src/tool/edit.test.ts +83 -0
  115. package/src/tool/edit.ts +95 -0
  116. package/src/tool/glob.test.ts +63 -0
  117. package/src/tool/glob.ts +166 -0
  118. package/src/tool/grep.test.ts +74 -0
  119. package/src/tool/grep.ts +256 -0
  120. package/src/tool/index.ts +10 -0
  121. package/src/tool/read.test.ts +77 -0
  122. package/src/tool/read.ts +117 -0
  123. package/src/tool/response.ts +82 -0
  124. package/src/tool/task.test.ts +299 -0
  125. package/src/tool/task.ts +399 -0
  126. package/src/tool/toolkit.test.ts +636 -0
  127. package/src/tool/toolkit.ts +601 -0
  128. package/src/tool/write.test.ts +52 -0
  129. package/src/tool/write.ts +57 -0
  130. package/src/type/index.ts +52 -0
  131. package/tsconfig.build.json +4 -0
  132. package/tsconfig.cjs.json +11 -0
  133. package/tsconfig.esm.json +10 -0
  134. package/tsconfig.json +14 -0
  135. package/tsup.config.ts +20 -0
  136. package/typedoc.json +52 -0
@@ -0,0 +1,1849 @@
1
+ // src/message/message.ts
2
+ function createMsg({
3
+ name,
4
+ content,
5
+ role,
6
+ metadata = {},
7
+ id = crypto.randomUUID(),
8
+ timestamp = (/* @__PURE__ */ new Date()).toISOString(),
9
+ usage
10
+ }) {
11
+ return { id, name, role, content, metadata, timestamp, usage };
12
+ }
13
+ function getTextContent(msg, separator = "\n") {
14
+ const textBlocks = msg.content.filter((block) => block.type === "text");
15
+ if (textBlocks.length === 0) {
16
+ return null;
17
+ }
18
+ return textBlocks.map((block) => block.text).join(separator);
19
+ }
20
+ function getContentBlocks(msg, blockType) {
21
+ if (!blockType) return msg.content;
22
+ return msg.content.filter((block) => block.type === blockType);
23
+ }
24
+
25
+ // src/model/base.ts
26
+ var ChatModelBase = class {
27
+ modelName;
28
+ stream;
29
+ maxRetries;
30
+ fallbackModelName;
31
+ formatter;
32
+ /**
33
+ * Initializes a new instance of the ChatModelBase class.
34
+ *
35
+ * @param options - The chat model options, including model name, streaming option, max retries, fallback
36
+ * model name, and formatter.
37
+ *
38
+ * @param options.modelName
39
+ * @param options.stream
40
+ * @param options.maxRetries
41
+ * @param options.fallbackModelName
42
+ * @param options.formatter
43
+ */
44
+ constructor({
45
+ modelName,
46
+ stream,
47
+ maxRetries,
48
+ fallbackModelName,
49
+ formatter
50
+ }) {
51
+ this.modelName = modelName;
52
+ this.stream = stream ?? true;
53
+ this.maxRetries = maxRetries ?? 0;
54
+ this.fallbackModelName = fallbackModelName;
55
+ this.formatter = formatter;
56
+ }
57
+ /**
58
+ * Calls the chat model with the given messages.
59
+ * This is the main method to interact with the model.
60
+ *
61
+ * @param options - The chat model call options.
62
+ * @returns A promise that resolves to the model's response.
63
+ */
64
+ async call(options) {
65
+ let formattedMessages;
66
+ if (this.formatter) {
67
+ formattedMessages = await this.formatter.format({ msgs: options.messages });
68
+ } else {
69
+ formattedMessages = options.messages;
70
+ }
71
+ const requestOptions = {
72
+ ...options,
73
+ messages: formattedMessages
74
+ };
75
+ let lastError;
76
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
77
+ try {
78
+ return await this._callAPI(this.modelName, requestOptions);
79
+ } catch (error) {
80
+ lastError = error;
81
+ if (attempt === this.maxRetries) {
82
+ throw error;
83
+ } else {
84
+ console.log(
85
+ `Attempt ${attempt + 1} failed for model ${this.modelName}. Retrying...`
86
+ );
87
+ }
88
+ }
89
+ }
90
+ if (this.fallbackModelName) {
91
+ console.log(
92
+ `Using fallback model ${this.fallbackModelName} after ${this.maxRetries} failed attempts.`
93
+ );
94
+ return await this._callAPI(this.fallbackModelName, requestOptions);
95
+ }
96
+ throw lastError;
97
+ }
98
+ /**
99
+ * A heuristic method to count the number of the tokens
100
+ * Note the multimodal content is ignored in the token counting
101
+ * @param options
102
+ * @param options.messages
103
+ * @param options.tools
104
+ * @returns The estimated number of tokens in the input messages and tools.
105
+ */
106
+ async countTokens(options) {
107
+ let accText = "";
108
+ for (const msg of options.messages) {
109
+ accText += getTextContent(msg) || "";
110
+ }
111
+ if (options.tools) {
112
+ accText += JSON.stringify(options.tools);
113
+ }
114
+ const chineseMatches = accText.match(/[\u4e00-\u9fff\u3400-\u4dbf\u{20000}-\u{2a6df}]/gu)?.length ?? 0;
115
+ const englishMatches = accText.match(/[a-zA-Z]+/g)?.length ?? 0;
116
+ return chineseMatches * 2 + englishMatches * 1.5;
117
+ }
118
+ /**
119
+ * A default implementation of the structured call method. For those supporting structured output, the model should
120
+ * override this method.
121
+ * @param options
122
+ * @returns The structured response from the model, which should conform to the provided Zod schema.
123
+ */
124
+ async callStructured(options) {
125
+ const toolSchema = {
126
+ type: "function",
127
+ function: {
128
+ name: "GenerateStructuredResponse",
129
+ description: "Generate required structured response by this toll.",
130
+ parameters: options.schema.toJSONSchema({
131
+ target: "openapi-3.0"
132
+ })
133
+ }
134
+ };
135
+ const res = await this.call({
136
+ messages: options.messages,
137
+ tools: [toolSchema],
138
+ toolChoice: "GenerateStructuredResponse"
139
+ });
140
+ let completedResponse;
141
+ if (this.stream) {
142
+ while (true) {
143
+ const { value, done } = await res.next();
144
+ if (done) {
145
+ completedResponse = value;
146
+ break;
147
+ }
148
+ }
149
+ } else {
150
+ completedResponse = res;
151
+ }
152
+ for (const block of completedResponse.content) {
153
+ if (block.type === "tool_call" && block.name === "GenerateStructuredResponse") {
154
+ const structuredContent = JSON.parse(block.input);
155
+ return {
156
+ ...completedResponse,
157
+ content: structuredContent,
158
+ type: "structured"
159
+ };
160
+ }
161
+ }
162
+ throw new Error(`Failed to generate the structured response`);
163
+ }
164
+ };
165
+
166
+ // src/_utils/common.ts
167
+ import { jsonrepair } from "jsonrepair";
168
+ async function* _parseStreamedResponse(response) {
169
+ const reader = response.body?.getReader();
170
+ if (!reader) {
171
+ throw new Error("Failed to get reader from response body for streaming.");
172
+ }
173
+ const decoder = new TextDecoder();
174
+ let buffer = "";
175
+ try {
176
+ while (true) {
177
+ const { done, value } = await reader.read();
178
+ if (done) break;
179
+ buffer += decoder.decode(value, { stream: true });
180
+ const lines = buffer.split("\n");
181
+ buffer = lines.pop() || "";
182
+ for (const line of lines) {
183
+ const trimmedLine = line.trim();
184
+ if (!trimmedLine || trimmedLine.startsWith(":")) {
185
+ continue;
186
+ }
187
+ if (trimmedLine.startsWith("data:")) {
188
+ const jsonStr = trimmedLine.slice(5).trim();
189
+ if (jsonStr === "[DONE]") {
190
+ break;
191
+ }
192
+ try {
193
+ const json = JSON.parse(jsonStr);
194
+ yield json;
195
+ } catch (e) {
196
+ console.error("Failed to parse JSON:", e);
197
+ throw new Error(`Failed to parse JSON from stream: ${jsonStr}`);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ } finally {
203
+ reader.releaseLock();
204
+ }
205
+ }
206
+
207
+ // src/formatter/base.ts
208
+ var FormatterBase = class {
209
+ /**
210
+ * Convert the tool output to string format for the LLM APIs that only accept text input. If
211
+ * `promoteMultimodalToolResult` is true, the multimodal content will be promoted to be a user message with
212
+ * "<system-info></system-info>" tags. Otherwise, the multimodal content will be saved to a storage and a URL link
213
+ * will be provided in the text output.
214
+ *
215
+ * @param output - The tool output, which can be a string or an array of content blocks.
216
+ * @param promoteMultimodalToolResult - Whether to promote the multimodal content to the prompt messages.
217
+ * @returns An object containing the text output and an optional promoted message.
218
+ */
219
+ convertToolOutputToString(output, promoteMultimodalToolResult) {
220
+ if (typeof output === "string") return { text: output, promotedMsg: null };
221
+ let textualOutput = [];
222
+ const promotedData = [];
223
+ for (const block of output) {
224
+ switch (block.type) {
225
+ case "text":
226
+ textualOutput.push(block.text);
227
+ break;
228
+ default:
229
+ const type = block.source.mediaType.split("/")[0];
230
+ if (type !== "image" && type !== "audio" && type !== "video") {
231
+ console.log(
232
+ `Unsupported media type '${block.source.mediaType}' in tool output. Only image, audio and video are supported.`
233
+ );
234
+ break;
235
+ }
236
+ if (block.source.type === "url") {
237
+ textualOutput.push(
238
+ `<system-info>One returned ${type} can be found at: ${block.source.url}</system-info>`
239
+ );
240
+ } else {
241
+ const shouldPromote = promoteMultimodalToolResult === true || typeof promoteMultimodalToolResult === "object" && promoteMultimodalToolResult[type];
242
+ if (shouldPromote) {
243
+ const dataID = Math.random().toString(36).substring(2, 10);
244
+ textualOutput.push(
245
+ `<system-info>One returned ${type} is embedded with ID '${dataID}' and will be attached within '<system-info></system-info>' tags later.</system-info>`
246
+ );
247
+ promotedData.push({ id: dataID, block });
248
+ } else {
249
+ textualOutput.push(`The returned ${block.type} is stored locally.`);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ const promotedBlocks = [];
255
+ promotedData.forEach(({ id, block }) => {
256
+ const type = block.source.mediaType.split("/")[0];
257
+ promotedBlocks.push({
258
+ id: crypto.randomUUID(),
259
+ type: "text",
260
+ text: `<${type}_data id='${id}'>`
261
+ });
262
+ promotedBlocks.push(block);
263
+ promotedBlocks.push({
264
+ id: crypto.randomUUID(),
265
+ type: "text",
266
+ text: `</${type}_data>
267
+ `
268
+ });
269
+ });
270
+ if (promotedBlocks.length > 0) {
271
+ const prefix = "<system-info>The multimodal contents returned from the tool call are as follows:\n";
272
+ if (promotedBlocks[0].type === "text") {
273
+ promotedBlocks[0].text = `${prefix}${promotedBlocks[0].text}`;
274
+ } else {
275
+ promotedBlocks.unshift({
276
+ id: crypto.randomUUID(),
277
+ type: "text",
278
+ text: `${prefix}`
279
+ });
280
+ }
281
+ const lastBlock = promotedBlocks[promotedBlocks.length - 1];
282
+ if (lastBlock.type === "text") {
283
+ promotedBlocks[promotedBlocks.length - 1] = {
284
+ id: crypto.randomUUID(),
285
+ type: "text",
286
+ text: `${lastBlock.text}</system-info>`
287
+ };
288
+ } else {
289
+ promotedBlocks.push({
290
+ id: crypto.randomUUID(),
291
+ type: "text",
292
+ text: `</system-info>`
293
+ });
294
+ }
295
+ }
296
+ return {
297
+ text: textualOutput.join("\n"),
298
+ promotedMsg: createMsg({ name: "user", content: promotedBlocks, role: "user" })
299
+ };
300
+ }
301
+ };
302
+
303
+ // src/formatter/dashscope-chat-formatter.ts
304
+ var DashScopeChatFormatter = class extends FormatterBase {
305
+ promoteMultimodalToolResult;
306
+ /**
307
+ * Initialize a DashScopeChatFormatter instance.
308
+ *
309
+ * @param promoteMultimodalToolResult - Since DashScope API doesn't support multimodal tool outputs, this option
310
+ * indicates whether to promote the multimodal tool results to the prompt messages, so that LLMs can see them.
311
+ * Note you should ensure your model supports the corresponding modalities.
312
+ * @param promoteMultimodalToolResult.promoteMultimodalToolResult
313
+ */
314
+ constructor({ promoteMultimodalToolResult = false } = {}) {
315
+ super();
316
+ this.promoteMultimodalToolResult = promoteMultimodalToolResult;
317
+ }
318
+ /**
319
+ * Format the input message objects into the required format by DashScope API.
320
+ *
321
+ * @param msgs - An array of Msg instances to be formatted.
322
+ * @param msgs.msgs
323
+ * @returns A promise that resolves to an array of formatted message objects.
324
+ */
325
+ async format({ msgs }) {
326
+ const formattedMsgs = [];
327
+ let index = 0;
328
+ while (index < msgs.length) {
329
+ const msg = msgs[index];
330
+ const formattedMsg = {
331
+ role: msg.role,
332
+ content: []
333
+ };
334
+ const cachedMsgs = [];
335
+ for (const block of getContentBlocks(msg)) {
336
+ switch (block.type) {
337
+ case "text":
338
+ formattedMsg.content.push(this._formatTextBlock(block));
339
+ break;
340
+ case "thinking":
341
+ break;
342
+ case "tool_call":
343
+ if (!formattedMsg.tool_calls) {
344
+ formattedMsg.tool_calls = [];
345
+ }
346
+ formattedMsg.tool_calls.push({
347
+ id: block.id,
348
+ type: "function",
349
+ function: {
350
+ name: block.name,
351
+ arguments: block.input
352
+ }
353
+ });
354
+ break;
355
+ case "tool_result":
356
+ const formattedToolResult = this.convertToolOutputToString(
357
+ block.output,
358
+ this.promoteMultimodalToolResult
359
+ );
360
+ cachedMsgs.push({
361
+ role: "tool",
362
+ tool_call_id: block.id,
363
+ name: block.name,
364
+ content: formattedToolResult.text
365
+ });
366
+ if (formattedToolResult.promotedMsg) {
367
+ msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
368
+ }
369
+ break;
370
+ case "data":
371
+ formattedMsg.content.push(...this._formatMultimodalBlock(block));
372
+ break;
373
+ }
374
+ }
375
+ if (formattedMsg.content.length > 0 || formattedMsg.tool_calls) {
376
+ formattedMsgs.push(formattedMsg);
377
+ }
378
+ if (cachedMsgs.length > 0) {
379
+ formattedMsgs.push(...cachedMsgs);
380
+ }
381
+ index++;
382
+ }
383
+ return formattedMsgs;
384
+ }
385
+ /**
386
+ * Format a text content block into the required format.
387
+ *
388
+ * @param block - The text content block to format.
389
+ * @returns An object representing the formatted text content.
390
+ */
391
+ _formatTextBlock(block) {
392
+ return { text: block.text };
393
+ }
394
+ /**
395
+ * Format a multimodal data block into the required format.
396
+ * In DashScope API, the local file paths should be prefixed with "file://". URLs are kept unchanged.
397
+ *
398
+ * @param block - The multimodal content block to format.
399
+ * @returns An object representing the formatted multimodal content.
400
+ */
401
+ _formatMultimodalBlock(block) {
402
+ const type = block.source.mediaType.split("/")[0];
403
+ if (!["image", "audio", "video"].includes(type)) {
404
+ console.log(
405
+ `Skip unsupported media type ${block.source.mediaType} in DashScopeChatFormatter. Only image, audio and video are supported.`
406
+ );
407
+ return [];
408
+ }
409
+ if (block.source.type === "url") {
410
+ return [{ [type]: block.source.url }];
411
+ }
412
+ return [
413
+ {
414
+ [type]: `data:${block.source.mediaType};base64,${block.source.data}`
415
+ }
416
+ ];
417
+ }
418
+ };
419
+
420
+ // src/formatter/deepseek-chat-formatter.ts
421
+ var DeepSeekChatFormatter = class extends FormatterBase {
422
+ promoteMultimodalToolResult;
423
+ /**
424
+ * Initializes a new instance of the DeepSeekChatFormatter class.
425
+ * @param root0
426
+ * @param root0.promoteMultimodalToolResult
427
+ */
428
+ constructor({ promoteMultimodalToolResult = false } = {}) {
429
+ super();
430
+ this.promoteMultimodalToolResult = promoteMultimodalToolResult;
431
+ }
432
+ /**
433
+ * Format the input messages into the structure expected by DeepSeek Chat Completions API.
434
+ * @param root0
435
+ * @param root0.msgs
436
+ * @returns An array of formatted message objects ready to be sent to the DeepSeek API.
437
+ */
438
+ async format({ msgs }) {
439
+ const formattedMsgs = [];
440
+ let index = 0;
441
+ while (index < msgs.length) {
442
+ const msg = msgs[index];
443
+ const formattedMsg = {
444
+ role: msg.role,
445
+ name: msg.name,
446
+ content: null
447
+ };
448
+ const content = [];
449
+ const cachedMsgs = [];
450
+ for (const block of getContentBlocks(msg)) {
451
+ switch (block.type) {
452
+ case "text":
453
+ content.push({
454
+ type: "text",
455
+ text: block.text
456
+ });
457
+ break;
458
+ case "thinking":
459
+ break;
460
+ case "tool_call":
461
+ if (!formattedMsg.tool_calls) {
462
+ formattedMsg.tool_calls = [];
463
+ }
464
+ formattedMsg.tool_calls.push({
465
+ id: block.id,
466
+ type: "function",
467
+ function: {
468
+ name: block.name,
469
+ arguments: block.input
470
+ }
471
+ });
472
+ break;
473
+ case "tool_result":
474
+ const formattedToolResult = this.convertToolOutputToString(
475
+ block.output,
476
+ this.promoteMultimodalToolResult
477
+ );
478
+ cachedMsgs.push({
479
+ role: "tool",
480
+ tool_call_id: block.id,
481
+ name: block.name,
482
+ content: formattedToolResult.text
483
+ });
484
+ if (formattedToolResult.promotedMsg?.content.length) {
485
+ msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
486
+ }
487
+ break;
488
+ case "data":
489
+ console.warn(
490
+ `DeepSeek models don't support multimodal data for now (2026-03), skip the data block in message content.`
491
+ );
492
+ break;
493
+ }
494
+ }
495
+ if (content.length > 0) {
496
+ formattedMsg.content = content;
497
+ }
498
+ if (formattedMsg.content || formattedMsg.tool_calls) {
499
+ formattedMsgs.push(formattedMsg);
500
+ }
501
+ if (cachedMsgs.length > 0) {
502
+ formattedMsgs.push(...cachedMsgs);
503
+ }
504
+ index++;
505
+ }
506
+ return formattedMsgs;
507
+ }
508
+ };
509
+
510
+ // src/formatter/ollama-chat-formatter.ts
511
+ var OllamaChatFormatter = class extends FormatterBase {
512
+ // eslint-disable-next-line jsdoc/require-returns
513
+ /**
514
+ * Format messages for Ollama API
515
+ * @param root0
516
+ * @param root0.msgs
517
+ */
518
+ async format({ msgs }) {
519
+ const formattedMsgs = [];
520
+ for (const msg of msgs) {
521
+ const formattedMsg = {
522
+ role: msg.role,
523
+ content: ""
524
+ };
525
+ const textContent = getTextContent(msg);
526
+ if (textContent) {
527
+ formattedMsg.content = textContent;
528
+ }
529
+ const toolCalls = getContentBlocks(msg, "tool_call");
530
+ if (toolCalls.length > 0) {
531
+ formattedMsg.tool_calls = toolCalls.map((toolCall) => ({
532
+ function: {
533
+ name: toolCall.name,
534
+ arguments: JSON.parse(toolCall.input)
535
+ }
536
+ }));
537
+ }
538
+ const toolResults = getContentBlocks(msg, "tool_result");
539
+ for (const toolResult of toolResults) {
540
+ const resultText = this.convertToolOutputToString(toolResult.output, false);
541
+ formattedMsgs.push({
542
+ role: "tool",
543
+ content: resultText.text
544
+ });
545
+ }
546
+ if (formattedMsg.content || formattedMsg.tool_calls) {
547
+ formattedMsgs.push(formattedMsg);
548
+ }
549
+ }
550
+ return formattedMsgs;
551
+ }
552
+ };
553
+
554
+ // src/formatter/openai-chat-formatter.ts
555
+ import { existsSync } from "fs";
556
+ import { readFile } from "fs/promises";
557
+ import { extname } from "path";
558
+ import { fileURLToPath } from "url";
559
+ var OpenAIChatFormatter = class extends FormatterBase {
560
+ promoteMultimodalToolResult;
561
+ /**
562
+ * Initializes a new instance of the OpenAIChatFormatter class.
563
+ * @param root0
564
+ * @param root0.promoteMultimodalToolResult
565
+ */
566
+ constructor({ promoteMultimodalToolResult = false } = {}) {
567
+ super();
568
+ this.promoteMultimodalToolResult = promoteMultimodalToolResult;
569
+ }
570
+ /**
571
+ * Format the input messages into OpenAI Chat Completions message format.
572
+ * @param root0
573
+ * @param root0.msgs
574
+ * @returns An array of formatted messages compatible with OpenAI Chat Completions API.
575
+ */
576
+ async format({ msgs }) {
577
+ const formattedMsgs = [];
578
+ let index = 0;
579
+ while (index < msgs.length) {
580
+ const msg = msgs[index];
581
+ const formattedMsg = {
582
+ role: msg.role,
583
+ name: msg.name,
584
+ content: null
585
+ };
586
+ const content = [];
587
+ const cachedMsgs = [];
588
+ for (const block of getContentBlocks(msg)) {
589
+ switch (block.type) {
590
+ case "text":
591
+ content.push(this._formatTextBlock(block));
592
+ break;
593
+ case "thinking":
594
+ break;
595
+ case "tool_call":
596
+ if (!formattedMsg.tool_calls) {
597
+ formattedMsg.tool_calls = [];
598
+ }
599
+ formattedMsg.tool_calls.push({
600
+ id: block.id,
601
+ type: "function",
602
+ function: {
603
+ name: block.name,
604
+ arguments: block.input
605
+ }
606
+ });
607
+ break;
608
+ case "tool_result":
609
+ const formattedToolResult = this.convertToolOutputToString(
610
+ block.output,
611
+ this.promoteMultimodalToolResult
612
+ );
613
+ cachedMsgs.push({
614
+ role: "tool",
615
+ tool_call_id: block.id,
616
+ name: block.name,
617
+ content: formattedToolResult.text
618
+ });
619
+ if (formattedToolResult.promotedMsg?.content.length) {
620
+ msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
621
+ }
622
+ break;
623
+ case "data":
624
+ content.push(
625
+ ...await this._formatMultimodalBlock({ block, role: msg.role })
626
+ );
627
+ break;
628
+ }
629
+ }
630
+ if (content.length > 0) {
631
+ formattedMsg.content = content;
632
+ }
633
+ if (formattedMsg.content || formattedMsg.tool_calls) {
634
+ formattedMsgs.push(formattedMsg);
635
+ }
636
+ if (cachedMsgs.length > 0) {
637
+ formattedMsgs.push(...cachedMsgs);
638
+ }
639
+ index++;
640
+ }
641
+ return formattedMsgs;
642
+ }
643
+ /**
644
+ * Format a text block into OpenAI Chat Completions message content format.
645
+ * @param block
646
+ * @returns An object representing the formatted text block.
647
+ */
648
+ _formatTextBlock(block) {
649
+ return {
650
+ type: "text",
651
+ text: block.text
652
+ };
653
+ }
654
+ /**
655
+ * Format a multimodal data block into OpenAI Chat Completions message content format.
656
+ * @param root0
657
+ * @param root0.block
658
+ * @param root0.role
659
+ * @returns The formatted content blocks
660
+ */
661
+ async _formatMultimodalBlock({
662
+ block,
663
+ role
664
+ }) {
665
+ const type = block.source.mediaType.split("/")[0];
666
+ if (type === "image") {
667
+ return [
668
+ {
669
+ type: "image_url",
670
+ image_url: {
671
+ url: await this._toOpenAIImageURL(block)
672
+ }
673
+ }
674
+ ];
675
+ }
676
+ if (type === "audio") {
677
+ if (role === "assistant") {
678
+ return [];
679
+ }
680
+ return [
681
+ {
682
+ type: "input_audio",
683
+ input_audio: await this._toOpenAIAudioData(block)
684
+ }
685
+ ];
686
+ }
687
+ console.log(
688
+ `Skip unsupported media type ${block.source.mediaType} in OpenAIChatFormatter. Only image and audio are supported.`
689
+ );
690
+ return [];
691
+ }
692
+ /**
693
+ * Convert the data block to an OpenAI compatible image URL.
694
+ * @param block
695
+ * @returns A promise that resolves to a string representing the image URL in a format compatible with OpenAI Chat Completions API.
696
+ */
697
+ async _toOpenAIImageURL(block) {
698
+ if (block.source.type === "base64") {
699
+ return `data:${block.source.mediaType};base64,${block.source.data}`;
700
+ }
701
+ const sourceUrl = block.source.url;
702
+ if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://")) {
703
+ return sourceUrl;
704
+ }
705
+ if (sourceUrl.startsWith("data:")) {
706
+ return sourceUrl;
707
+ }
708
+ const localPath = this._toLocalPath(sourceUrl);
709
+ if (!localPath || !existsSync(localPath)) {
710
+ throw new Error(`Image path not found: ${sourceUrl}`);
711
+ }
712
+ const ext = extname(localPath).toLowerCase();
713
+ const supportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
714
+ if (!supportedImageExtensions.includes(ext)) {
715
+ throw new TypeError(
716
+ `Unsupported image extension: ${ext}. Supported: ${supportedImageExtensions.join(", ")}`
717
+ );
718
+ }
719
+ const file = await readFile(localPath);
720
+ const mime = block.source.mediaType || `image/${ext.slice(1)}`;
721
+ return `data:${mime};base64,${file.toString("base64")}`;
722
+ }
723
+ /**
724
+ * Converts a data block to OpenAI compatible audio data format.
725
+ *
726
+ * @param block - The data block containing audio information.
727
+ * @returns A promise that resolves to an object with audio data and format.
728
+ */
729
+ async _toOpenAIAudioData(block) {
730
+ const supportedMediaTypes = /* @__PURE__ */ new Map([
731
+ ["audio/wav", "wav"],
732
+ ["audio/mp3", "mp3"],
733
+ ["audio/mpeg", "mp3"]
734
+ ]);
735
+ if (block.source.type === "base64") {
736
+ const format2 = supportedMediaTypes.get(block.source.mediaType);
737
+ if (!format2) {
738
+ throw new TypeError(
739
+ `Unsupported audio media type: ${block.source.mediaType}, only audio/wav and audio/mp3 are supported.`
740
+ );
741
+ }
742
+ return { data: block.source.data, format: format2 };
743
+ }
744
+ const sourceUrl = block.source.url;
745
+ const localPath = this._toLocalPath(sourceUrl);
746
+ let data;
747
+ if (localPath && existsSync(localPath)) {
748
+ const file = await readFile(localPath);
749
+ data = file.toString("base64");
750
+ } else if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://")) {
751
+ const response = await fetch(sourceUrl);
752
+ if (!response.ok) {
753
+ throw new Error(
754
+ `Failed to fetch audio from URL: ${sourceUrl} (${response.status})`
755
+ );
756
+ }
757
+ const arr = await response.arrayBuffer();
758
+ data = Buffer.from(arr).toString("base64");
759
+ } else {
760
+ throw new Error(
761
+ `Unsupported audio source: ${sourceUrl}, it should be a local file path, file URL, or an HTTP URL.`
762
+ );
763
+ }
764
+ const ext = extname(localPath || sourceUrl).toLowerCase();
765
+ const extToFormat = /* @__PURE__ */ new Map([
766
+ [".wav", "wav"],
767
+ [".mp3", "mp3"]
768
+ ]);
769
+ const format = extToFormat.get(ext);
770
+ if (!format) {
771
+ throw new TypeError(`Unsupported audio extension: ${ext}, wav and mp3 are supported.`);
772
+ }
773
+ return { data, format };
774
+ }
775
+ /**
776
+ * Converts a URL or path to a local file path.
777
+ *
778
+ * @param urlOrPath - The URL or path to convert.
779
+ * @returns The local file path, or null if not a local path.
780
+ */
781
+ _toLocalPath(urlOrPath) {
782
+ if (urlOrPath.startsWith("file://")) {
783
+ return fileURLToPath(urlOrPath);
784
+ }
785
+ if (!urlOrPath.includes("://")) {
786
+ return urlOrPath;
787
+ }
788
+ return null;
789
+ }
790
+ };
791
+
792
+ // src/model/dashscope-model.ts
793
+ var DashScopeChatModel = class extends ChatModelBase {
794
+ apiURL;
795
+ apiKey;
796
+ presetGenParams;
797
+ presetHeaders;
798
+ thinkingConfig;
799
+ /**
800
+ * Initializes a new instance of the DashScopeChatModel class.
801
+ *
802
+ * @param options - The DashScope chat model options.
803
+ * @param options.modelName - The name of the model to use.
804
+ * @param options.apiKey - The API key for authentication.
805
+ * @param options.stream - Whether to use streaming responses. Default is true.
806
+ * @param options.thinkingConfig - The thinking configuration for DashScope models, including whether to enable thinking and the thinking budget.
807
+ * @param options.maxRetries - The maximum number of retries for failed requests. Default is 3.
808
+ * @param options.fallbackModelName - The fallback model name to use if the primary model fails.
809
+ * @param options.presetGenParams - Preset generation parameters to include in each request.
810
+ * @param options.presetHeaders - Preset headers that will be included in each request.
811
+ * @param options.multimodal - Whether the model is multimodal or not, this will decide the default API endpoint. If not provided, it will be inferred from the model name.
812
+ * @param options.formatter - An optional custom formatter. If not provided, a default DashScopeChatFormatter will be used.
813
+ */
814
+ constructor({
815
+ modelName,
816
+ apiKey,
817
+ stream = true,
818
+ thinkingConfig,
819
+ maxRetries = 0,
820
+ fallbackModelName,
821
+ presetGenParams,
822
+ presetHeaders,
823
+ multimodal,
824
+ formatter
825
+ }) {
826
+ const defaultFormatter = formatter || new DashScopeChatFormatter();
827
+ super({
828
+ modelName,
829
+ stream,
830
+ maxRetries,
831
+ fallbackModelName,
832
+ formatter: defaultFormatter
833
+ });
834
+ this.apiKey = apiKey;
835
+ this.thinkingConfig = thinkingConfig;
836
+ this.presetGenParams = presetGenParams;
837
+ this.presetHeaders = presetHeaders;
838
+ if (multimodal === void 0) {
839
+ multimodal = modelName.includes("vl") || modelName.includes("qwen3.5-plus") || modelName.includes("qvq");
840
+ }
841
+ this.apiURL = multimodal ? "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" : "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
842
+ }
843
+ /**
844
+ * Calls the DashScope API with the given parameters.
845
+ *
846
+ * @param modelName - The name of the model to use.
847
+ * @param options - The chat model options.
848
+ * @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
849
+ */
850
+ async _callAPI(modelName, options) {
851
+ const data = {
852
+ model: modelName,
853
+ input: {
854
+ messages: options.messages
855
+ },
856
+ parameters: {
857
+ result_format: "message",
858
+ tools: this._formatToolSchemas(options.tools),
859
+ toolChoice: this._formatToolChoice(options.toolChoice),
860
+ enable_thinking: this.thinkingConfig?.enableThinking ?? false,
861
+ ...this.thinkingConfig?.thinkingBudget !== void 0 && {
862
+ thinking_budget: this.thinkingConfig.thinkingBudget
863
+ },
864
+ ...this.presetGenParams ?? {},
865
+ incremental_output: true
866
+ }
867
+ };
868
+ const headers = {
869
+ Authorization: `Bearer ${this.apiKey}`,
870
+ "Content-Type": "application/json",
871
+ ...this.presetHeaders
872
+ };
873
+ if (this.stream) {
874
+ headers["X-DashScope-SSE"] = "enable";
875
+ }
876
+ const startTime = Date.now();
877
+ const response = await fetch(this.apiURL, {
878
+ method: "POST",
879
+ headers,
880
+ body: JSON.stringify(data)
881
+ });
882
+ if (!response.ok) {
883
+ throw new Error(
884
+ `DashScope API request failed with status ${response.status}: ${await response.text()}`
885
+ );
886
+ }
887
+ if (this.stream) {
888
+ return this._parseDashScopeStreamedResponse(response, startTime);
889
+ }
890
+ const blocks = [];
891
+ const res = await response.json();
892
+ const choice = res.output.choices[0];
893
+ if (choice.message.reasoning_content) {
894
+ blocks.push({
895
+ type: "thinking",
896
+ thinking: choice.message.reasoning_content,
897
+ id: crypto.randomUUID()
898
+ });
899
+ }
900
+ if (choice.message.content) {
901
+ blocks.push({ type: "text", text: choice.message.content, id: crypto.randomUUID() });
902
+ }
903
+ if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
904
+ choice.message.tool_calls.forEach((toolCall) => {
905
+ if ("id" in toolCall && "function" in toolCall && typeof toolCall.function === "object" && toolCall.function && "name" in toolCall.function && "arguments" in toolCall.function) {
906
+ const inputString = String(toolCall.function.arguments);
907
+ blocks.push({
908
+ type: "tool_call",
909
+ id: String(toolCall.id),
910
+ name: String(toolCall.function.name),
911
+ input: inputString
912
+ });
913
+ }
914
+ });
915
+ }
916
+ const usage = res.usage ? {
917
+ type: "chat_usage",
918
+ inputTokens: res.usage.input_tokens || 0,
919
+ outputTokens: res.usage.output_tokens || 0,
920
+ time: (Date.now() - startTime) / 1e3
921
+ } : void 0;
922
+ return {
923
+ type: "chat",
924
+ id: crypto.randomUUID(),
925
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
926
+ content: blocks,
927
+ usage
928
+ };
929
+ }
930
+ /**
931
+ * The method to format the tool choice parameter.
932
+ *
933
+ * @param toolChoice - The tool choice option.
934
+ * @returns The formatted tool choice.
935
+ */
936
+ _formatToolChoice(toolChoice) {
937
+ if (toolChoice) {
938
+ if (toolChoice === "auto") return "auto";
939
+ if (toolChoice === "none") return "none";
940
+ return {
941
+ type: "function",
942
+ function: {
943
+ name: toolChoice
944
+ }
945
+ };
946
+ }
947
+ return "auto";
948
+ }
949
+ /**
950
+ * Parses a streamed response from DashScope API specifically for chat responses.
951
+ * An async generator that yields delta ChatResponse objects as they are received.
952
+ *
953
+ * @param response - The fetch response object.
954
+ * @param startTime - The start time of the request for usage calculation.
955
+ * @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
956
+ */
957
+ async *_parseDashScopeStreamedResponse(response, startTime) {
958
+ const asyncGenerator = _parseStreamedResponse(response);
959
+ let accText = "";
960
+ let accThinking = "";
961
+ const accToolInputs = /* @__PURE__ */ new Map();
962
+ const toolCallMeta = /* @__PURE__ */ new Map();
963
+ let lastUsage = void 0;
964
+ for await (const jsonObj of asyncGenerator) {
965
+ if (jsonObj.output && jsonObj.output.choices) {
966
+ const choice = jsonObj.output.choices[0];
967
+ let deltaText = "";
968
+ let deltaThinking = "";
969
+ const deltaToolCalls = /* @__PURE__ */ new Map();
970
+ const content = choice.message?.content;
971
+ if (content) {
972
+ if (typeof content === "string") {
973
+ deltaText = content;
974
+ } else if (Array.isArray(content)) {
975
+ for (const block of content) {
976
+ if (block.text) {
977
+ deltaText += block.text;
978
+ }
979
+ }
980
+ }
981
+ accText += deltaText;
982
+ }
983
+ if (choice.message?.reasoning_content) {
984
+ deltaThinking = choice.message.reasoning_content;
985
+ accThinking += deltaThinking;
986
+ }
987
+ if (choice.message?.tool_calls) {
988
+ choice.message.tool_calls.forEach((toolCall) => {
989
+ const index = toolCall.index.toString();
990
+ if (!toolCallMeta.has(index)) {
991
+ toolCallMeta.set(index, { id: "", name: "" });
992
+ }
993
+ if (!accToolInputs.has(index)) {
994
+ accToolInputs.set(index, "");
995
+ }
996
+ if (toolCall.id) {
997
+ toolCallMeta.get(index).id = toolCall.id;
998
+ }
999
+ if (toolCall.function?.name) {
1000
+ toolCallMeta.get(index).name = toolCall.function.name;
1001
+ }
1002
+ if (toolCall.function?.arguments) {
1003
+ const deltaArgs = toolCall.function.arguments;
1004
+ accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
1005
+ const meta = toolCallMeta.get(index);
1006
+ deltaToolCalls.set(index, {
1007
+ type: "tool_call",
1008
+ id: meta.id,
1009
+ name: meta.name,
1010
+ input: deltaArgs
1011
+ });
1012
+ }
1013
+ });
1014
+ }
1015
+ const deltaBlocks = this._dataToBlocks(deltaText, deltaThinking, deltaToolCalls);
1016
+ lastUsage = jsonObj.usage ? {
1017
+ type: "chat_usage",
1018
+ inputTokens: jsonObj.usage.input_tokens || 0,
1019
+ outputTokens: jsonObj.usage.output_tokens || 0,
1020
+ time: (Date.now() - startTime) / 1e3
1021
+ } : void 0;
1022
+ yield {
1023
+ type: "chat",
1024
+ id: crypto.randomUUID(),
1025
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1026
+ content: deltaBlocks,
1027
+ usage: lastUsage
1028
+ };
1029
+ }
1030
+ }
1031
+ const finalToolCalls = /* @__PURE__ */ new Map();
1032
+ toolCallMeta.forEach((meta, index) => {
1033
+ finalToolCalls.set(index, {
1034
+ type: "tool_call",
1035
+ id: meta.id,
1036
+ name: meta.name,
1037
+ input: accToolInputs.get(index) || "{}"
1038
+ });
1039
+ });
1040
+ const blocks = this._dataToBlocks(accText, accThinking, finalToolCalls);
1041
+ return {
1042
+ type: "chat",
1043
+ id: crypto.randomUUID(),
1044
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1045
+ content: blocks,
1046
+ usage: lastUsage
1047
+ };
1048
+ }
1049
+ /**
1050
+ * Convert data into blocks
1051
+ *
1052
+ * @param text - The text response from the llm API
1053
+ * @param thinking - The thinking response
1054
+ * @param toolCalls - The tool calls
1055
+ * @returns An array of blocks
1056
+ */
1057
+ _dataToBlocks(text, thinking, toolCalls) {
1058
+ const blocks = [];
1059
+ if (thinking) {
1060
+ blocks.push({ type: "thinking", thinking, id: crypto.randomUUID() });
1061
+ }
1062
+ if (text) {
1063
+ blocks.push({ type: "text", text, id: crypto.randomUUID() });
1064
+ }
1065
+ if (toolCalls.size > 0) {
1066
+ toolCalls.forEach((value) => {
1067
+ blocks.push(value);
1068
+ });
1069
+ }
1070
+ return blocks;
1071
+ }
1072
+ /**
1073
+ * Format the tool schemas to the expected API format.
1074
+ * @param tools
1075
+ * @returns The formatted tool schemas.
1076
+ */
1077
+ _formatToolSchemas(tools) {
1078
+ return tools || [];
1079
+ }
1080
+ };
1081
+
1082
+ // src/model/deepseek-model.ts
1083
+ var DeepSeekChatModel = class extends ChatModelBase {
1084
+ apiURL;
1085
+ apiKey;
1086
+ presetGenParams;
1087
+ presetHeaders;
1088
+ thinkingConfig;
1089
+ /**
1090
+ * Initializes a new instance of the DeepSeekChatModel class.
1091
+ *
1092
+ * @param options - The DeepSeek chat model options.
1093
+ * @param options.modelName - The name of the model to use.
1094
+ * @param options.apiKey - The API key for authentication.
1095
+ * @param options.stream - Whether to use streaming responses. Default is true.
1096
+ * @param options.thinkingConfig - Thinking configuration.
1097
+ * @param options.maxRetries - The maximum number of retries for failed requests. Default is 0.
1098
+ * @param options.fallbackModelName - The fallback model name to use if the primary model fails.
1099
+ * @param options.presetGenParams - Preset generation parameters to include in each request.
1100
+ * @param options.presetHeaders - Preset headers that will be included in each request.
1101
+ * @param options.formatter
1102
+ */
1103
+ constructor({
1104
+ modelName,
1105
+ apiKey,
1106
+ stream = true,
1107
+ thinkingConfig,
1108
+ maxRetries = 0,
1109
+ fallbackModelName,
1110
+ presetGenParams,
1111
+ presetHeaders,
1112
+ formatter
1113
+ }) {
1114
+ const defaultFormatter = formatter || new DeepSeekChatFormatter();
1115
+ super({
1116
+ modelName,
1117
+ stream,
1118
+ maxRetries,
1119
+ fallbackModelName,
1120
+ formatter: defaultFormatter
1121
+ });
1122
+ this.apiKey = apiKey;
1123
+ this.thinkingConfig = thinkingConfig || { enableThinking: false };
1124
+ this.presetGenParams = presetGenParams;
1125
+ this.presetHeaders = presetHeaders;
1126
+ this.apiURL = "https://api.deepseek.com/chat/completions";
1127
+ }
1128
+ /**
1129
+ * Calls the DeepSeek API with the given parameters.
1130
+ *
1131
+ * @param modelName - The name of the model to use.
1132
+ * @param options - The chat model options.
1133
+ * @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
1134
+ */
1135
+ async _callAPI(modelName, options) {
1136
+ const data = {
1137
+ model: modelName,
1138
+ messages: options.messages,
1139
+ tools: this._formatToolSchemas(options.tools),
1140
+ tool_choice: this._formatToolChoice(options.toolChoice),
1141
+ thinking: this.thinkingConfig.enableThinking ? { type: "enabled" } : { type: "disabled" },
1142
+ stream: this.stream,
1143
+ ...this.presetGenParams ?? {}
1144
+ };
1145
+ const headers = {
1146
+ Authorization: `Bearer ${this.apiKey}`,
1147
+ "Content-Type": "application/json",
1148
+ ...this.presetHeaders
1149
+ };
1150
+ const startTime = Date.now();
1151
+ const response = await fetch(this.apiURL, {
1152
+ method: "POST",
1153
+ headers,
1154
+ body: JSON.stringify(data)
1155
+ });
1156
+ if (!response.ok) {
1157
+ throw new Error(
1158
+ `DeepSeek API request failed with status ${response.status}: ${await response.text()}`
1159
+ );
1160
+ }
1161
+ if (this.stream) {
1162
+ return this._parseDeepSeekStreamedResponse(response, startTime);
1163
+ }
1164
+ const blocks = [];
1165
+ const res = await response.json();
1166
+ const choice = res.choices[0];
1167
+ if (choice.message.reasoning_content) {
1168
+ blocks.push({
1169
+ id: crypto.randomUUID(),
1170
+ type: "thinking",
1171
+ thinking: choice.message.reasoning_content
1172
+ });
1173
+ }
1174
+ if (choice.message.content) {
1175
+ blocks.push({ id: crypto.randomUUID(), type: "text", text: choice.message.content });
1176
+ }
1177
+ if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
1178
+ choice.message.tool_calls.forEach((toolCall) => {
1179
+ if ("id" in toolCall && "function" in toolCall && typeof toolCall.function === "object" && toolCall.function && "name" in toolCall.function && "arguments" in toolCall.function) {
1180
+ const inputString = String(toolCall.function.arguments);
1181
+ blocks.push({
1182
+ type: "tool_call",
1183
+ id: String(toolCall.id),
1184
+ name: String(toolCall.function.name),
1185
+ input: inputString
1186
+ });
1187
+ }
1188
+ });
1189
+ }
1190
+ const usage = res.usage ? {
1191
+ type: "chat_usage",
1192
+ inputTokens: res.usage.prompt_tokens || 0,
1193
+ outputTokens: res.usage.completion_tokens || 0,
1194
+ time: (Date.now() - startTime) / 1e3
1195
+ } : void 0;
1196
+ return {
1197
+ type: "chat",
1198
+ id: crypto.randomUUID(),
1199
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1200
+ content: blocks,
1201
+ usage
1202
+ };
1203
+ }
1204
+ /**
1205
+ * The method to format the tool choice parameter.
1206
+ *
1207
+ * @param toolChoice - The tool choice option.
1208
+ * @returns The formatted tool choice.
1209
+ */
1210
+ _formatToolChoice(toolChoice) {
1211
+ if (toolChoice) {
1212
+ if (toolChoice === "auto") return "auto";
1213
+ if (toolChoice === "none") return "none";
1214
+ if (this.thinkingConfig?.enableThinking) {
1215
+ console.log(
1216
+ `The deepseek reasoning model does not support tool choice options '${toolChoice}'. 'auto' will be used instead.`
1217
+ );
1218
+ return "auto";
1219
+ }
1220
+ if (toolChoice === "required") return "required";
1221
+ return {
1222
+ type: "function",
1223
+ function: {
1224
+ name: toolChoice
1225
+ }
1226
+ };
1227
+ }
1228
+ return "auto";
1229
+ }
1230
+ /**
1231
+ * Parses a streamed response from DeepSeek API specifically for chat responses.
1232
+ * An async generator that yields delta ChatResponse objects as they are received.
1233
+ *
1234
+ * @param response - The fetch response object.
1235
+ * @param startTime - The start time of the request for usage calculation.
1236
+ * @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
1237
+ */
1238
+ async *_parseDeepSeekStreamedResponse(response, startTime) {
1239
+ const asyncGenerator = _parseStreamedResponse(response);
1240
+ let accText = "";
1241
+ let accThinking = "";
1242
+ const accToolInputs = /* @__PURE__ */ new Map();
1243
+ const toolCallMeta = /* @__PURE__ */ new Map();
1244
+ let lastUsage = void 0;
1245
+ for await (const jsonObj of asyncGenerator) {
1246
+ if (jsonObj.choices && jsonObj.choices.length > 0) {
1247
+ const choice = jsonObj.choices[0];
1248
+ let deltaText = "";
1249
+ let deltaThinking = "";
1250
+ const deltaToolCalls = /* @__PURE__ */ new Map();
1251
+ if (choice.delta?.content) {
1252
+ deltaText = choice.delta.content;
1253
+ accText += deltaText;
1254
+ }
1255
+ if (choice.delta?.reasoning_content) {
1256
+ deltaThinking = choice.delta.reasoning_content;
1257
+ accThinking += deltaThinking;
1258
+ }
1259
+ if (choice.delta?.tool_calls) {
1260
+ choice.delta.tool_calls.forEach((toolCall) => {
1261
+ const index = toolCall.index.toString();
1262
+ if (!toolCallMeta.has(index)) {
1263
+ toolCallMeta.set(index, { id: "", name: "" });
1264
+ }
1265
+ if (!accToolInputs.has(index)) {
1266
+ accToolInputs.set(index, "");
1267
+ }
1268
+ if (toolCall.id) {
1269
+ toolCallMeta.get(index).id = toolCall.id;
1270
+ }
1271
+ if (toolCall.function?.name) {
1272
+ toolCallMeta.get(index).name = toolCall.function.name;
1273
+ }
1274
+ if (toolCall.function?.arguments) {
1275
+ const deltaArgs = toolCall.function.arguments;
1276
+ accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
1277
+ const meta = toolCallMeta.get(index);
1278
+ deltaToolCalls.set(index, {
1279
+ type: "tool_call",
1280
+ id: meta.id,
1281
+ name: meta.name,
1282
+ input: deltaArgs
1283
+ });
1284
+ }
1285
+ });
1286
+ }
1287
+ const deltaBlocks = this._accDataToBlocks(deltaText, deltaThinking, deltaToolCalls);
1288
+ lastUsage = jsonObj.usage ? {
1289
+ type: "chat_usage",
1290
+ inputTokens: jsonObj.usage.prompt_tokens || 0,
1291
+ outputTokens: jsonObj.usage.completion_tokens || 0,
1292
+ time: (Date.now() - startTime) / 1e3
1293
+ } : void 0;
1294
+ yield {
1295
+ type: "chat",
1296
+ id: crypto.randomUUID(),
1297
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1298
+ content: deltaBlocks,
1299
+ usage: lastUsage
1300
+ };
1301
+ }
1302
+ }
1303
+ const finalToolCalls = /* @__PURE__ */ new Map();
1304
+ toolCallMeta.forEach((meta, index) => {
1305
+ finalToolCalls.set(index, {
1306
+ type: "tool_call",
1307
+ id: meta.id,
1308
+ name: meta.name,
1309
+ input: accToolInputs.get(index) || "{}"
1310
+ });
1311
+ });
1312
+ const blocks = this._accDataToBlocks(accText, accThinking, finalToolCalls);
1313
+ return {
1314
+ type: "chat",
1315
+ id: crypto.randomUUID(),
1316
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1317
+ content: blocks,
1318
+ usage: lastUsage
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Convert data into blocks
1323
+ *
1324
+ * @param text - The text response from the llm API
1325
+ * @param thinking - The thinking response
1326
+ * @param toolCalls - The tool calls
1327
+ * @returns An array of blocks
1328
+ */
1329
+ _accDataToBlocks(text, thinking, toolCalls) {
1330
+ const blocks = [];
1331
+ if (thinking) {
1332
+ blocks.push({ id: crypto.randomUUID(), type: "thinking", thinking });
1333
+ }
1334
+ if (text) {
1335
+ blocks.push({ id: crypto.randomUUID(), type: "text", text });
1336
+ }
1337
+ if (toolCalls.size > 0) {
1338
+ toolCalls.forEach((value) => {
1339
+ blocks.push(value);
1340
+ });
1341
+ }
1342
+ return blocks;
1343
+ }
1344
+ /**
1345
+ * Format the tool schemas to the expected API format for DeepSeek API.
1346
+ * @param tools
1347
+ * @returns The formatted tool schemas.
1348
+ */
1349
+ _formatToolSchemas(tools) {
1350
+ return tools || [];
1351
+ }
1352
+ };
1353
+
1354
+ // src/model/ollama-model.ts
1355
+ import { Ollama } from "ollama";
1356
+ var OllamaChatModel = class extends ChatModelBase {
1357
+ client;
1358
+ options;
1359
+ keepAlive;
1360
+ thinkingConfig;
1361
+ generateKwargs;
1362
+ /**
1363
+ * Initializes a new instance of the OllamaChatModel class.
1364
+ * @param root0
1365
+ * @param root0.modelName
1366
+ * @param root0.stream
1367
+ * @param root0.options
1368
+ * @param root0.keepAlive
1369
+ * @param root0.thinkingConfig
1370
+ * @param root0.host
1371
+ * @param root0.maxRetries
1372
+ * @param root0.fallbackModelName
1373
+ * @param root0.clientKwargs
1374
+ * @param root0.generateKwargs
1375
+ * @param root0.formatter
1376
+ */
1377
+ constructor({
1378
+ modelName,
1379
+ stream = true,
1380
+ options,
1381
+ keepAlive = "5m",
1382
+ thinkingConfig,
1383
+ host,
1384
+ maxRetries = 0,
1385
+ fallbackModelName,
1386
+ clientKwargs,
1387
+ generateKwargs,
1388
+ formatter
1389
+ }) {
1390
+ const defaultFormatter = formatter || new OllamaChatFormatter();
1391
+ super({
1392
+ modelName,
1393
+ stream,
1394
+ maxRetries,
1395
+ fallbackModelName,
1396
+ formatter: defaultFormatter
1397
+ });
1398
+ this.options = options;
1399
+ this.keepAlive = keepAlive;
1400
+ this.thinkingConfig = thinkingConfig || {
1401
+ enableThinking: false
1402
+ };
1403
+ this.generateKwargs = generateKwargs || {};
1404
+ this.client = new Ollama({
1405
+ host,
1406
+ ...clientKwargs
1407
+ });
1408
+ }
1409
+ /**
1410
+ * Calls the Ollama API with the given parameters.
1411
+ * @param modelName
1412
+ * @param options
1413
+ * @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
1414
+ */
1415
+ async _callAPI(modelName, options) {
1416
+ const kwargs = {
1417
+ model: modelName,
1418
+ messages: options.messages,
1419
+ stream: this.stream,
1420
+ options: this.options,
1421
+ keep_alive: this.keepAlive,
1422
+ ...this.generateKwargs
1423
+ };
1424
+ if (this.thinkingConfig.enableThinking) {
1425
+ kwargs.think = this.thinkingConfig.thinkingLevel || true;
1426
+ } else {
1427
+ kwargs.think = false;
1428
+ }
1429
+ if (options.tools) {
1430
+ kwargs.tools = this._formatToolSchemas(options.tools);
1431
+ }
1432
+ if (options.toolChoice) {
1433
+ console.warn("Ollama does not support tool_choice yet, ignored.");
1434
+ }
1435
+ const startTime = Date.now();
1436
+ if (this.stream) {
1437
+ const response2 = await this.client.chat({
1438
+ ...kwargs,
1439
+ stream: true
1440
+ });
1441
+ return this._parseOllamaStreamResponse(response2, startTime);
1442
+ }
1443
+ const response = await this.client.chat({
1444
+ ...kwargs,
1445
+ stream: false
1446
+ });
1447
+ return this._parseOllamaResponse(response, startTime);
1448
+ }
1449
+ /**
1450
+ * Parse Ollama streaming response.
1451
+ * @param stream
1452
+ * @param startTime
1453
+ * @returns An async generator that yields delta ChatResponse objects and returns the complete ChatResponse.
1454
+ */
1455
+ async *_parseOllamaStreamResponse(stream, startTime) {
1456
+ let accText = "";
1457
+ let accThinking = "";
1458
+ const toolCalls = /* @__PURE__ */ new Map();
1459
+ let lastUsage = null;
1460
+ for await (const chunk of stream) {
1461
+ const msg = chunk.message;
1462
+ let deltaText = "";
1463
+ let deltaThinking = "";
1464
+ const deltaToolCalls = /* @__PURE__ */ new Map();
1465
+ if (msg.thinking) {
1466
+ deltaThinking = msg.thinking;
1467
+ accThinking += msg.thinking;
1468
+ }
1469
+ if (msg.content) {
1470
+ deltaText = msg.content;
1471
+ accText += msg.content;
1472
+ }
1473
+ if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
1474
+ for (let idx = 0; idx < msg.tool_calls.length; idx++) {
1475
+ const toolCall = msg.tool_calls[idx];
1476
+ const func = toolCall.function;
1477
+ const toolId = `${idx}_${func.name}`;
1478
+ const toolCallBlock = {
1479
+ type: "tool_call",
1480
+ id: toolId,
1481
+ name: func.name,
1482
+ input: JSON.stringify(func.arguments)
1483
+ };
1484
+ toolCalls.set(toolId, toolCallBlock);
1485
+ deltaToolCalls.set(toolId, toolCallBlock);
1486
+ }
1487
+ }
1488
+ const currentTime = (Date.now() - startTime) / 1e3;
1489
+ lastUsage = {
1490
+ type: "chat_usage",
1491
+ inputTokens: chunk.prompt_eval_count || 0,
1492
+ outputTokens: chunk.eval_count || 0,
1493
+ time: currentTime
1494
+ };
1495
+ const deltaBlocks = this._buildContentBlocks(deltaText, deltaThinking, deltaToolCalls);
1496
+ yield {
1497
+ type: "chat",
1498
+ id: crypto.randomUUID(),
1499
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1500
+ content: deltaBlocks,
1501
+ usage: lastUsage
1502
+ };
1503
+ }
1504
+ const blocks = this._buildContentBlocks(accText, accThinking, toolCalls);
1505
+ return {
1506
+ type: "chat",
1507
+ id: crypto.randomUUID(),
1508
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1509
+ content: blocks,
1510
+ usage: lastUsage
1511
+ };
1512
+ }
1513
+ /**
1514
+ * Parse Ollama non-streaming response.
1515
+ * @param response
1516
+ * @param startTime
1517
+ * @returns A ChatResponse object containing the content blocks and usage.
1518
+ */
1519
+ _parseOllamaResponse(response, startTime) {
1520
+ const blocks = [];
1521
+ if (response.message.thinking) {
1522
+ blocks.push({
1523
+ id: crypto.randomUUID(),
1524
+ type: "thinking",
1525
+ thinking: response.message.thinking
1526
+ });
1527
+ }
1528
+ if (response.message.content) {
1529
+ blocks.push({
1530
+ id: crypto.randomUUID(),
1531
+ type: "text",
1532
+ text: response.message.content
1533
+ });
1534
+ }
1535
+ if (response.message.tool_calls && Array.isArray(response.message.tool_calls)) {
1536
+ for (let idx = 0; idx < response.message.tool_calls.length; idx++) {
1537
+ const toolCall = response.message.tool_calls[idx];
1538
+ blocks.push({
1539
+ type: "tool_call",
1540
+ id: `${idx}_${toolCall.function.name}`,
1541
+ name: toolCall.function.name,
1542
+ input: JSON.stringify(toolCall.function.arguments)
1543
+ });
1544
+ }
1545
+ }
1546
+ const usage = response.prompt_eval_count !== void 0 && response.eval_count !== void 0 ? {
1547
+ type: "chat_usage",
1548
+ inputTokens: response.prompt_eval_count || 0,
1549
+ outputTokens: response.eval_count || 0,
1550
+ time: (Date.now() - startTime) / 1e3
1551
+ } : void 0;
1552
+ return {
1553
+ type: "chat",
1554
+ id: crypto.randomUUID(),
1555
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1556
+ content: blocks,
1557
+ usage
1558
+ };
1559
+ }
1560
+ /**
1561
+ * Build content blocks from accumulated data.
1562
+ * @param text
1563
+ * @param thinking
1564
+ * @param toolCalls
1565
+ * @returns An array of content blocks.
1566
+ */
1567
+ _buildContentBlocks(text, thinking, toolCalls) {
1568
+ const blocks = [];
1569
+ if (thinking) {
1570
+ blocks.push({ id: crypto.randomUUID(), type: "thinking", thinking });
1571
+ }
1572
+ if (text) {
1573
+ blocks.push({ id: crypto.randomUUID(), type: "text", text });
1574
+ }
1575
+ toolCalls.forEach((toolCall) => {
1576
+ blocks.push(toolCall);
1577
+ });
1578
+ return blocks;
1579
+ }
1580
+ /**
1581
+ * Format tool choice parameter (not supported by Ollama).
1582
+ * @param _toolChoice
1583
+ * @returns undefined as Ollama does not support tool choice.
1584
+ */
1585
+ _formatToolChoice(_toolChoice) {
1586
+ return void 0;
1587
+ }
1588
+ /**
1589
+ * Format tool schemas for Ollama API (no special formatting needed).
1590
+ * @param tools
1591
+ * @returns The same array of tool schemas, or an empty array if undefined.
1592
+ */
1593
+ _formatToolSchemas(tools) {
1594
+ return tools || [];
1595
+ }
1596
+ };
1597
+
1598
+ // src/model/openai-model.ts
1599
+ import { OpenAI } from "openai";
1600
+ var OpenAIChatModel = class extends ChatModelBase {
1601
+ client;
1602
+ presetGenParams;
1603
+ /**
1604
+ * Initializes a new instance of the OpenAIChatModel class.
1605
+ * @param options
1606
+ * @param options.modelName
1607
+ * @param options.apiKey
1608
+ * @param options.stream
1609
+ * @param options.maxRetries
1610
+ * @param options.fallbackModelName
1611
+ * @param options.presetGenParams
1612
+ * @param options.baseURL
1613
+ * @param options.formatter
1614
+ */
1615
+ constructor({
1616
+ modelName,
1617
+ apiKey,
1618
+ stream = true,
1619
+ maxRetries = 3,
1620
+ fallbackModelName,
1621
+ presetGenParams,
1622
+ baseURL,
1623
+ formatter
1624
+ }) {
1625
+ const defaultFormatter = formatter || new OpenAIChatFormatter();
1626
+ super({
1627
+ modelName,
1628
+ stream,
1629
+ maxRetries,
1630
+ fallbackModelName,
1631
+ formatter: defaultFormatter
1632
+ });
1633
+ this.client = new OpenAI({
1634
+ apiKey,
1635
+ baseURL
1636
+ });
1637
+ this.presetGenParams = presetGenParams;
1638
+ }
1639
+ /**
1640
+ * Calls the OpenAI API with the given parameters.
1641
+ *
1642
+ * @param modelName - The name of the model to use.
1643
+ * @param options - The chat model options.
1644
+ * @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
1645
+ */
1646
+ async _callAPI(modelName, options) {
1647
+ const startTime = Date.now();
1648
+ if (this.stream) {
1649
+ const stream = await this.client.chat.completions.create({
1650
+ model: modelName,
1651
+ messages: options.messages,
1652
+ tools: this._formatToolSchemas(options.tools),
1653
+ tool_choice: this._formatToolChoice(options.toolChoice),
1654
+ stream: true,
1655
+ ...this.presetGenParams ?? {}
1656
+ });
1657
+ return this._parseOpenAIStreamedResponse(stream, startTime);
1658
+ }
1659
+ const response = await this.client.chat.completions.create({
1660
+ model: modelName,
1661
+ messages: options.messages,
1662
+ tools: options.tools,
1663
+ tool_choice: this._formatToolChoice(options.toolChoice),
1664
+ stream: false,
1665
+ ...this.presetGenParams ?? {}
1666
+ });
1667
+ const choice = response.choices[0];
1668
+ const blocks = [];
1669
+ if (choice.message.content) {
1670
+ blocks.push({ id: crypto.randomUUID(), type: "text", text: choice.message.content });
1671
+ }
1672
+ if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
1673
+ choice.message.tool_calls.forEach((toolCall) => {
1674
+ if (toolCall.type === "function") {
1675
+ blocks.push({
1676
+ type: "tool_call",
1677
+ id: toolCall.id,
1678
+ name: toolCall.function.name,
1679
+ input: toolCall.function.arguments
1680
+ });
1681
+ }
1682
+ });
1683
+ }
1684
+ const usage = response.usage ? {
1685
+ type: "chat_usage",
1686
+ inputTokens: response.usage.prompt_tokens,
1687
+ outputTokens: response.usage.completion_tokens,
1688
+ time: (Date.now() - startTime) / 1e3
1689
+ } : void 0;
1690
+ return {
1691
+ type: "chat",
1692
+ id: response.id,
1693
+ createdAt: new Date(response.created * 1e3).toISOString(),
1694
+ content: blocks,
1695
+ usage
1696
+ };
1697
+ }
1698
+ /**
1699
+ * Formats the tool choice for the API request.
1700
+ *
1701
+ * TODO: supports grouped tool choices.
1702
+ *
1703
+ * @param toolChoice - The tool choice option.
1704
+ * @returns The formatted tool choice.
1705
+ */
1706
+ _formatToolChoice(toolChoice) {
1707
+ if (toolChoice) {
1708
+ if (toolChoice === "none" || toolChoice === "auto" || toolChoice === "required") {
1709
+ return toolChoice;
1710
+ }
1711
+ return {
1712
+ type: "function",
1713
+ function: {
1714
+ name: toolChoice
1715
+ }
1716
+ };
1717
+ }
1718
+ return "auto";
1719
+ }
1720
+ /**
1721
+ * Parses a streamed response from OpenAI API.
1722
+ * An async generator that yields delta ChatResponse objects as they are received.
1723
+ *
1724
+ * @param stream - The OpenAI stream object.
1725
+ * @param startTime - The start time of the request for usage calculation.
1726
+ * @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
1727
+ */
1728
+ async *_parseOpenAIStreamedResponse(stream, startTime) {
1729
+ let accText = "";
1730
+ const accToolInputs = /* @__PURE__ */ new Map();
1731
+ const toolCallMeta = /* @__PURE__ */ new Map();
1732
+ let lastUsage = null;
1733
+ let responseId = "";
1734
+ let createdTimestamp = 0;
1735
+ for await (const chunk of stream) {
1736
+ if (!responseId && chunk.id) {
1737
+ responseId = chunk.id;
1738
+ }
1739
+ if (!createdTimestamp && chunk.created) {
1740
+ createdTimestamp = chunk.created;
1741
+ }
1742
+ if (chunk.choices && chunk.choices.length > 0) {
1743
+ const choice = chunk.choices[0];
1744
+ let deltaText = "";
1745
+ const deltaToolCalls = /* @__PURE__ */ new Map();
1746
+ if (choice.delta?.content) {
1747
+ deltaText = choice.delta.content;
1748
+ accText += deltaText;
1749
+ }
1750
+ if (choice.delta?.tool_calls) {
1751
+ choice.delta.tool_calls.forEach((toolCall) => {
1752
+ const index = toolCall.index.toString();
1753
+ if (!toolCallMeta.has(index)) {
1754
+ toolCallMeta.set(index, { id: "", name: "" });
1755
+ }
1756
+ if (!accToolInputs.has(index)) {
1757
+ accToolInputs.set(index, "");
1758
+ }
1759
+ if (toolCall.id) {
1760
+ toolCallMeta.get(index).id = toolCall.id;
1761
+ }
1762
+ if (toolCall.function?.name) {
1763
+ toolCallMeta.get(index).name = toolCall.function.name;
1764
+ }
1765
+ if (toolCall.function?.arguments) {
1766
+ const deltaArgs = toolCall.function.arguments;
1767
+ accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
1768
+ const meta = toolCallMeta.get(index);
1769
+ deltaToolCalls.set(index, {
1770
+ type: "tool_call",
1771
+ id: meta.id,
1772
+ name: meta.name,
1773
+ input: deltaArgs
1774
+ });
1775
+ }
1776
+ });
1777
+ }
1778
+ const deltaBlocks = this._accDataToBlocks(deltaText, deltaToolCalls);
1779
+ yield {
1780
+ type: "chat",
1781
+ id: responseId || crypto.randomUUID(),
1782
+ createdAt: createdTimestamp ? new Date(createdTimestamp * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1783
+ content: deltaBlocks,
1784
+ usage: lastUsage
1785
+ };
1786
+ }
1787
+ if (chunk.usage) {
1788
+ lastUsage = {
1789
+ type: "chat_usage",
1790
+ inputTokens: chunk.usage.prompt_tokens || 0,
1791
+ outputTokens: chunk.usage.completion_tokens || 0,
1792
+ time: (Date.now() - startTime) / 1e3
1793
+ };
1794
+ }
1795
+ }
1796
+ const finalToolCalls = /* @__PURE__ */ new Map();
1797
+ toolCallMeta.forEach((meta, index) => {
1798
+ finalToolCalls.set(index, {
1799
+ type: "tool_call",
1800
+ id: meta.id,
1801
+ name: meta.name,
1802
+ input: accToolInputs.get(index) || "{}"
1803
+ });
1804
+ });
1805
+ const blocks = this._accDataToBlocks(accText, finalToolCalls);
1806
+ return {
1807
+ type: "chat",
1808
+ id: responseId || crypto.randomUUID(),
1809
+ createdAt: createdTimestamp ? new Date(createdTimestamp * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1810
+ content: blocks,
1811
+ usage: lastUsage
1812
+ };
1813
+ }
1814
+ /**
1815
+ * Convert data into blocks
1816
+ *
1817
+ * @param text - The text response from the llm API
1818
+ * @param toolCalls - The tool calls
1819
+ * @returns An array of blocks
1820
+ */
1821
+ _accDataToBlocks(text, toolCalls) {
1822
+ const blocks = [];
1823
+ if (text) {
1824
+ blocks.push({ id: crypto.randomUUID(), type: "text", text });
1825
+ }
1826
+ if (toolCalls.size > 0) {
1827
+ toolCalls.forEach((value) => {
1828
+ blocks.push(value);
1829
+ });
1830
+ }
1831
+ return blocks;
1832
+ }
1833
+ /**
1834
+ * Format the tool schemas to the expected API format.
1835
+ * @param tools
1836
+ * @returns The formatted tool schemas.
1837
+ */
1838
+ _formatToolSchemas(tools) {
1839
+ return tools || [];
1840
+ }
1841
+ };
1842
+ export {
1843
+ ChatModelBase,
1844
+ DashScopeChatModel,
1845
+ DeepSeekChatModel,
1846
+ OllamaChatModel,
1847
+ OpenAIChatModel
1848
+ };
1849
+ //# sourceMappingURL=index.mjs.map