@ai-sdk/deepseek 3.0.0-beta.5 → 3.0.0-beta.55

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.
@@ -30,10 +30,10 @@ The DeepSeek provider is available via the `@ai-sdk/deepseek` module. You can in
30
30
 
31
31
  ## Provider Instance
32
32
 
33
- You can import the default provider instance `deepseek` from `@ai-sdk/deepseek`:
33
+ You can import the default provider instance `deepSeek` from `@ai-sdk/deepseek`:
34
34
 
35
35
  ```ts
36
- import { deepseek } from '@ai-sdk/deepseek';
36
+ import { deepSeek } from '@ai-sdk/deepseek';
37
37
  ```
38
38
 
39
39
  For custom configuration, you can import `createDeepSeek` and create a provider instance with your settings:
@@ -41,7 +41,7 @@ For custom configuration, you can import `createDeepSeek` and create a provider
41
41
  ```ts
42
42
  import { createDeepSeek } from '@ai-sdk/deepseek';
43
43
 
44
- const deepseek = createDeepSeek({
44
+ const deepSeek = createDeepSeek({
45
45
  apiKey: process.env.DEEPSEEK_API_KEY ?? '',
46
46
  });
47
47
  ```
@@ -71,11 +71,11 @@ You can use the following optional settings to customize the DeepSeek provider i
71
71
  You can create language models using a provider instance:
72
72
 
73
73
  ```ts
74
- import { deepseek } from '@ai-sdk/deepseek';
74
+ import { deepSeek } from '@ai-sdk/deepseek';
75
75
  import { generateText } from 'ai';
76
76
 
77
77
  const { text } = await generateText({
78
- model: deepseek('deepseek-chat'),
78
+ model: deepSeek('deepseek-chat'),
79
79
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
80
80
  });
81
81
  ```
