@ai-sdk/deepseek 2.0.58 → 2.0.60

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.
@@ -75,7 +75,7 @@ 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-v4-flash'),
79
79
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
80
80
  });
81
81
  ```
@@ -83,39 +83,73 @@ 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-v4-flash');
87
87
  // or
88
- const model = deepseek.languageModel('deepseek-chat');
88
+ const model = deepseek.languageModel('deepseek-v4-flash');
89
89
  ```
90
90
 
91
91
  DeepSeek language models can be used in the `streamText` function
92
92
  (see [AI SDK Core](/docs/ai-sdk-core)).
93
93
 
94
+ DeepSeek retired the `deepseek-chat` and `deepseek-reasoner` aliases on July 24,
95
+ 2026. Use `deepseek-v4-flash` or `deepseek-v4-pro` for the current API. Custom
96
+ and legacy model IDs remain accepted as strings for compatibility with custom
97
+ endpoints.
98
+
94
99
  The following optional provider options are available for DeepSeek models:
95
100
 
101
+ - `logprobs` _boolean_
102
+
103
+ Optional. Returns log probabilities for generated content and reasoning
104
+ tokens in `providerMetadata.deepseek.logprobs`.
105
+
106
+ - `topLogprobs` _number_
107
+
108
+ Optional. Returns the specified number of most likely tokens at each token
109
+ position. Accepts values from `0` through `20` and automatically enables
110
+ `logprobs`.
111
+
112
+ - `userId` _string_
113
+
114
+ Optional. An opaque end-user identifier that DeepSeek uses for content-safety
115
+ tracing, KV-cache isolation, and scheduling isolation. The value must match
116
+ `^[a-zA-Z0-9_-]+$` and contain at most 512 characters. Do not include names,
117
+ email addresses, or other private user information.
118
+
96
119
  - `thinking` _object_
97
120
 
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
- - `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).
121
+ Optional. Controls thinking mode (chain-of-thought reasoning) for DeepSeek V4 models.
122
+ - `type`: `'enabled' | 'disabled'` - Enable or disable thinking mode. See [DeepSeek's thinking mode docs](https://api-docs.deepseek.com/guides/thinking_mode).
123
+
124
+ - `reasoningEffort` _'low' | 'high' | 'max'_
125
+
126
+ Optional. Controls thinking strength for DeepSeek V4 reasoning models.
127
+ `medium` is sent as `high`, and `xhigh` is sent as `max`. A compatibility
128
+ warning is returned whenever the requested value is mapped.
100
129
 
101
- - `reasoningEffort` _'low' | 'medium' | 'high' | 'xhigh' | 'max'_
130
+ For backwards compatibility, legacy provider options supplied at runtime are
131
+ also mapped to documented values: `thinking.type: 'adaptive'` becomes
132
+ `'enabled'`, `reasoningEffort: 'medium'` becomes `'high'`, and
133
+ `reasoningEffort: 'xhigh'` becomes `'max'`. Each mapping returns a compatibility
134
+ warning so callers can migrate to a canonical value.
102
135
 
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. Ignored when thinking is explicitly disabled.
136
+ DeepSeek has deprecated the top-level `frequencyPenalty` and `presencePenalty`
137
+ settings. The provider omits these settings and returns a deprecation warning
138
+ when they are used. `temperature` and `topP` have no effect while thinking is
139
+ enabled, including the default thinking mode for DeepSeek V4 models, so the
140
+ provider omits them with an unsupported warning. Explicitly set
141
+ `thinking.type` to `'disabled'` to use `temperature` and `topP`.
109
142
 
110
- ```ts highlight="7-12"
143
+ ```ts highlight="7-13"
111
144
  import { deepseek, type DeepSeekLanguageModelOptions } from '@ai-sdk/deepseek';
112
145
  import { generateText } from 'ai';
113
146
 
114
147
  const { text, reasoning } = await generateText({
115
- model: deepseek('deepseek-chat'),
148
+ model: deepseek('deepseek-v4-flash'),
116
149
  prompt: 'How many "r"s are in the word "strawberry"?',
117
150
  providerOptions: {
118
151
  deepseek: {
152
+ userId: 'tenant_123-user',
119
153
  thinking: { type: 'enabled' },
120
154
  reasoningEffort: 'high',
121
155
  } satisfies DeepSeekLanguageModelOptions,
@@ -123,16 +157,106 @@ const { text, reasoning } = await generateText({
123
157
  });
124
158
  ```
