@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,279 @@
1
+ import { DeepSeekChatModel } from './deepseek-model';
2
+ import { ChatResponse } from './response';
3
+ import { createMsg } from '../message';
4
+
5
+ // Mock fetch for streaming responses
6
+ global.fetch = jest.fn();
7
+
8
+ describe('DeepSeekChatModel', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ test('Test stream generation with delta output', async () => {
14
+ // Mock streaming response with multiple chunks (SSE format)
15
+ // DeepSeek uses OpenAI-compatible API format
16
+ const mockStreamChunks = [
17
+ 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"reasoning_content":"Analyzing the"},"finish_reason":null}]}\n\n',
18
+ 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"reasoning_content":" weather query"},"finish_reason":null}]}\n\n',
19
+ 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call-123","type":"function","function":{"name":"get_current_weather","arguments":"{\\"location\\""}}]},"finish_reason":null}]}\n\n',
20
+ 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":\\"Beijing\\"}"}}]},"finish_reason":null}]}\n\n',
21
+ 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":100,"completion_tokens":50,"total_tokens":150}}\n\n',
22
+ 'data: [DONE]\n\n',
23
+ ];
24
+
25
+ const mockReadableStream = new ReadableStream({
26
+ start(controller) {
27
+ mockStreamChunks.forEach(chunk =>
28
+ controller.enqueue(new TextEncoder().encode(chunk))
29
+ );
30
+ controller.close();
31
+ },
32
+ });
33
+
34
+ (global.fetch as jest.Mock).mockResolvedValue({
35
+ ok: true,
36
+ body: mockReadableStream,
37
+ });
38
+
39
+ const model = new DeepSeekChatModel({
40
+ modelName: 'deepseek-reasoner',
41
+ apiKey: 'test-api-key',
42
+ });
43
+
44
+ const res = await model.call({
45
+ messages: [
46
+ createMsg({
47
+ name: 'user',
48
+ role: 'user',
49
+ content: [{ id: crypto.randomUUID(), type: 'text', text: '查询北京天气' }],
50
+ }),
51
+ ],
52
+ tools: [
53
+ {
54
+ type: 'function',
55
+ function: {
56
+ name: 'get_current_weather',
57
+ description: 'Get the current weather in a given location',
58
+ parameters: {
59
+ type: 'object',
60
+ properties: {
61
+ location: {
62
+ type: 'string',
63
+ description: 'The city and state, e.g. San Francisco, CA',
64
+ },
65
+ },
66
+ required: ['location'],
67
+ },
68
+ },
69
+ },
70
+ ],
71
+ });
72
+
73
+ const generator = res as AsyncGenerator<ChatResponse, ChatResponse>;
74
+ let completeResponse: ChatResponse | undefined;
75
+ const yieldedChunks: ChatResponse[] = [];
76
+
77
+ // Manual iteration to capture both yielded and returned values
78
+ while (true) {
79
+ const result = await generator.next();
80
+ if (result.done) {
81
+ completeResponse = result.value;
82
+ break;
83
+ }
84
+ yieldedChunks.push(result.value);
85
+ }
86
+
87
+ // Verify we received multiple yielded chunks
88
+ expect(yieldedChunks.length).toBeGreaterThan(0);
89
+
90
+ // Verify the final complete response has correct structure
91
+ expect(completeResponse.content.length).toBe(2);
92
+
93
+ // Check thinking block - should be complete after accumulation
94
+ const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
95
+ expect(thinkingBlock).toBeDefined();
96
+ expect(thinkingBlock).toMatchObject({
97
+ type: 'thinking',
98
+ thinking: 'Analyzing the weather query',
99
+ });
100
+
101
+ // Check tool_call block - input should be complete after accumulation
102
+ const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
103
+ expect(toolCallBlock).toBeDefined();
104
+ expect(toolCallBlock).toMatchObject({
105
+ type: 'tool_call',
106
+ name: 'get_current_weather',
107
+ id: 'call-123',
108
+ input: '{"location":"Beijing"}',
109
+ });
110
+
111
+ // Verify usage
112
+ expect(completeResponse.usage).toBeDefined();
113
+ expect(completeResponse.usage?.inputTokens).toBe(100);
114
+ expect(completeResponse.usage?.outputTokens).toBe(50);
115
+ }, 10000);
116
+
117
+ test('Test non-streaming generation', async () => {
118
+ // Mock non-streaming response
119
+ const mockResponse = {
120
+ id: 'chatcmpl-123',
121
+ object: 'chat.completion',
122
+ created: 1234567890,
123
+ model: 'deepseek-reasoner',
124
+ choices: [
125
+ {
126
+ index: 0,
127
+ message: {
128
+ role: 'assistant',
129
+ reasoning_content: 'Analyzing the weather query',
130
+ tool_calls: [
131
+ {
132
+ id: 'call-123',
133
+ type: 'function',
134
+ function: {
135
+ name: 'get_current_weather',
136
+ arguments: '{"location":"Beijing"}',
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ finish_reason: 'tool_calls',
142
+ },
143
+ ],
144
+ usage: {
145
+ prompt_tokens: 100,
146
+ completion_tokens: 50,
147
+ total_tokens: 150,
148
+ },
149
+ };
150
+
151
+ (global.fetch as jest.Mock).mockResolvedValue({
152
+ ok: true,
153
+ json: async () => mockResponse,
154
+ });
155
+
156
+ const model = new DeepSeekChatModel({
157
+ modelName: 'deepseek-reasoner',
158
+ apiKey: 'test-api-key',
159
+ stream: false,
160
+ });
161
+
162
+ const res = await model.call({
163
+ messages: [
164
+ createMsg({
165
+ name: 'user',
166
+ role: 'user',
167
+ content: [{ id: crypto.randomUUID(), type: 'text', text: '查询北京天气' }],
168
+ }),
169
+ ],
170
+ tools: [
171
+ {
172
+ type: 'function',
173
+ function: {
174
+ name: 'get_current_weather',
175
+ description: 'Get the current weather in a given location',
176
+ parameters: {
177
+ type: 'object',
178
+ properties: {
179
+ location: {
180
+ type: 'string',
181
+ description: 'The city and state, e.g. San Francisco, CA',
182
+ },
183
+ },
184
+ required: ['location'],
185
+ },
186
+ },
187
+ },
188
+ ],
189
+ });
190
+
191
+ const completeResponse = res as ChatResponse;
192
+
193
+ // Verify complete response structure
194
+ expect(completeResponse.content.length).toBe(2);
195
+
196
+ // Check thinking block
197
+ const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
198
+ expect(thinkingBlock).toBeDefined();
199
+ expect(thinkingBlock).toMatchObject({
200
+ type: 'thinking',
201
+ thinking: 'Analyzing the weather query',
202
+ });
203
+
204
+ // Check tool_call block
205
+ const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
206
+ expect(toolCallBlock).toBeDefined();
207
+ expect(toolCallBlock).toMatchObject({
208
+ type: 'tool_call',
209
+ name: 'get_current_weather',
210
+ id: 'call-123',
211
+ input: '{"location":"Beijing"}',
212
+ });
213
+
214
+ // Verify usage
215
+ expect(completeResponse.usage).toBeDefined();
216
+ expect(completeResponse.usage?.inputTokens).toBe(100);
217
+ expect(completeResponse.usage?.outputTokens).toBe(50);
218
+ }, 10000);
219
+
220
+ test('Test formatToolChoice function', () => {
221
+ const model = new DeepSeekChatModel({
222
+ modelName: 'deepseek-chat',
223
+ apiKey: 'test-api-key',
224
+ });
225
+
226
+ // Test 'auto' case
227
+ expect(model._formatToolChoice('auto')).toBe('auto');
228
+
229
+ // Test 'none' case
230
+ expect(model._formatToolChoice('none')).toBe('none');
231
+
232
+ // Test 'required' case
233
+ expect(model._formatToolChoice('required')).toBe('required');
234
+
235
+ // Test specific function name case
236
+ expect(model._formatToolChoice('get_current_weather')).toEqual({
237
+ type: 'function',
238
+ function: {
239
+ name: 'get_current_weather',
240
+ },
241
+ });
242
+
243
+ // Test undefined case (should default to 'auto')
244
+ expect(model._formatToolChoice(undefined)).toBe('auto');
245
+ });
246
+
247
+ test('Test formatToolSchemas function', () => {
248
+ const model = new DeepSeekChatModel({
249
+ modelName: 'deepseek-chat',
250
+ apiKey: 'test-api-key',
251
+ });
252
+
253
+ const toolSchemas = [
254
+ {
255
+ type: 'function' as const,
256
+ function: {
257
+ name: 'get_current_weather',
258
+ description: 'Get the current weather in a given location',
259
+ parameters: {
260
+ type: 'object' as const,
261
+ properties: {
262
+ location: {
263
+ type: 'string',
264
+ description: 'The city and state, e.g. San Francisco, CA',
265
+ },
266
+ },
267
+ required: ['location'],
268
+ },
269
+ },
270
+ },
271
+ ];
272
+
273
+ // Test with tool schemas
274
+ expect(model._formatToolSchemas(toolSchemas)).toEqual(toolSchemas);
275
+
276
+ // Test with undefined (should return empty array)
277
+ expect(model._formatToolSchemas(undefined)).toEqual([]);
278
+ });
279
+ });
@@ -0,0 +1,401 @@
1
+ import { ChatModelBase, ChatModelOptions, ChatModelRequestOptions } from './base';
2
+ import { ChatResponse } from './response';
3
+ import { DataBlock, TextBlock, ThinkingBlock, ToolCallBlock } from '../message';
4
+ import { ToolChoice, ToolSchema } from '../type';
5
+ import { ChatUsage } from './usage';
6
+ import { _parseStreamedResponse } from '../_utils';
7
+ import { DeepSeekChatFormatter } from '../formatter';
8
+
9
+ interface _DeepSeekStreamChunk {
10
+ choices?: {
11
+ delta?: {
12
+ content?: string;
13
+ reasoning_content?: string;
14
+ tool_calls?: {
15
+ index: number;
16
+ id?: string;
17
+ function?: {
18
+ name?: string;
19
+ arguments?: string;
20
+ };
21
+ }[];
22
+ };
23
+ finish_reason?: string | null;
24
+ }[];
25
+ usage?: {
26
+ prompt_tokens?: number;
27
+ completion_tokens?: number;
28
+ };
29
+ }
30
+
31
+ interface DeepSeekThinkingConfig {
32
+ /**
33
+ * Whether to enable thinking or not.
34
+ */
35
+ enableThinking: boolean;
36
+ }
37
+
38
+ interface DeepSeekChatModelOptions extends ChatModelOptions {
39
+ /**
40
+ * The API key for authenticating with DeepSeek API.
41
+ */
42
+ apiKey: string;
43
+
44
+ /**
45
+ * Thinking configuration for DeepSeek models.
46
+ */
47
+ thinkingConfig?: DeepSeekThinkingConfig;
48
+
49
+ /**
50
+ * Preset generation parameters to include in each request.
51
+ * These parameters will be merged with the request-specific parameters.
52
+ */
53
+ presetGenParams?: Record<string, unknown>;
54
+
55
+ /**
56
+ * Preset headers that will be included in each request.
57
+ */
58
+ presetHeaders?: Record<string, unknown>;
59
+ }
60
+
61
+ /**
62
+ * The DeepSeek API chat model.
63
+ */
64
+ export class DeepSeekChatModel extends ChatModelBase {
65
+ apiURL: string;
66
+ protected apiKey: string;
67
+ protected presetGenParams: Record<string, unknown> | undefined;
68
+ protected presetHeaders: Record<string, unknown> | undefined;
69
+ protected thinkingConfig: DeepSeekThinkingConfig;
70
+
71
+ /**
72
+ * Initializes a new instance of the DeepSeekChatModel class.
73
+ *
74
+ * @param options - The DeepSeek chat model options.
75
+ * @param options.modelName - The name of the model to use.
76
+ * @param options.apiKey - The API key for authentication.
77
+ * @param options.stream - Whether to use streaming responses. Default is true.
78
+ * @param options.thinkingConfig - Thinking configuration.
79
+ * @param options.maxRetries - The maximum number of retries for failed requests. Default is 0.
80
+ * @param options.fallbackModelName - The fallback model name to use if the primary model fails.
81
+ * @param options.presetGenParams - Preset generation parameters to include in each request.
82
+ * @param options.presetHeaders - Preset headers that will be included in each request.
83
+ * @param options.formatter
84
+ */
85
+ constructor({
86
+ modelName,
87
+ apiKey,
88
+ stream = true,
89
+ thinkingConfig,
90
+ maxRetries = 0,
91
+ fallbackModelName,
92
+ presetGenParams,
93
+ presetHeaders,
94
+ formatter,
95
+ }: DeepSeekChatModelOptions) {
96
+ // If no formatter is provided, create a default DeepSeekChatFormatter
97
+ const defaultFormatter = formatter || new DeepSeekChatFormatter();
98
+ super({
99
+ modelName,
100
+ stream,
101
+ maxRetries,
102
+ fallbackModelName,
103
+ formatter: defaultFormatter,
104
+ } as ChatModelOptions);
105
+
106
+ this.apiKey = apiKey;
107
+ this.thinkingConfig = thinkingConfig || { enableThinking: false };
108
+ this.presetGenParams = presetGenParams;
109
+ this.presetHeaders = presetHeaders;
110
+ this.apiURL = 'https://api.deepseek.com/chat/completions';
111
+ }
112
+
113
+ /**
114
+ * Calls the DeepSeek API with the given parameters.
115
+ *
116
+ * @param modelName - The name of the model to use.
117
+ * @param options - The chat model options.
118
+ * @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
119
+ */
120
+ async _callAPI(
121
+ modelName: string,
122
+ options: ChatModelRequestOptions<Record<string, unknown>>
123
+ ): Promise<ChatResponse | AsyncGenerator<ChatResponse, ChatResponse>> {
124
+ // Set up request data
125
+ const data = {
126
+ model: modelName,
127
+ messages: options.messages,
128
+ tools: this._formatToolSchemas(options.tools),
129
+ tool_choice: this._formatToolChoice(options.toolChoice),
130
+ thinking: this.thinkingConfig.enableThinking
131
+ ? { type: 'enabled' }
132
+ : { type: 'disabled' },
133
+ stream: this.stream,
134
+ ...(this.presetGenParams ?? {}),
135
+ } as Record<string, unknown>;
136
+
137
+ // Set up headers
138
+ const headers: Record<string, unknown> = {
139
+ Authorization: `Bearer ${this.apiKey}`,
140
+ 'Content-Type': 'application/json',
141
+ ...this.presetHeaders,
142
+ };
143
+
144
+ // Counting the time cost
145
+ const startTime = Date.now();
146
+ const response = await fetch(this.apiURL, {
147
+ method: 'POST',
148
+ headers: headers as HeadersInit,
149
+ body: JSON.stringify(data),
150
+ });
151
+
152
+ if (!response.ok) {
153
+ throw new Error(
154
+ `DeepSeek API request failed with status ${response.status}: ${await response.text()}`
155
+ );
156
+ }
157
+
158
+ if (this.stream) {
159
+ // Handle the streaming response
160
+ return this._parseDeepSeekStreamedResponse(response, startTime);
161
+ }
162
+
163
+ // Handle the non-streaming response
164
+ const blocks: Array<TextBlock | ToolCallBlock | ThinkingBlock | DataBlock> = [];
165
+ const res = await response.json();
166
+ const choice = res.choices[0];
167
+
168
+ if (choice.message.reasoning_content) {
169
+ blocks.push({
170
+ id: crypto.randomUUID(),
171
+ type: 'thinking',
172
+ thinking: choice.message.reasoning_content,
173
+ });
174
+ }
175
+ if (choice.message.content) {
176
+ blocks.push({ id: crypto.randomUUID(), type: 'text', text: choice.message.content });
177
+ }
178
+ if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
179
+ choice.message.tool_calls.forEach((toolCall: object) => {
180
+ if (
181
+ 'id' in toolCall &&
182
+ 'function' in toolCall &&
183
+ typeof toolCall.function === 'object' &&
184
+ toolCall.function &&
185
+ 'name' in toolCall.function &&
186
+ 'arguments' in toolCall.function
187
+ ) {
188
+ const inputString = String(toolCall.function.arguments);
189
+ blocks.push({
190
+ type: 'tool_call',
191
+ id: String(toolCall.id),
192
+ name: String(toolCall.function.name),
193
+ input: inputString,
194
+ });
195
+ }
196
+ });
197
+ }
198
+
199
+ const usage = res.usage
200
+ ? {
201
+ type: 'chat_usage',
202
+ inputTokens: res.usage.prompt_tokens || 0,
203
+ outputTokens: res.usage.completion_tokens || 0,
204
+ time: (Date.now() - startTime) / 1000,
205
+ }
206
+ : undefined;
207
+
208
+ return {
209
+ type: 'chat',
210
+ id: crypto.randomUUID(),
211
+ createdAt: new Date().toISOString(),
212
+ content: blocks,
213
+ usage,
214
+ } as ChatResponse;
215
+ }
216
+
217
+ /**
218
+ * The method to format the tool choice parameter.
219
+ *
220
+ * @param toolChoice - The tool choice option.
221
+ * @returns The formatted tool choice.
222
+ */
223
+ _formatToolChoice(
224
+ toolChoice?: ToolChoice
225
+ ): 'auto' | 'none' | 'required' | Record<string, unknown> {
226
+ if (toolChoice) {
227
+ if (toolChoice === 'auto') return 'auto';
228
+ if (toolChoice === 'none') return 'none';
229
+ if (this.thinkingConfig?.enableThinking) {
230
+ console.log(
231
+ `The deepseek reasoning model does not support tool choice options '${toolChoice}'. 'auto' will be used instead.`
232
+ );
233
+ return 'auto';
234
+ }
235
+ if (toolChoice === 'required') return 'required';
236
+ return {
237
+ type: 'function',
238
+ function: {
239
+ name: toolChoice,
240
+ },
241
+ };
242
+ }
243
+ return 'auto';
244
+ }
245
+
246
+ /**
247
+ * Parses a streamed response from DeepSeek API specifically for chat responses.
248
+ * An async generator that yields delta ChatResponse objects as they are received.
249
+ *
250
+ * @param response - The fetch response object.
251
+ * @param startTime - The start time of the request for usage calculation.
252
+ * @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
253
+ */
254
+ async *_parseDeepSeekStreamedResponse(
255
+ response: Response,
256
+ startTime: number
257
+ ): AsyncGenerator<ChatResponse, ChatResponse> {
258
+ const asyncGenerator = _parseStreamedResponse<_DeepSeekStreamChunk>(response);
259
+
260
+ let accText: string = '';
261
+ let accThinking: string = '';
262
+ // Store accumulated input strings for each tool call
263
+ const accToolInputs: Map<string, string> = new Map();
264
+ // Store tool call metadata (id, name)
265
+ const toolCallMeta: Map<string, { id: string; name: string }> = new Map();
266
+ let lastUsage: ChatUsage | undefined = undefined;
267
+
268
+ for await (const jsonObj of asyncGenerator) {
269
+ if (jsonObj.choices && jsonObj.choices.length > 0) {
270
+ const choice = jsonObj.choices[0];
271
+
272
+ // Delta data for this chunk
273
+ let deltaText: string = '';
274
+ let deltaThinking: string = '';
275
+ const deltaToolCalls: Map<string, ToolCallBlock> = new Map();
276
+
277
+ if (choice.delta?.content) {
278
+ deltaText = choice.delta.content;
279
+ accText += deltaText;
280
+ }
281
+ if (choice.delta?.reasoning_content) {
282
+ deltaThinking = choice.delta.reasoning_content;
283
+ accThinking += deltaThinking;
284
+ }
285
+ if (choice.delta?.tool_calls) {
286
+ choice.delta.tool_calls.forEach(toolCall => {
287
+ const index = toolCall.index.toString();
288
+
289
+ // Initialize metadata if not exists
290
+ if (!toolCallMeta.has(index)) {
291
+ toolCallMeta.set(index, { id: '', name: '' });
292
+ }
293
+ if (!accToolInputs.has(index)) {
294
+ accToolInputs.set(index, '');
295
+ }
296
+
297
+ // Update the tool use id
298
+ if (toolCall.id) {
299
+ toolCallMeta.get(index)!.id = toolCall.id;
300
+ }
301
+ // Update the tool use name
302
+ if (toolCall.function?.name) {
303
+ toolCallMeta.get(index)!.name = toolCall.function.name;
304
+ }
305
+ // Update the tool use input
306
+ if (toolCall.function?.arguments) {
307
+ const deltaArgs = toolCall.function.arguments;
308
+ accToolInputs.set(index, accToolInputs.get(index)! + deltaArgs);
309
+
310
+ // Create delta tool call with incremental input
311
+ const meta = toolCallMeta.get(index)!;
312
+ deltaToolCalls.set(index, {
313
+ type: 'tool_call',
314
+ id: meta.id,
315
+ name: meta.name,
316
+ input: deltaArgs,
317
+ });
318
+ }
319
+ });
320
+ }
321
+
322
+ // Create a delta ChatResponse object
323
+ const deltaBlocks = this._accDataToBlocks(deltaText, deltaThinking, deltaToolCalls);
324
+ lastUsage = jsonObj.usage
325
+ ? {
326
+ type: 'chat_usage',
327
+ inputTokens: jsonObj.usage.prompt_tokens || 0,
328
+ outputTokens: jsonObj.usage.completion_tokens || 0,
329
+ time: (Date.now() - startTime) / 1000,
330
+ }
331
+ : undefined;
332
+
333
+ yield {
334
+ type: 'chat',
335
+ id: crypto.randomUUID(),
336
+ createdAt: new Date().toISOString(),
337
+ content: deltaBlocks,
338
+ usage: lastUsage,
339
+ } as ChatResponse;
340
+ }
341
+ }
342
+ // Build final tool calls with complete JSON strings
343
+ const finalToolCalls: Map<string, ToolCallBlock> = new Map();
344
+ toolCallMeta.forEach((meta, index) => {
345
+ finalToolCalls.set(index, {
346
+ type: 'tool_call',
347
+ id: meta.id,
348
+ name: meta.name,
349
+ input: accToolInputs.get(index) || '{}',
350
+ });
351
+ });
352
+
353
+ const blocks = this._accDataToBlocks(accText, accThinking, finalToolCalls);
354
+ return {
355
+ type: 'chat',
356
+ id: crypto.randomUUID(),
357
+ createdAt: new Date().toISOString(),
358
+ content: blocks,
359
+ usage: lastUsage,
360
+ } as ChatResponse;
361
+ }
362
+
363
+ /**
364
+ * Convert data into blocks
365
+ *
366
+ * @param text - The text response from the llm API
367
+ * @param thinking - The thinking response
368
+ * @param toolCalls - The tool calls
369
+ * @returns An array of blocks
370
+ */
371
+ _accDataToBlocks(
372
+ text: string,
373
+ thinking: string,
374
+ toolCalls: Map<string, ToolCallBlock>
375
+ ): (TextBlock | ThinkingBlock | ToolCallBlock)[] {
376
+ const blocks: (TextBlock | ThinkingBlock | ToolCallBlock)[] = [];
377
+ if (thinking) {
378
+ blocks.push({ id: crypto.randomUUID(), type: 'thinking', thinking: thinking });
379
+ }
380
+ if (text) {
381
+ blocks.push({ id: crypto.randomUUID(), type: 'text', text: text });
382
+ }
383
+ // Push the tool calls into the blocks
384
+ if (toolCalls.size > 0) {
385
+ toolCalls.forEach(value => {
386
+ blocks.push(value);
387
+ });
388
+ }
389
+
390
+ return blocks;
391
+ }
392
+
393
+ /**
394
+ * Format the tool schemas to the expected API format for DeepSeek API.
395
+ * @param tools
396
+ * @returns The formatted tool schemas.
397
+ */
398
+ _formatToolSchemas(tools: ToolSchema[] | undefined): ToolSchema[] {
399
+ return tools || [];
400
+ }
401
+ }
@@ -0,0 +1,7 @@
1
+ export { ChatModelBase } from './base';
2
+ export { ChatResponse } from './response';
3
+ export { ChatUsage } from './usage';
4
+ export { DashScopeChatModel } from './dashscope-model';
5
+ export { DeepSeekChatModel } from './deepseek-model';
6
+ export { OllamaChatModel } from './ollama-model';
7
+ export { OpenAIChatModel } from './openai-model';