@@ -83,9 +83,9 @@ const { text } = await generateText({
83
83
  You can also use the `.chat()` or `.languageModel()` factory methods:
84
84
 
85
85
  ```ts
86
- const model = deepseek.chat('deepseek-chat');
86
+ const model = deepSeek.chat('deepseek-chat');
87
87
  // or
88
- const model = deepseek.languageModel('deepseek-chat');
88
+ const model = deepSeek.languageModel('deepseek-chat');
89
89
  ```
90
90
 
91
91
  DeepSeek language models can be used in the `streamText` function
@@ -96,20 +96,32 @@ The following optional provider options are available for DeepSeek models:
96
96
  - `thinking` _object_
97
97
 
98
98
  Optional. Controls thinking mode (chain-of-thought reasoning). You can enable thinking mode either by using the `deepseek-reasoner` model or by setting this option.
99
-
100
- - `type`: `'enabled' | 'disabled'` - Enable or disable thinking mode.
101
-
102
- ```ts highlight="7-11"
103
- import { deepseek, type DeepSeekLanguageModelOptions } from '@ai-sdk/deepseek';
99
+ - `type`: `'adaptive' | 'enabled' | 'disabled'` - Enable, disable, or let the model decide (`adaptive`) when to think. See [DeepSeek's thinking mode docs](https://api-docs.deepseek.com/guides/thinking_mode).
100
+
101
+ - `reasoningEffort` _'low' | 'medium' | 'high' | 'xhigh' | 'max'_
102
+
103
+ Optional. Controls thinking strength for DeepSeek V4 reasoning models. Per
104
+ DeepSeek's docs, `low` and `medium` are mapped to `high`, and `xhigh` is
105
+ mapped to `max` server-side for compatibility with other providers. When
106
+ using the top-level `reasoning` setting, `minimal` is sent as `low`, and
107
+ `low`, `medium`, `high`, and `xhigh` pass through to DeepSeek's native
108
+ effort values.
109
+
110
+ ```ts highlight="7-12"
111
+ import {
112
+ deepSeek,
113
+ type DeepSeekLanguageModelChatOptions,
114
+ } from '@ai-sdk/deepseek';
104
115
  import { generateText } from 'ai';
105
116
 
106
117
  const { text, reasoning } = await generateText({
107
- model: deepseek('deepseek-chat'),
118
+ model: deepSeek('deepseek-chat'),
108
119
  prompt: 'How many "r"s are in the word "strawberry"?',
109
120
  providerOptions: {
110
121
  deepseek: {
111
122
  thinking: { type: 'enabled' },
112
- } satisfies DeepSeekLanguageModelOptions,
123
+ reasoningEffort: 'high',
124
+ } satisfies DeepSeekLanguageModelChatOptions,
113
125
  },
114
126
  });
115
127
  ```
@@ -119,15 +131,15 @@ const { text, reasoning } = await generateText({
119
131
  DeepSeek has reasoning support for the `deepseek-reasoner` model. The reasoning is exposed through streaming:
120
132
 
121
133
  ```ts
122
- import { deepseek } from '@ai-sdk/deepseek';
134
+ import { deepSeek } from '@ai-sdk/deepseek';
123
135
  import { streamText } from 'ai';
124
136
 
125
137
  const result = streamText({
126
- model: deepseek('deepseek-reasoner'),
138
+ model: deepSeek('deepseek-reasoner'),
127
139
  prompt: 'How many "r"s are in the word "strawberry"?',
128
140
  });
129
141
 
130
- for await (const part of result.fullStream) {
142
+ for await (const part of result.stream) {
131
143
  if (part.type === 'reasoning') {
132
144
  // This is the reasoning text
133
145
  console.log('Reasoning:', part.text);
@@ -146,11 +158,11 @@ on how to integrate reasoning into your chatbot.
146
158
  DeepSeek provides context caching on disk technology that can significantly reduce token costs for repeated content. You can access the cache hit/miss metrics through the `providerMetadata` property in the response:
147
159
 
148
160
  ```ts
149
- import { deepseek } from '@ai-sdk/deepseek';
161
+ import { deepSeek } from '@ai-sdk/deepseek';
150
162
  import { generateText } from 'ai';
151
163
 
152
164
  const result = await generateText({
153
- model: deepseek('deepseek-chat'),
165
+ model: deepSeek('deepseek-chat'),
154
166
  prompt: 'Your prompt here',
155
167
  });
156
168
 
package/internal.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/internal';
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@ai-sdk/deepseek",
3
- "version": "3.0.0-beta.5",
3
+ "version": "3.0.0-beta.55",
4
+ "type": "module",
4
5
  "license": "Apache-2.0",
5
6
  "sideEffects": false,
6
7
  "main": "./dist/index.js",
7
- "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist/**/*",
@@ -15,7 +15,8 @@
15
15
  "!src/**/__snapshots__",
16
16
  "!src/**/__fixtures__",
17
17
  "CHANGELOG.md",
18
- "README.md"
18
+ "README.md",
19
+ "internal.d.ts"
19
20
  ],
20
21
  "directories": {
21
22
  "doc": "./docs"
@@ -24,35 +25,42 @@
24
25
  "./package.json": "./package.json",
25
26
  ".": {
26
27
  "types": "./dist/index.d.ts",
27
- "import": "./dist/index.mjs",
28
- "require": "./dist/index.js"
28
+ "import": "./dist/index.js",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "./internal": {
32
+ "types": "./dist/internal/index.d.ts",
33
+ "import": "./dist/internal/index.js",
34
+ "default": "./dist/internal/index.js"
29
35
  }
30
36
  },
31
37
  "dependencies": {
32
- "@ai-sdk/provider": "4.0.0-beta.2",
33
- "@ai-sdk/provider-utils": "5.0.0-beta.4"
38
+ "@ai-sdk/provider": "4.0.0-beta.19",
39
+ "@ai-sdk/provider-utils": "5.0.0-beta.49"
34
40
  },
35
41
  "devDependencies": {
36
- "@types/node": "20.17.24",
37
- "tsup": "^8",
42
+ "@types/node": "22.19.19",
43
+ "tsup": "^8.5.1",
38
44
  "typescript": "5.8.3",
39
45
  "zod": "3.25.76",
40
- "@ai-sdk/test-server": "2.0.0-beta.0",
46
+ "@ai-sdk/test-server": "2.0.0-beta.7",
41
47
  "@vercel/ai-tsconfig": "0.0.0"
42
48
  },
43
49
  "peerDependencies": {
44
50
  "zod": "^3.25.76 || ^4.1.8"
45
51
  },
46
52
  "engines": {
47
- "node": ">=18"
53
+ "node": ">=22"
48
54
  },
49
55
  "publishConfig": {
50
- "access": "public"
56
+ "access": "public",
57
+ "provenance": true
51
58
  },
52
59
  "homepage": "https://ai-sdk.dev/docs",
53
60
  "repository": {
54
61
  "type": "git",
55
- "url": "git+https://github.com/vercel/ai.git"
62
+ "url": "https://github.com/vercel/ai",
63
+ "directory": "packages/deepseek"
56
64
  },
57
65
  "bugs": {
58
66
  "url": "https://github.com/vercel/ai/issues"
@@ -64,9 +72,7 @@
64
72
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
65
73
  "build:watch": "pnpm clean && tsup --watch",
66
74
  "clean": "del-cli dist docs *.tsbuildinfo",
67
- "lint": "eslint \"./**/*.ts*\"",
68
75
  "type-check": "tsc --build",
69
- "prettier-check": "prettier --check \"./**/*.ts*\"",
70
76
  "test": "pnpm test:node && pnpm test:edge",
71
77
  "test:update": "pnpm test:node -u",
72
78
  "test:watch": "vitest --config vitest.node.config.js",
@@ -1,20 +1,23 @@
1
- import {
1
+ import type {
2
2
  LanguageModelV4CallOptions,
3
3
  LanguageModelV4Prompt,
4
4
  SharedV4Warning,
5
5
  } from '@ai-sdk/provider';
6
- import { DeepSeekChatPrompt } from './deepseek-chat-api-types';
6
+ import type { DeepSeekChatPrompt } from './deepseek-chat-api-types';
7
7
 
8
8
  export function convertToDeepSeekChatMessages({
9
9
  prompt,
10
10
  responseFormat,
11
+ modelId,
11
12
  }: {
12
13
  prompt: LanguageModelV4Prompt;
13
14
  responseFormat: LanguageModelV4CallOptions['responseFormat'];
15
+ modelId: string;
14
16
  }): {
15
17
  messages: DeepSeekChatPrompt;
16
18
  warnings: Array<SharedV4Warning>;
17
19
  } {
20
+ const isDeepSeekV4 = modelId.includes('deepseek-v4');
18
21
  const messages: DeepSeekChatPrompt = [];
19
22
  const warnings: Array<SharedV4Warning> = [];
20
23
 
@@ -96,7 +99,8 @@ export function convertToDeepSeekChatMessages({
96
99
  break;
97
100
  }
98
101
  case 'reasoning': {
99
- if (index <= lastUserMessageIndex) {
102
+ // R1 must not receive prior reasoning; V4 requires it.
103
+ if (index <= lastUserMessageIndex && !isDeepSeekV4) {
100
104
  break;
101
105
  }
102
106
 
@@ -121,10 +125,12 @@ export function convertToDeepSeekChatMessages({
121
125
  }
122
126
  }
123
127
 
128
+ // V4 demands the field on every assistant turn — back-fill an empty
129
+ // string when the source message had no reasoning part at all.
124
130
  messages.push({
125
131
  role: 'assistant',
126
132
  content: text,
127
- reasoning_content: reasoning,
133
+ reasoning_content: reasoning ?? (isDeepSeekV4 ? '' : undefined),
128
134
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
129
135
  });
130
136
 
@@ -145,7 +151,7 @@ export function convertToDeepSeekChatMessages({
145
151
  contentValue = output.value;
146
152
  break;
147
153
  case 'execution-denied':
148
- contentValue = output.reason ?? 'Tool execution denied.';
154
+ contentValue = output.reason ?? 'Tool call execution denied.';
149
155
  break;
150
156
  case 'content':
151
157
  case 'json':
@@ -1,4 +1,4 @@
1
- import { LanguageModelV4Usage } from '@ai-sdk/provider';
1
+ import type { LanguageModelV4Usage } from '@ai-sdk/provider';
2
2
 
3
3
  export function convertDeepSeekUsage(
4
4
  usage:
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ // https://api-docs.deepseek.com/quick_start/pricing
4
+ export type DeepSeekChatModelId =
5
+ | 'deepseek-chat'
6
+ | 'deepseek-reasoner'
7
+ | (string & {});
8
+
9
+ export const deepseekLanguageModelChatOptions = z.object({
10
+ /**
11
+ * Type of thinking to use. Defaults to `enabled`.
12
+ *
13
+ * See https://api-docs.deepseek.com/guides/thinking_mode for the
14
+ * `adaptive` option, which lets the model decide when to think.
15
+ */
16
+ thinking: z
17
+ .object({
18
+ type: z.enum(['adaptive', 'enabled', 'disabled']).optional(),
19
+ })
20
+ .optional(),
21
+
22
+ /**
23
+ * Controls the thinking strength for DeepSeek V4 reasoning models.
24
+ *
25
+ * DeepSeek's API accepts `low`, `medium`, `high`, `xhigh`, and `max`.
26
+ * Per their docs, `low` and `medium` are mapped to `high`, and `xhigh`
27
+ * is mapped to `max` server-side for compatibility with other providers.
28
+ */
29
+ reasoningEffort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
30
+ });
31
+
32
+ export type DeepSeekLanguageModelChatOptions = z.infer<
33
+ typeof deepseekLanguageModelChatOptions
34
+ >;
@@ -1,6 +1,5 @@
1
- import {
1
+ import type {
2
2
  APICallError,
3
- InvalidResponseDataError,
4
3
  LanguageModelV4,
5
4
  LanguageModelV4CallOptions,
6
5
  LanguageModelV4Content,
@@ -8,42 +7,49 @@ import {
8
7
  LanguageModelV4GenerateResult,
9
8
  LanguageModelV4StreamPart,
10
9
  LanguageModelV4StreamResult,
10
+ SharedV4Warning,
11
11
  } from '@ai-sdk/provider';
12
12
  import {
13
13
  combineHeaders,
14
14
  createEventSourceResponseHandler,
15
15
  createJsonErrorResponseHandler,
16
16
  createJsonResponseHandler,
17
- FetchFunction,
18
17
  generateId,
19
- InferSchema,
20
- isParsableJson,
18
+ isCustomReasoning,
19
+ mapReasoningToProviderEffort,
21
20
  parseProviderOptions,
22
- ParseResult,
23
21
  postJsonToApi,
24
- ResponseHandler,
22
+ serializeModelOptions,
23
+ StreamingToolCallTracker,
24
+ WORKFLOW_SERIALIZE,
25
+ WORKFLOW_DESERIALIZE,
26
+ type FetchFunction,
27
+ type InferSchema,
28
+ type ParseResult,
29
+ type ResponseHandler,
25
30
  } from '@ai-sdk/provider-utils';
26
31
  import { convertToDeepSeekChatMessages } from './convert-to-deepseek-chat-messages';
27
32
  import { convertDeepSeekUsage } from './convert-to-deepseek-usage';
28
33
  import {
29
34
  deepseekChatChunkSchema,
30
35
  deepseekChatResponseSchema,
31
- DeepSeekChatTokenUsage,
32
36
  deepSeekErrorSchema,
37
+ type DeepSeekChatTokenUsage,
33
38
  } from './deepseek-chat-api-types';
34
39
  import {
35
- DeepSeekChatModelId,
36
- deepseekLanguageModelOptions,
37
- } from './deepseek-chat-options';
40
+ deepseekLanguageModelChatOptions,
41
+ type DeepSeekChatModelId,
42
+ } from './deepseek-chat-language-model-options';
38
43
  import { prepareTools } from './deepseek-prepare-tools';
39
44
  import { getResponseMetadata } from './get-response-metadata';
40
45
  import { mapDeepSeekFinishReason } from './map-deepseek-finish-reason';
41
46
 
42
47
  export type DeepSeekChatConfig = {
43
48
  provider: string;
44
- headers: () => Record<string, string | undefined>;
49
+ headers?: () => Record<string, string | undefined>;
45
50
  url: (options: { modelId: string; path: string }) => string;
46
51
  fetch?: FetchFunction;
52
+ supportsThinking?: boolean;
47
53
  };
48
54
 
49
55
  export class DeepSeekChatLanguageModel implements LanguageModelV4 {
@@ -55,6 +61,20 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
55
61
  private readonly config: DeepSeekChatConfig;
56
62
  private readonly failedResponseHandler: ResponseHandler<APICallError>;
57
63
 
64
+ static [WORKFLOW_SERIALIZE](model: DeepSeekChatLanguageModel) {
65
+ return serializeModelOptions({
66
+ modelId: model.modelId,
67
+ config: model.config,
68
+ });
69
+ }
70
+
71
+ static [WORKFLOW_DESERIALIZE](options: {
72
+ modelId: DeepSeekChatModelId;
73
+ config: DeepSeekChatConfig;
74
+ }) {
75
+ return new DeepSeekChatLanguageModel(options.modelId, options.config);
76
+ }
77
+
58
78
  constructor(modelId: DeepSeekChatModelId, config: DeepSeekChatConfig) {
59
79
  this.modelId = modelId;
60
80
  this.config = config;
@@ -82,6 +102,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
82
102
  topK,
83
103
  frequencyPenalty,
84
104
  presencePenalty,
105
+ reasoning,
85
106
  providerOptions,
86
107
  stopSequences,
87
108
  responseFormat,
@@ -93,20 +114,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
93
114
  (await parseProviderOptions({
94
115
  provider: this.providerOptionsName,
95
116
  providerOptions,
96
- schema: deepseekLanguageModelOptions,
117
+ schema: deepseekLanguageModelChatOptions,
97
118
  })) ?? {};
98
119
 
99
120
  const { messages, warnings } = convertToDeepSeekChatMessages({
100
121
  prompt,
101
122
  responseFormat,
123
+ modelId: this.modelId,
102
124
  });
125
+ const allWarnings: SharedV4Warning[] = [...warnings];
103
126
 
104
127
  if (topK != null) {
105
- warnings.push({ type: 'unsupported', feature: 'topK' });
128
+ allWarnings.push({ type: 'unsupported', feature: 'topK' });
106
129
  }
107
130
 
108
131
  if (seed != null) {
109
- warnings.push({ type: 'unsupported', feature: 'seed' });
132
+ allWarnings.push({ type: 'unsupported', feature: 'seed' });
110
133
  }
111
134
 
112
135
  const {
@@ -118,6 +141,31 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
118
141
  toolChoice,
119
142
  });
120
143
 
144
+ const thinking =
145
+ this.config.supportsThinking === false
146
+ ? undefined
147
+ : deepseekOptions.thinking?.type != null
148
+ ? { type: deepseekOptions.thinking.type }
149
+ : isCustomReasoning(reasoning)
150
+ ? { type: reasoning === 'none' ? 'disabled' : 'enabled' }
151
+ : undefined;
152
+
153
+ const reasoningEffort =
154
+ deepseekOptions.reasoningEffort ??
155
+ (isCustomReasoning(reasoning) && reasoning !== 'none'
156
+ ? mapReasoningToProviderEffort({
157
+ reasoning,
158
+ effortMap: {
159
+ minimal: 'low',
160
+ low: 'low',
161
+ medium: 'medium',
162
+ high: 'high',
163
+ xhigh: 'max',
164
+ },
165
+ warnings: allWarnings,
166
+ })
167
+ : undefined);
168
+
121
169
  return {
122
170
  args: {
123
171
  model: this.modelId,
@@ -132,12 +180,13 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
132
180
  messages,
133
181
  tools: deepseekTools,
134
182
  tool_choice: deepseekToolChoices,
135
- thinking:
136
- deepseekOptions.thinking?.type != null
137
- ? { type: deepseekOptions.thinking.type }
138
- : undefined,
183
+ thinking,
184
+ ...(thinking?.type !== 'disabled' &&
185
+ reasoningEffort != null && {
186
+ reasoning_effort: reasoningEffort,
187
+ }),
139
188
  },
140
- warnings: [...warnings, ...toolWarnings],
189
+ warnings: [...allWarnings, ...toolWarnings],
141
190
  };
142
191
  }
143
192
 
@@ -155,7 +204,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
155
204
  path: '/chat/completions',
156
205
  modelId: this.modelId,
157
206
  }),
158
- headers: combineHeaders(this.config.headers(), options.headers),
207
+ headers: combineHeaders(this.config.headers?.(), options.headers),
159
208
  body: args,
160
209
  failedResponseHandler: this.failedResponseHandler,
161
210
  successfulResponseHandler: createJsonResponseHandler(
@@ -234,7 +283,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
234
283
  path: '/chat/completions',
235
284
  modelId: this.modelId,
236
285
  }),
237
- headers: combineHeaders(this.config.headers(), options.headers),
286
+ headers: combineHeaders(this.config.headers?.(), options.headers),
238
287
  body,
239
288
  failedResponseHandler: this.failedResponseHandler,
240
289
  successfulResponseHandler: createEventSourceResponseHandler(
@@ -244,15 +293,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
244
293
  fetch: this.config.fetch,
245
294
  });
246
295
 
247
- const toolCalls: Array<{
248
- id: string;
249
- type: 'function';
250
- function: {
251
- name: string;
252
- arguments: string;
253
- };
254
- hasFinished: boolean;
255
- }> = [];
296
+ let toolCallTracker: StreamingToolCallTracker;
256
297
 
257
298
  let finishReason: LanguageModelV4FinishReason = {
258
299
  unified: 'other',
@@ -271,6 +312,9 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
271
312
  LanguageModelV4StreamPart
272
313
  >({
273
314
  start(controller) {
315
+ toolCallTracker = new StreamingToolCallTracker(controller, {
316
+ generateId,
317
+ });
274
318
  controller.enqueue({ type: 'stream-start', warnings });
275
319
  },
276
320
 
@@ -374,113 +418,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
374
418
  }
375
419
 
376
420
  for (const toolCallDelta of delta.tool_calls) {
377
- const index = toolCallDelta.index;
378
-
379
- if (toolCalls[index] == null) {
380
- if (toolCallDelta.id == null) {
381
- throw new InvalidResponseDataError({
382
- data: toolCallDelta,
383
- message: `Expected 'id' to be a string.`,
384
- });
385
- }
386
-
387
- if (toolCallDelta.function?.name == null) {
388
- throw new InvalidResponseDataError({
389
- data: toolCallDelta,
390
- message: `Expected 'function.name' to be a string.`,
391
- });
392
- }
393
-
394
- controller.enqueue({
395
- type: 'tool-input-start',
396
- id: toolCallDelta.id,
397
- toolName: toolCallDelta.function.name,
398
- });
399
-
400
- toolCalls[index] = {
401
- id: toolCallDelta.id,
402
- type: 'function',
403
- function: {
404
- name: toolCallDelta.function.name,
405
- arguments: toolCallDelta.function.arguments ?? '',
406
- },
407
- hasFinished: false,
408
- };
409
-
410
- const toolCall = toolCalls[index];
411
-
412
- if (
413
- toolCall.function?.name != null &&
414
- toolCall.function?.arguments != null
415
- ) {
416
- // send delta if the argument text has already started:
417
- if (toolCall.function.arguments.length > 0) {
418
- controller.enqueue({
419
- type: 'tool-input-delta',
420
- id: toolCall.id,
421
- delta: toolCall.function.arguments,
422
- });
423
- }
424
-
425
- // check if tool call is complete
426
- // (some providers send the full tool call in one chunk):
427
- if (isParsableJson(toolCall.function.arguments)) {
428
- controller.enqueue({
429
- type: 'tool-input-end',
430
- id: toolCall.id,
431
- });
432
-
433
- controller.enqueue({
434
- type: 'tool-call',
435
- toolCallId: toolCall.id ?? generateId(),
436
- toolName: toolCall.function.name,
437
- input: toolCall.function.arguments,
438
- });
439
- toolCall.hasFinished = true;
440
- }
441
- }
442
-
443
- continue;
444
- }
445
-
446
- // existing tool call, merge if not finished
447
- const toolCall = toolCalls[index];
448
-
449
- if (toolCall.hasFinished) {
450
- continue;
451
- }
452
-
453
- if (toolCallDelta.function?.arguments != null) {
454
- toolCall.function!.arguments +=
455
- toolCallDelta.function?.arguments ?? '';
456
- }
457
-
458
- // send delta
459
- controller.enqueue({
460
- type: 'tool-input-delta',
461
- id: toolCall.id,
462
- delta: toolCallDelta.function.arguments ?? '',
463
- });
464
-
465
- // check if tool call is complete
466
- if (
467
- toolCall.function?.name != null &&
468
- toolCall.function?.arguments != null &&
469
- isParsableJson(toolCall.function.arguments)
470
- ) {
471
- controller.enqueue({
472
- type: 'tool-input-end',
473
- id: toolCall.id,
474
- });
475
-
476
- controller.enqueue({
477
- type: 'tool-call',
478
- toolCallId: toolCall.id ?? generateId(),
479
- toolName: toolCall.function.name,
480
- input: toolCall.function.arguments,
481
- });
482
- toolCall.hasFinished = true;
483
- }
421
+ toolCallTracker.processDelta(toolCallDelta);
484
422
  }
485
423
  }
486
424
  },
@@ -494,22 +432,7 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
494
432
  controller.enqueue({ type: 'text-end', id: 'txt-0' });
495
433
  }
496
434
 
497
- // go through all tool calls and send the ones that are not finished
498
- for (const toolCall of toolCalls.filter(
499
- toolCall => !toolCall.hasFinished,
500
- )) {
501
- controller.enqueue({
502
- type: 'tool-input-end',
503
- id: toolCall.id,
504
- });
505
-
506
- controller.enqueue({
507
- type: 'tool-call',
508
- toolCallId: toolCall.id ?? generateId(),
509
- toolName: toolCall.function.name,
510
- input: toolCall.function.arguments,
511
- });
512
- }
435
+ toolCallTracker.flush();
513
436
 
514
437
  controller.enqueue({
515
438
  type: 'finish',
@@ -1,5 +1,8 @@
1
- import { LanguageModelV4CallOptions, SharedV4Warning } from '@ai-sdk/provider';
2
- import {
1
+ import type {
2
+ LanguageModelV4CallOptions,
3
+ SharedV4Warning,
4
+ } from '@ai-sdk/provider';
5
+ import type {
3
6
  DeepSeekFunctionTool,
4
7
  DeepSeekToolChoice,
5
8
  } from './deepseek-chat-api-types';
@@ -1,4 +1,4 @@
1
- import { LanguageModelV4FinishReason } from '@ai-sdk/provider';
1
+ import type { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
3
  export function mapDeepSeekFinishReason(
4
4
  finishReason: string | null | undefined,