125
159
 
160
+ ### Message Names
161
+
162
+ DeepSeek supports optional participant names on system, user, and assistant
163
+ messages. Set `providerOptions.deepseek.name` on each message that should
164
+ include a name:
165
+
166
+ ```ts
167
+ import {
168
+ deepSeek,
169
+ type DeepSeekMessageProviderOptions,
170
+ } from '@ai-sdk/deepseek';
171
+ import { generateText } from 'ai';
172
+
173
+ const { text } = await generateText({
174
+ model: deepSeek('deepseek-chat'),
175
+ instructions: {
176
+ role: 'system',
177
+ content: 'Help the customer plan a short trip.',
178
+ providerOptions: {
179
+ deepseek: {
180
+ name: 'travel_planner',
181
+ } satisfies DeepSeekMessageProviderOptions,
182
+ },
183
+ },
184
+ messages: [
185
+ {
186
+ role: 'user',
187
+ content: 'I want to visit Lisbon for a weekend.',
188
+ providerOptions: {
189
+ deepseek: {
190
+ name: 'customer',
191
+ } satisfies DeepSeekMessageProviderOptions,
192
+ },
193
+ },
194
+ {
195
+ role: 'assistant',
196
+ content: 'What kinds of activities do you enjoy?',
197
+ providerOptions: {
198
+ deepseek: {
199
+ name: 'travel_planner',
200
+ } satisfies DeepSeekMessageProviderOptions,
201
+ },
202
+ },
203
+ {
204
+ role: 'user',
205
+ content: 'Food, architecture, and walking.',
206
+ providerOptions: {
207
+ deepseek: {
208
+ name: 'customer',
209
+ } satisfies DeepSeekMessageProviderOptions,
210
+ },
211
+ },
212
+ ],
213
+ });
214
+ ```
215
+
216
+ The same message option works with `streamText`:
217
+
218
+ ```ts
219
+ import {
220
+ deepSeek,
221
+ type DeepSeekMessageProviderOptions,
222
+ } from '@ai-sdk/deepseek';
223
+ import { streamText } from 'ai';
224
+
225
+ const result = streamText({
226
+ model: deepSeek('deepseek-chat'),
227
+ messages: [
228
+ {
229
+ role: 'user',
230
+ content: 'Suggest a name for my neighborhood book club.',
231
+ providerOptions: {
232
+ deepseek: {
233
+ name: 'organizer',
234
+ } satisfies DeepSeekMessageProviderOptions,
235
+ },
236
+ },
237
+ ],
238
+ });
239
+
240
+ for await (const textPart of result.textStream) {
241
+ process.stdout.write(textPart);
242
+ }
243
+ ```
244
+
245
+ Names are omitted when the option is not set. The `name` value must be a
246
+ string. DeepSeek does not support names on tool messages, so the provider
247
+ ignores that placement and returns an unsupported-feature warning. Avoid
248
+ including unnecessary personal or identifying information in message names.
249
+
126
250
  ### Reasoning
127
251
 
128
- DeepSeek has reasoning support for the `deepseek-reasoner` model. The reasoning is exposed through streaming:
252
+ DeepSeek V4 models support reasoning. The reasoning is exposed through streaming:
129
253
 
130
254
  ```ts
131
255
  import { deepseek } from '@ai-sdk/deepseek';
132
256
  import { streamText } from 'ai';
133
257
 
134
258
  const result = streamText({
135
- model: deepseek('deepseek-reasoner'),
259
+ model: deepseek('deepseek-v4-pro'),
136
260
  prompt: 'How many "r"s are in the word "strawberry"?',
137
261
  });
138
262
 
@@ -150,39 +274,186 @@ for await (const part of result.fullStream) {
150
274
  See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
151
275
  on how to integrate reasoning into your chatbot.
152
276
 
153
- ### Cache Token Usage
277
+ ### Chat Prefix Completion
154
278
 
155
- 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:
279
+ DeepSeek's beta [chat prefix completion](https://api-docs.deepseek.com/guides/chat_prefix_completion/)
280
+ continues the content of a final assistant message. Create a provider with a
281
+ beta base URL and set `prefix: true` in that assistant message's provider
282
+ options:
283
+
284
+ ```ts highlight="8,20-26"
285
+ import {
286
+ createDeepSeek,
287
+ type DeepSeekAssistantMessageProviderOptions,
288
+ } from '@ai-sdk/deepseek';
289
+ import { generateText } from 'ai';
290
+
291
+ const deepSeek = createDeepSeek({
292
+ baseURL: 'https://api.deepseek.com/beta',
293
+ });
294
+
295
+ const { text } = await generateText({
296
+ model: deepSeek('deepseek-v4-flash'),
297
+ messages: [
298
+ {
299
+ role: 'user',
300
+ content: 'Write a short sentence about the color of the sky.',
301
+ },
302
+ {
303
+ role: 'assistant',
304
+ content: 'The sky is',
305
+ providerOptions: {
306
+ deepseek: {
307
+ prefix: true,
308
+ } satisfies DeepSeekAssistantMessageProviderOptions,
309
+ },
310
+ },
311
+ ],
312
+ });
313
+ ```
314
+
315
+ The prefixed message must be an assistant message and the final message in the
316
+ prompt. The configured `baseURL` must end in `/beta`, including when using a
317
+ proxy. Invalid placement or a non-beta base URL causes the request to fail
318
+ before it is sent.
319
+
320
+ <Note>
321
+ Chat prefix completion is a DeepSeek beta feature and its behavior may change.
322
+ </Note>
323
+
324
+ ### Strict Tool Calls
325
+
326
+ DeepSeek's strict tool-call mode is a beta feature. Create the provider with a
327
+ beta base URL and set `strict: true` on every function tool in the request:
328
+
329
+ ```ts highlight="5,14"
330
+ import { createDeepSeek } from '@ai-sdk/deepseek';
331
+ import { generateText, tool } from 'ai';
332
+ import { z } from 'zod';
333
+
334
+ const deepSeek = createDeepSeek({
335
+ baseURL: 'https://api.deepseek.com/beta',
336
+ });
337
+
338
+ const result = await generateText({
339
+ model: deepSeek('deepseek-chat'),
340
+ prompt: 'What is the weather in San Francisco?',
341
+ tools: {
342
+ weather: tool({
343
+ description: 'Get the weather for a location.',
344
+ inputSchema: z.object({ location: z.string() }),
345
+ strict: true,
346
+ execute: async ({ location }) => ({ location, temperature: 18 }),
347
+ }),
348
+ },
349
+ });
350
+ ```
351
+
352
+ Strict tools fail locally when the base URL does not end in `/beta`. When any
353
+ function tool is strict, every function tool in the same request must set
354
+ `strict: true`.
355
+
356
+ ### Provider Metadata
357
+
358
+ DeepSeek exposes the response system fingerprint and context cache usage through
359
+ the `providerMetadata` property:
156
360
 
157
361
  ```ts
158
362
  import { deepseek } from '@ai-sdk/deepseek';
159
363
  import { generateText } from 'ai';
160
364
 
161
365
  const result = await generateText({
162
- model: deepseek('deepseek-chat'),
366
+ model: deepseek('deepseek-v4-flash'),
163
367
  prompt: 'Your prompt here',
164
368
  });
165
369
 
166
370
  console.log(result.providerMetadata);
167
- // Example output: { deepseek: { promptCacheHitTokens: 1856, promptCacheMissTokens: 5 } }
371
+ // Example output:
372
+ // {
373
+ // deepseek: {
374
+ // systemFingerprint: 'fp_eaab8d114b_prod0820_fp8_kvcache',
375
+ // promptCacheHitTokens: 1856,
376
+ // promptCacheMissTokens: 5,
377
+ // },
378
+ // }
168
379
  ```
169
380
 
170
- The metrics include:
381
+ The metadata includes:
171
382
 
383
+ - `systemFingerprint`: The backend configuration fingerprint for the response
172
384
  - `promptCacheHitTokens`: Number of input tokens that were cached
173
385
  - `promptCacheMissTokens`: Number of input tokens that were not cached
174
386
 
387
+ For streamed responses, the latest non-null fingerprint from the response
388
+ chunks is returned.
389
+
175
390
  <Note>
176
391
  For more details about DeepSeek's caching system, see the [DeepSeek caching
177
392
  documentation](https://api-docs.deepseek.com/guides/kv_cache#checking-cache-hit-status).
178
393
  </Note>
179
394
 
395
+ ### Chat Response Metadata
396
+
397
+ DeepSeek preserves provider-specific response fields in
398
+ `providerMetadata.deepseek` for generated and streamed responses:
399
+
400
+ - `responseObject`: `chat.completion` or `chat.completion.chunk`
401
+ - `choiceIndex`: the selected response choice index
402
+ - `messageRole`: the response message role, when supplied
403
+ - `toolCallTypes`: the tool-call type for each returned call
404
+
405
+ These fields remain in provider metadata because they are specific to
406
+ DeepSeek's Chat Completions response rather than shared AI SDK result fields.
407
+
408
+ ### Vision File Parts
409
+
410
+ For inline images or image URLs, use file-part provider options to select the
411
+ image processing detail. DeepSeek supports `low`, `high`, `original`, and
412
+ `auto`:
413
+
414
+ ```ts highlight="2,13-17"
415
+ import {
416
+ deepseek,
417
+ type DeepSeekFilePartProviderOptions,
418
+ } from '@ai-sdk/deepseek';
419
+ import { generateText } from 'ai';
420
+
421
+ const { text } = await generateText({
422
+ model: deepseek('deepseek-v4-flash-vision-exp'),
423
+ messages: [
424
+ {
425
+ role: 'user',
426
+ content: [
427
+ { type: 'text', text: 'Describe this image.' },
428
+ {
429
+ type: 'file',
430
+ data: new URL('https://example.com/image.webp'),
431
+ mediaType: 'image/webp',
432
+ providerOptions: {
433
+ deepseek: {
434
+ imageDetail: 'low',
435
+ } satisfies DeepSeekFilePartProviderOptions,
436
+ },
437
+ },
438
+ ],
439
+ },
440
+ ],
441
+ });
442
+ ```
443
+
444
+ Set `fileData: true` on an inline image file part to use DeepSeek's
445
+ `file_data` content-part representation. This preserves the file part's
446
+ `filename`. `fileData` cannot be used with image URLs or `imageDetail`.
447
+
448
+ DeepSeek accepts JPEG, PNG, GIF, and WebP image inputs. HTTP image URLs can be
449
+ at most 8,192 characters.
450
+
180
451
  ## Model Capabilities
181
452
 
182
453
  | Model | Text Generation | Object Generation | Image Input | Tool Usage | Tool Streaming |
183
454
  | ------------------------------ | ------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
184
- | `deepseek-chat` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
185
- | `deepseek-reasoner` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
455
+ | `deepseek-v4-flash` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
456
+ | `deepseek-v4-pro` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
186
457
  | `deepseek-v4-flash-vision-exp` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
187
458
 
188
459
  <Note>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/deepseek",
3
- "version": "2.0.58",
3
+ "version": "2.0.60",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@ai-sdk/provider": "3.0.15",
40
- "@ai-sdk/provider-utils": "4.0.47"
40
+ "@ai-sdk/provider-utils": "4.0.49"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -1,28 +1,44 @@
1
- import type {
2
- LanguageModelV3CallOptions,
3
- LanguageModelV3Prompt,
4
- SharedV3Warning,
1
+ import {
2
+ InvalidPromptError,
3
+ UnsupportedFunctionalityError,
4
+ type LanguageModelV3CallOptions,
5
+ type LanguageModelV3Prompt,
6
+ type SharedV3Warning,
5
7
  } from '@ai-sdk/provider';
6
- import { convertToBase64 } from '@ai-sdk/provider-utils';
8
+ import { convertToBase64, parseProviderOptions } from '@ai-sdk/provider-utils';
7
9
  import type {
8
10
  DeepSeekChatPrompt,
9
11
  DeepSeekContentPart,
10
12
  } from './deepseek-chat-api-types';
13
+ import { deepseekAssistantMessageProviderOptions } from './deepseek-chat-options';
14
+ import { deepseekFilePartProviderOptions } from './deepseek-file-part-options';
15
+
16
+ const supportedImageMediaTypes = new Set([
17
+ 'image/gif',
18
+ 'image/jpeg',
19
+ 'image/jpg',
20
+ 'image/png',
21
+ 'image/webp',
22
+ ]);
11
23
 
12
- export function convertToDeepSeekChatMessages({
24
+ export async function convertToDeepSeekChatMessages({
13
25
  prompt,
14
26
  responseFormat,
15
27
  modelId,
28
+ providerOptionsName = 'deepseek',
29
+ supportsAssistantPrefixCompletion = false,
16
30
  supportsStructuredOutputs = false,
17
31
  }: {
18
32
  prompt: LanguageModelV3Prompt;
19
33
  responseFormat: LanguageModelV3CallOptions['responseFormat'];
20
34
  modelId: string;
35
+ providerOptionsName?: string;
36
+ supportsAssistantPrefixCompletion?: boolean;
21
37
  supportsStructuredOutputs?: boolean;
22
- }): {
38
+ }): Promise<{
23
39
  messages: DeepSeekChatPrompt;
24
40
  warnings: Array<SharedV3Warning>;
25
- } {
41
+ }> {
26
42
  const isDeepSeekV4 = modelId.includes('deepseek-v4');
27
43
  const messages: DeepSeekChatPrompt = [];
28
44
  const warnings: Array<SharedV3Warning> = [];
@@ -59,12 +75,34 @@ export function convertToDeepSeekChatMessages({
59
75
  }
60
76
 
61
77
  let index = -1;
62
- for (const { role, content } of prompt) {
78
+ for (const { role, content, providerOptions } of prompt) {
63
79
  index++;
64
80
 
81
+ // The assistant schema extends the common message schema, so one parse
82
+ // validates names for every role and the assistant-only prefix option.
83
+ const deepseekMessageOptions = await parseProviderOptions({
84
+ provider: providerOptionsName,
85
+ providerOptions,
86
+ schema: deepseekAssistantMessageProviderOptions,
87
+ });
88
+
89
+ if (deepseekMessageOptions?.prefix === true && role !== 'assistant') {
90
+ throw new InvalidPromptError({
91
+ prompt,
92
+ message:
93
+ 'DeepSeek assistant prefix completion requires `prefix: true` on an assistant message.',
94
+ });
95
+ }
96
+
65
97
  switch (role) {
66
98
  case 'system': {
67
- messages.push({ role: 'system', content });
99
+ messages.push({
100
+ role: 'system',
101
+ content,
102
+ ...(deepseekMessageOptions?.name != null && {
103
+ name: deepseekMessageOptions.name,
104
+ }),
105
+ });
68
106
  break;
69
107
  }
70
108
 
@@ -88,7 +126,13 @@ export function convertToDeepSeekChatMessages({
88
126
  }
89
127
  }
90
128
 
91
- messages.push({ role: 'user', content: userContent });
129
+ messages.push({
130
+ role: 'user',
131
+ content: userContent,
132
+ ...(deepseekMessageOptions?.name != null && {
133
+ name: deepseekMessageOptions.name,
134
+ }),
135
+ });
92
136
  break;
93
137
  }
94
138
 
@@ -100,20 +144,86 @@ export function convertToDeepSeekChatMessages({
100
144
  part.type === 'file' &&
101
145
  (part.mediaType === 'image' || part.mediaType.startsWith('image/'))
102
146
  ) {
103
- const mediaType =
147
+ const filePartOptions = await parseProviderOptions({
148
+ provider: providerOptionsName,
149
+ providerOptions: part.providerOptions,
150
+ schema: deepseekFilePartProviderOptions,
151
+ });
152
+
153
+ const resolvedMediaType =
104
154
  part.mediaType === 'image' || part.mediaType === 'image/*'
105
155
  ? 'image/jpeg'
106
156
  : part.mediaType;
107
157
 
108
- userContent.push({
109
- type: 'image_url',
110
- image_url: {
111
- url:
112
- part.data instanceof URL
113
- ? part.data.toString()
114
- : `data:${mediaType};base64,${convertToBase64(part.data)}`,
115
- },
116
- });
158
+ if (!supportedImageMediaTypes.has(resolvedMediaType)) {
159
+ throw new UnsupportedFunctionalityError({
160
+ functionality: `DeepSeek image media type ${resolvedMediaType}`,
161
+ message:
162
+ 'DeepSeek supports JPEG, PNG, GIF, and WebP image inputs.',
163
+ });
164
+ }
165
+
166
+ if (part.data instanceof URL) {
167
+ const url = part.data.toString();
168
+
169
+ if (url.length > 8192) {
170
+ throw new InvalidPromptError({
171
+ prompt,
172
+ message:
173
+ 'DeepSeek image URLs must not exceed 8192 characters.',
174
+ });
175
+ }
176
+
177
+ if (filePartOptions?.fileData === true) {
178
+ throw new InvalidPromptError({
179
+ prompt,
180
+ message:
181
+ 'DeepSeek `fileData` image parts require inline data, not a URL.',
182
+ });
183
+ }
184
+
185
+ userContent.push({
186
+ type: 'image_url',
187
+ image_url: {
188
+ url,
189
+ ...(filePartOptions?.imageDetail != null && {
190
+ detail: filePartOptions.imageDetail,
191
+ }),
192
+ },
193
+ });
194
+ } else {
195
+ const dataUrl = `data:${
196
+ resolvedMediaType === 'image/jpg'
197
+ ? 'image/jpeg'
198
+ : resolvedMediaType
199
+ };base64,${convertToBase64(part.data)}`;
200
+
201
+ if (filePartOptions?.fileData === true) {
202
+ if (filePartOptions.imageDetail != null) {
203
+ throw new InvalidPromptError({
204
+ prompt,
205
+ message:
206
+ 'DeepSeek `imageDetail` cannot be combined with `fileData`.',
207
+ });
208
+ }
209
+
210
+ userContent.push({
211
+ type: 'file',
212
+ file_data: dataUrl,
213
+ ...(part.filename != null && { filename: part.filename }),
214
+ });
215
+ } else {
216
+ userContent.push({
217
+ type: 'image_url',
218
+ image_url: {
219
+ url: dataUrl,
220
+ ...(filePartOptions?.imageDetail != null && {
221
+ detail: filePartOptions.imageDetail,
222
+ }),
223
+ },
224
+ });
225
+ }
226
+ }
117
227
  } else {
118
228
  warnings.push({
119
229
  type: 'unsupported',
@@ -122,11 +232,35 @@ export function convertToDeepSeekChatMessages({
122
232
  }
123
233
  }
124
234
 
125
- messages.push({ role: 'user', content: userContent });
235
+ messages.push({
236
+ role: 'user',
237
+ content: userContent,
238
+ ...(deepseekMessageOptions?.name != null && {
239
+ name: deepseekMessageOptions.name,
240
+ }),
241
+ });
126
242
 
127
243
  break;
128
244
  }
129
245
  case 'assistant': {
246
+ if (deepseekMessageOptions?.prefix === true) {
247
+ if (index !== prompt.length - 1) {
248
+ throw new InvalidPromptError({
249
+ prompt,
250
+ message:
251
+ 'DeepSeek assistant prefix completion requires the prefixed assistant message to be the final message.',
252
+ });
253
+ }
254
+
255
+ if (!supportsAssistantPrefixCompletion) {
256
+ throw new UnsupportedFunctionalityError({
257
+ functionality: 'DeepSeek assistant prefix completion',
258
+ message:
259
+ 'DeepSeek assistant prefix completion requires a beta base URL ending in `/beta`.',
260
+ });
261
+ }
262
+ }
263
+
130
264
  let text = '';
131
265
  let reasoning: string | undefined;
132
266
 
@@ -174,6 +308,12 @@ export function convertToDeepSeekChatMessages({
174
308
  messages.push({
175
309
  role: 'assistant',
176
310
  content: text,
311
+ ...(deepseekMessageOptions?.name != null && {
312
+ name: deepseekMessageOptions.name,
313
+ }),
314
+ ...(deepseekMessageOptions?.prefix === true && {
315
+ prefix: true,
316
+ }),
177
317
  reasoning_content: reasoning ?? (isDeepSeekV4 ? '' : undefined),
178
318
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
179
319
  });
@@ -182,6 +322,13 @@ export function convertToDeepSeekChatMessages({
182
322
  }
183
323
 
184
324
  case 'tool': {
325
+ if (deepseekMessageOptions?.name != null) {
326
+ warnings.push({
327
+ type: 'unsupported',
328
+ feature: 'message name on tool messages',
329
+ });
330
+ }
331
+
185
332
  for (const toolResponse of content) {
186
333
  if (toolResponse.type === 'tool-approval-response') {
187
334
  continue;
@@ -48,7 +48,7 @@ export function convertDeepSeekUsage(
48
48
  },
49
49
  outputTokens: {
50
50
  total: completionTokens,
51
- text: completionTokens - reasoningTokens,
51
+ text: Math.max(0, completionTokens - reasoningTokens),
52
52
  reasoning: reasoningTokens,
53
53
  },
54
54
  raw: usage,