@ai-sdk/deepseek 2.0.58 → 2.0.59

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.
@@ -12,16 +12,19 @@ export type DeepSeekMessage =
12
12
  export interface DeepSeekSystemMessage {
13
13
  role: 'system';
14
14
  content: string;
15
+ name?: string;
15
16
  }
16
17
 
17
18
  export interface DeepSeekUserMessage {
18
19
  role: 'user';
19
20
  content: string | Array<DeepSeekContentPart>;
21
+ name?: string;
20
22
  }
21
23
 
22
24
  export type DeepSeekContentPart =
23
25
  | DeepSeekContentPartText
24
- | DeepSeekContentPartImage;
26
+ | DeepSeekContentPartImage
27
+ | DeepSeekContentPartFile;
25
28
 
26
29
  export interface DeepSeekContentPartText {
27
30
  type: 'text';
@@ -30,12 +33,22 @@ export interface DeepSeekContentPartText {
30
33
 
31
34
  export interface DeepSeekContentPartImage {
32
35
  type: 'image_url';
33
- image_url: { url: string };
36
+ image_url: {
37
+ url: string;
38
+ detail?: 'low' | 'high' | 'original' | 'auto';
39
+ };
34
40
  }
35
41
 
42
+ export interface DeepSeekContentPartFile {
43
+ type: 'file';
44
+ file_data: string;
45
+ filename?: string;
46
+ }
36
47
  export interface DeepSeekAssistantMessage {
37
48
  role: 'assistant';
38
49
  content?: string | null;
50
+ name?: string;
51
+ prefix?: true;
39
52
  reasoning_content?: string;
40
53
  tool_calls?: Array<DeepSeekMessageToolCall>;
41
54
  }
@@ -100,14 +113,39 @@ export const deepSeekErrorSchema = z.object({
100
113
 
101
114
  export type DeepSeekErrorData = z.infer<typeof deepSeekErrorSchema>;
102
115
 
116
+ const deepseekChatLogprobSchema = z.object({
117
+ token: z.string(),
118
+ logprob: z.number(),
119
+ bytes: z.array(z.number()).nullable(),
120
+ top_logprobs: z.array(
121
+ z.object({
122
+ token: z.string(),
123
+ logprob: z.number(),
124
+ bytes: z.array(z.number()).nullable(),
125
+ }),
126
+ ),
127
+ });
128
+
129
+ const deepseekChatLogprobsSchema = z
130
+ .object({
131
+ content: z.array(deepseekChatLogprobSchema).nullish(),
132
+ reasoning_content: z.array(deepseekChatLogprobSchema).nullish(),
133
+ })
134
+ .nullish();
135
+
136
+ export type DeepSeekChatLogprob = z.infer<typeof deepseekChatLogprobSchema>;
137
+
103
138
  // limited version of the schema, focussed on what is needed for the implementation
104
139
  // this approach limits breakages when the API changes and increases efficiency
105
140
  export const deepseekChatResponseSchema = z.object({
106
141
  id: z.string().nullish(),
107
142
  created: z.number().nullish(),
108
143
  model: z.string().nullish(),
144
+ object: z.literal('chat.completion').nullish(),
145
+ system_fingerprint: z.string().nullish(),
109
146
  choices: z.array(
110
147
  z.object({
148
+ index: z.number().nullish(),
111
149
  message: z.object({
112
150
  role: z.literal('assistant').nullish(),
113
151
  content: z.string().nullish(),
@@ -116,6 +154,7 @@ export const deepseekChatResponseSchema = z.object({
116
154
  .array(
117
155
  z.object({
118
156
  id: z.string().nullish(),
157
+ type: z.literal('function').nullish(),
119
158
  function: z.object({
120
159
  name: z.string(),
121
160
  arguments: z.string(),
@@ -124,6 +163,7 @@ export const deepseekChatResponseSchema = z.object({
124
163
  )
125
164
  .nullish(),
126
165
  }),
166
+ logprobs: deepseekChatLogprobsSchema,
127
167
  finish_reason: z.string().nullish(),
128
168
  }),
129
169
  ),
@@ -139,8 +179,11 @@ export const deepseekChatChunkSchema = lazySchema(() =>
139
179
  id: z.string().nullish(),
140
180
  created: z.number().nullish(),
141
181
  model: z.string().nullish(),
182
+ object: z.literal('chat.completion.chunk').nullish(),
183
+ system_fingerprint: z.string().nullish(),
142
184
  choices: z.array(
143
185
  z.object({
186
+ index: z.number().nullish(),
144
187
  delta: z
145
188
  .object({
146
189
  role: z.enum(['assistant']).nullish(),
@@ -151,6 +194,7 @@ export const deepseekChatChunkSchema = lazySchema(() =>
151
194
  z.object({
152
195
  index: z.number(),
153
196
  id: z.string().nullish(),
197
+ type: z.literal('function').nullish(),
154
198
  function: z.object({
155
199
  name: z.string().nullish(),
156
200
  arguments: z.string().nullish(),
@@ -160,6 +204,7 @@ export const deepseekChatChunkSchema = lazySchema(() =>
160
204
  .nullish(),
161
205
  })
162
206
  .nullish(),
207
+ logprobs: deepseekChatLogprobsSchema,
163
208
  finish_reason: z.string().nullish(),
164
209
  }),
165
210
  ),
@@ -8,6 +8,7 @@ import {
8
8
  type LanguageModelV3GenerateResult,
9
9
  type LanguageModelV3StreamPart,
10
10
  type LanguageModelV3StreamResult,
11
+ type SharedV3Warning,
11
12
  } from '@ai-sdk/provider';
12
13
  import {
13
14
  combineHeaders,
@@ -28,6 +29,7 @@ import {
28
29
  deepseekChatChunkSchema,
29
30
  deepseekChatResponseSchema,
30
31
  deepSeekErrorSchema,
32
+ type DeepSeekChatLogprob,
31
33
  type DeepSeekChatTokenUsage,
32
34
  } from './deepseek-chat-api-types';
33
35
  import {
@@ -43,10 +45,38 @@ export type DeepSeekChatConfig = {
43
45
  headers: () => Record<string, string | undefined>;
44
46
  url: (options: { modelId: string; path: string }) => string;
45
47
  fetch?: FetchFunction;
48
+ supportsAssistantPrefixCompletion?: boolean;
49
+ supportsPenaltySampling?: boolean;
50
+ supportsStrictToolCalls?: boolean;
46
51
  supportsThinking?: boolean;
47
52
  supportsStructuredOutputs?: boolean;
48
53
  };
49
54
 
55
+ function mapDeepSeekProviderReasoningEffort({
56
+ reasoningEffort,
57
+ warnings,
58
+ }: {
59
+ reasoningEffort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
60
+ warnings: SharedV3Warning[];
61
+ }): 'low' | 'high' | 'max' {
62
+ const mapped =
63
+ reasoningEffort === 'medium'
64
+ ? 'high'
65
+ : reasoningEffort === 'xhigh'
66
+ ? 'max'
67
+ : reasoningEffort;
68
+
69
+ if (mapped !== reasoningEffort) {
70
+ warnings.push({
71
+ type: 'compatibility',
72
+ feature: 'reasoningEffort',
73
+ details: `reasoningEffort "${reasoningEffort}" is not a canonical DeepSeek value. mapped to "${mapped}".`,
74
+ });
75
+ }
76
+
77
+ return mapped;
78
+ }
79
+
50
80
  export class DeepSeekChatLanguageModel implements LanguageModelV3 {
51
81
  readonly specificationVersion = 'v3';
52
82
 
@@ -102,20 +132,42 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
102
132
 
103
133
  const supportsStructuredOutputs =
104
134
  this.config.supportsStructuredOutputs === true;
135
+ const supportsPenaltySampling =
136
+ this.config.supportsPenaltySampling === true;
105
137
 
106
- const { messages, warnings } = convertToDeepSeekChatMessages({
138
+ const { messages, warnings } = await convertToDeepSeekChatMessages({
107
139
  prompt,
108
140
  responseFormat,
109
141
  modelId: this.modelId,
142
+ providerOptionsName: this.providerOptionsName,
143
+ supportsAssistantPrefixCompletion:
144
+ this.config.supportsAssistantPrefixCompletion,
110
145
  supportsStructuredOutputs,
111
146
  });
147
+ const allWarnings = [...warnings];
112
148
 
113
149
  if (topK != null) {
114
- warnings.push({ type: 'unsupported', feature: 'topK' });
150
+ allWarnings.push({ type: 'unsupported', feature: 'topK' });
115
151
  }
116
152
 
117
153
  if (seed != null) {
118
- warnings.push({ type: 'unsupported', feature: 'seed' });
154
+ allWarnings.push({ type: 'unsupported', feature: 'seed' });
155
+ }
156
+
157
+ if (!supportsPenaltySampling && frequencyPenalty != null) {
158
+ allWarnings.push({
159
+ type: 'other',
160
+ message:
161
+ 'frequencyPenalty is deprecated by DeepSeek and has been omitted. Remove frequencyPenalty from the request.',
162
+ });
163
+ }
164
+
165
+ if (!supportsPenaltySampling && presencePenalty != null) {
166
+ allWarnings.push({
167
+ type: 'other',
168
+ message:
169
+ 'presencePenalty is deprecated by DeepSeek and has been omitted. Remove presencePenalty from the request.',
170
+ });
119
171
  }
120
172
 
121
173
  const {
@@ -125,23 +177,75 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
125
177
  } = prepareTools({
126
178
  tools,
127
179
  toolChoice,
180
+ supportsStrictToolCalls: this.config.supportsStrictToolCalls,
128
181
  });
182
+ allWarnings.push(...toolWarnings);
183
+
184
+ const thinkingType = deepseekOptions.thinking?.type;
185
+ if (thinkingType === 'adaptive') {
186
+ allWarnings.push({
187
+ type: 'compatibility',
188
+ feature: 'thinking.type',
189
+ details:
190
+ 'thinking.type "adaptive" is not a canonical DeepSeek value. mapped to "enabled".',
191
+ });
192
+ }
129
193
 
130
194
  const thinking =
131
195
  this.config.supportsThinking === false
132
196
  ? undefined
133
- : deepseekOptions.thinking?.type != null
134
- ? { type: deepseekOptions.thinking.type }
197
+ : thinkingType != null
198
+ ? { type: thinkingType === 'adaptive' ? 'enabled' : thinkingType }
135
199
  : undefined;
136
200
 
201
+ const isThinkingEnabled =
202
+ this.config.supportsThinking !== false &&
203
+ thinking?.type !== 'disabled' &&
204
+ (thinking != null ||
205
+ this.modelId === 'deepseek-reasoner' ||
206
+ this.modelId.includes('deepseek-v4'));
207
+
208
+ if (isThinkingEnabled && temperature != null) {
209
+ allWarnings.push({
210
+ type: 'unsupported',
211
+ feature: 'temperature',
212
+ details:
213
+ "temperature has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use temperature.",
214
+ });
215
+ }
216
+
217
+ if (isThinkingEnabled && topP != null) {
218
+ allWarnings.push({
219
+ type: 'unsupported',
220
+ feature: 'topP',
221
+ details:
222
+ "topP has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use topP.",
223
+ });
224
+ }
225
+
226
+ const reasoningEffort =
227
+ deepseekOptions.reasoningEffort != null
228
+ ? mapDeepSeekProviderReasoningEffort({
229
+ reasoningEffort: deepseekOptions.reasoningEffort,
230
+ warnings: allWarnings,
231
+ })
232
+ : undefined;
233
+
137
234
  return {
138
235
  args: {
139
236
  model: this.modelId,
237
+ ...((deepseekOptions.logprobs === true ||
238
+ deepseekOptions.topLogprobs != null) && { logprobs: true }),
239
+ ...(deepseekOptions.topLogprobs != null && {
240
+ top_logprobs: deepseekOptions.topLogprobs,
241
+ }),
140
242
  max_tokens: maxOutputTokens,
141
- temperature,
142
- top_p: topP,
143
- frequency_penalty: frequencyPenalty,
144
- presence_penalty: presencePenalty,
243
+ temperature: isThinkingEnabled ? undefined : temperature,
244
+ top_p: isThinkingEnabled ? undefined : topP,
245
+ frequency_penalty: supportsPenaltySampling
246
+ ? frequencyPenalty
247
+ : undefined,
248
+ presence_penalty: supportsPenaltySampling ? presencePenalty : undefined,
145
249
  response_format:
146
250
  responseFormat?.type === 'json'
147
251
  ? supportsStructuredOutputs && responseFormat.schema != null
@@ -161,12 +265,15 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
161
265
  tools: deepseekTools,
162
266
  tool_choice: deepseekToolChoices,
163
267
  thinking,
268
+ ...(deepseekOptions.userId != null && {
269
+ user_id: deepseekOptions.userId,
270
+ }),
164
271
  ...(thinking?.type !== 'disabled' &&
165
- deepseekOptions.reasoningEffort != null && {
166
- reasoning_effort: deepseekOptions.reasoningEffort,
272
+ reasoningEffort != null && {
273
+ reasoning_effort: reasoningEffort,
167
274
  }),
168
275
  },
169
- warnings: [...warnings, ...toolWarnings],
276
+ warnings: allWarnings,
170
277
  };
171
278
  }
172
279
 
@@ -235,6 +342,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
235
342
  [this.providerOptionsName]: {
236
343
  promptCacheHitTokens: responseBody.usage?.prompt_cache_hit_tokens,
237
344
  promptCacheMissTokens: responseBody.usage?.prompt_cache_miss_tokens,
345
+ ...(responseBody.object != null && {
346
+ responseObject: responseBody.object,
347
+ }),
348
+ ...(choice.index != null && { choiceIndex: choice.index }),
349
+ ...(choice.message.role != null && {
350
+ messageRole: choice.message.role,
351
+ }),
352
+ ...(choice.message.tool_calls != null && {
353
+ toolCallTypes: choice.message.tool_calls
354
+ .map(toolCall => toolCall.type)
355
+ .filter(type => type != null),
356
+ }),
357
+ ...(choice.logprobs != null && { logprobs: choice.logprobs }),
358
+ ...(responseBody.system_fingerprint != null && {
359
+ systemFingerprint: responseBody.system_fingerprint,
360
+ }),
238
361
  },
239
362
  },
240
363
  request: { body: args },
@@ -288,10 +411,17 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
288
411
  raw: undefined,
289
412
  };
290
413
  let usage: DeepSeekChatTokenUsage | undefined = undefined;
414
+ let systemFingerprint: string | undefined = undefined;
291
415
  let isFirstChunk = true;
292
416
  const providerOptionsName = this.providerOptionsName;
293
417
  let isActiveReasoning = false;
294
418
  let isActiveText = false;
419
+ let responseObject: 'chat.completion.chunk' | undefined;
420
+ let choiceIndex: number | undefined;
421
+ let messageRole: 'assistant' | undefined;
422
+ const toolCallTypes = new Map<number, 'function'>();
423
+ const contentLogprobs: DeepSeekChatLogprob[] = [];
424
+ const reasoningLogprobs: DeepSeekChatLogprob[] = [];
295
425
 
296
426
  return {
297
427
  stream: response.pipeThrough(
@@ -337,8 +467,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
337
467
  usage = value.usage;
338
468
  }
339
469
 
470
+ if (value.object != null) {
471
+ responseObject = value.object;
472
+ }
473
+
474
+ // The fingerprint is repeated on stream chunks; keep the latest
475
+ // non-null value in case it changes during the response.
476
+ if (value.system_fingerprint != null) {
477
+ systemFingerprint = value.system_fingerprint;
478
+ }
479
+
340
480
  const choice = value.choices[0];
341
481
 
482
+ if (choice?.index != null) {
483
+ choiceIndex = choice.index;
484
+ }
485
+
342
486
  if (choice?.finish_reason != null) {
343
487
  finishReason = {
344
488
  unified: mapDeepSeekFinishReason(choice.finish_reason),
@@ -346,12 +490,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
346
490
  };
347
491
  }
348
492
 
493
+ if (choice?.logprobs?.content != null) {
494
+ contentLogprobs.push(...choice.logprobs.content);
495
+ }
496
+
497
+ if (choice?.logprobs?.reasoning_content != null) {
498
+ reasoningLogprobs.push(...choice.logprobs.reasoning_content);
499
+ }
500
+
349
501
  if (choice?.delta == null) {
350
502
  return;
351
503
  }
352
504
 
353
505
  const delta = choice.delta;
354
506
 
507
+ if (delta.role != null) {
508
+ messageRole = delta.role;
509
+ }
510
+
355
511
  // enqueue reasoning before text deltas:
356
512
  const reasoningContent = delta.reasoning_content;
357
513
  if (reasoningContent) {
@@ -403,6 +559,10 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
403
559
  }
404
560
 
405
561
  for (const toolCallDelta of delta.tool_calls) {
562
+ if (toolCallDelta.type != null) {
563
+ toolCallTypes.set(toolCallDelta.index, toolCallDelta.type);
564
+ }
565
+
406
566
  const index = toolCallDelta.index;
407
567
 
408
568
  if (toolCalls[index] == null) {
@@ -513,6 +673,26 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
513
673
  usage?.prompt_cache_hit_tokens ?? undefined,
514
674
  promptCacheMissTokens:
515
675
  usage?.prompt_cache_miss_tokens ?? undefined,
676
+ ...(responseObject != null && { responseObject }),
677
+ ...(choiceIndex != null && { choiceIndex }),
678
+ ...(messageRole != null && { messageRole }),
679
+ ...(toolCallTypes.size > 0 && {
680
+ toolCallTypes: [...toolCallTypes.entries()]
681
+ .sort(([left], [right]) => left - right)
682
+ .map(([, type]) => type),
683
+ }),
684
+ ...((contentLogprobs.length > 0 ||
685
+ reasoningLogprobs.length > 0) && {
686
+ logprobs: {
687
+ ...(contentLogprobs.length > 0 && {
688
+ content: contentLogprobs,
689
+ }),
690
+ ...(reasoningLogprobs.length > 0 && {
691
+ reasoning_content: reasoningLogprobs,
692
+ }),
693
+ },
694
+ }),
695
+ ...(systemFingerprint != null && { systemFingerprint }),
516
696
  },
517
697
  },
518
698
  });
@@ -2,31 +2,57 @@ import { z } from 'zod/v4';
2
2
 
3
3
  // https://api-docs.deepseek.com/quick_start/pricing
4
4
  export type DeepSeekChatModelId =
5
- | 'deepseek-chat'
6
- | 'deepseek-reasoner'
5
+ | 'deepseek-v4-flash'
6
+ | 'deepseek-v4-pro'
7
7
  | 'deepseek-v4-flash-vision-exp'
8
+ // Retired aliases remain assignable through the string escape hatch, but are
9
+ // intentionally omitted from first-class editor suggestions.
8
10
  | (string & {});
9
11
 
10
12
  export const deepseekLanguageModelOptions = z.object({
11
13
  /**
12
- * Type of thinking to use. Defaults to `enabled`.
14
+ * Whether to return log probabilities for generated tokens.
15
+ */
16
+ logprobs: z.boolean().optional(),
17
+
18
+ /**
19
+ * Number of most likely tokens to return at each token position.
20
+ *
21
+ * Setting this option automatically enables `logprobs`.
22
+ */
23
+ topLogprobs: z.number().int().min(0).max(20).optional(),
24
+
25
+ /**
26
+ * An opaque identifier for the end user. DeepSeek uses this identifier for
27
+ * content-safety tracing and request isolation.
13
28
  *
14
- * See https://api-docs.deepseek.com/guides/thinking_mode for the
15
- * `adaptive` option, which lets the model decide when to think.
29
+ * Must contain only ASCII letters, numbers, underscores, and hyphens, and
30
+ * must be at most 512 characters long.
31
+ */
32
+ userId: z
33
+ .string()
34
+ .regex(/^[a-zA-Z0-9_-]+$/, 'userId must match /^[a-zA-Z0-9_-]+$/')
35
+ .max(512, 'userId must be at most 512 characters long')
36
+ .optional(),
37
+
38
+ /**
39
+ * Type of thinking to use. Defaults to `enabled`.
16
40
  */
17
41
  thinking: z
18
42
  .object({
43
+ // `adaptive` is accepted at runtime for backwards compatibility and
44
+ // mapped to `enabled`, but is intentionally excluded from the exported
45
+ // provider options type.
19
46
  type: z.enum(['adaptive', 'enabled', 'disabled']).optional(),
20
47
  })
21
48
  .optional(),
22
49
 
23
50
  /**
24
51
  * Controls the thinking strength for DeepSeek V4 reasoning models.
25
- *
26
- * DeepSeek's API accepts `low`, `medium`, `high`, `xhigh`, and `max`.
27
- * Per their docs, `low` and `medium` are mapped to `high`, and `xhigh`
28
- * is mapped to `max` server-side for compatibility with other providers.
29
52
  */
53
+ // `medium` and `xhigh` are accepted at runtime for backwards compatibility
54
+ // and mapped to canonical DeepSeek values, but are intentionally excluded
55
+ // from the exported provider options type.
30
56
  reasoningEffort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
31
57
 
32
58
  /**
@@ -37,6 +63,71 @@ export const deepseekLanguageModelOptions = z.object({
37
63
  strictJsonSchema: z.boolean().optional(),
38
64
  });
39
65
 
40
- export type DeepSeekLanguageModelOptions = z.infer<
41
- typeof deepseekLanguageModelOptions
66
+ export type DeepSeekLanguageModelOptions = {
67
+ /**
68
+ * Whether to return log probabilities for generated tokens.
69
+ */
70
+ logprobs?: boolean;
71
+
72
+ /**
73
+ * Number of most likely tokens to return at each token position.
74
+ *
75
+ * Setting this option automatically enables `logprobs`.
76
+ */
77
+ topLogprobs?: number;
78
+
79
+ /**
80
+ * An opaque identifier for the end user. DeepSeek uses this identifier for
81
+ * content-safety tracing and request isolation.
82
+ *
83
+ * Must contain only ASCII letters, numbers, underscores, and hyphens, and
84
+ * must be at most 512 characters long.
85
+ */
86
+ userId?: string;
87
+
88
+ /**
89
+ * Controls whether thinking mode is enabled. Defaults to `enabled`.
90
+ */
91
+ thinking?: {
92
+ type?: 'enabled' | 'disabled';
93
+ };
94
+
95
+ /**
96
+ * Controls the thinking strength for DeepSeek V4 reasoning models.
97
+ */
98
+ reasoningEffort?: 'low' | 'high' | 'max';
99
+
100
+ /**
101
+ * Whether to use strict JSON schema validation for structured outputs.
102
+ * Only applies when the serving endpoint supports JSON schema response
103
+ * formats (e.g. Azure). Defaults to `true`.
104
+ */
105
+ strictJsonSchema?: boolean;
106
+ };
107
+
108
+ export const deepseekMessageProviderOptions = z.object({
109
+ /**
110
+ * The name of the participant represented by the message.
111
+ *
112
+ * Supported on system, user, and assistant messages.
113
+ */
114
+ name: z.string().optional(),
115
+ });
116
+
117
+ export type DeepSeekMessageProviderOptions = z.infer<
118
+ typeof deepseekMessageProviderOptions
119
+ >;
120
+
121
+ export const deepseekAssistantMessageProviderOptions =
122
+ deepseekMessageProviderOptions.extend({
123
+ /**
124
+ * Whether the assistant message content is a prefix that DeepSeek should
125
+ * continue. This beta feature is only supported on the final assistant
126
+ * message when using a beta base URL.
127
+ */
128
+ prefix: z.literal(true).optional(),
129
+ });
130
+
131
+ export type DeepSeekAssistantMessageProviderOptions = z.infer<
132
+ typeof deepseekAssistantMessageProviderOptions
42
133
  >;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ export const deepseekFilePartProviderOptions = z.object({
4
+ /**
5
+ * Controls how DeepSeek processes an image sent as an `image_url` part.
6
+ *
7
+ * @see https://api-docs.deepseek.com/api/create-chat-completion/
8
+ */
9
+ imageDetail: z.enum(['low', 'high', 'original', 'auto']).optional(),
10
+
11
+ /**
12
+ * Sends inline image data as a DeepSeek `file` part using `file_data`
13
+ * instead of an `image_url` data URL. When set, the file part's filename
14
+ * is preserved.
15
+ *
16
+ * This option only applies to inline image data. It cannot be combined
17
+ * with `imageDetail`.
18
+ */
19
+ fileData: z.literal(true).optional(),
20
+ });
21
+
22
+ export type DeepSeekFilePartProviderOptions = z.infer<
23
+ typeof deepseekFilePartProviderOptions
24
+ >;
@@ -1,6 +1,7 @@
1
- import type {
2
- LanguageModelV3CallOptions,
3
- SharedV3Warning,
1
+ import {
2
+ UnsupportedFunctionalityError,
3
+ type LanguageModelV3CallOptions,
4
+ type SharedV3Warning,
4
5
  } from '@ai-sdk/provider';
5
6
  import type {
6
7
  DeepSeekFunctionTool,
@@ -10,9 +11,11 @@ import type {
10
11
  export function prepareTools({
11
12
  tools,
12
13
  toolChoice,
14
+ supportsStrictToolCalls,
13
15
  }: {
14
16
  tools: LanguageModelV3CallOptions['tools'];
15
17
  toolChoice?: LanguageModelV3CallOptions['toolChoice'];
18
+ supportsStrictToolCalls?: boolean;
16
19
  }): {
17
20
  tools: undefined | Array<DeepSeekFunctionTool>;
18
21
  toolChoice: DeepSeekToolChoice;
@@ -27,6 +30,29 @@ export function prepareTools({
27
30
  return { tools: undefined, toolChoice: undefined, toolWarnings };
28
31
  }
29
32
 
33
+ const functionTools = tools.filter(tool => tool.type === 'function');
34
+ const hasStrictTool = functionTools.some(tool => tool.strict === true);
35
+
36
+ if (hasStrictTool && supportsStrictToolCalls === false) {
37
+ throw new UnsupportedFunctionalityError({
38
+ functionality: 'DeepSeek strict tool calls',
39
+ message:
40
+ 'DeepSeek strict tool calls require a beta base URL ending in `/beta`.',
41
+ });
42
+ }
43
+
44
+ if (
45
+ hasStrictTool &&
46
+ supportsStrictToolCalls === true &&
47
+ functionTools.some(tool => tool.strict !== true)
48
+ ) {
49
+ throw new UnsupportedFunctionalityError({
50
+ functionality: 'mixed DeepSeek strict and non-strict tool calls',
51
+ message:
52
+ 'DeepSeek strict mode requires every function tool in the request to set `strict: true`.',
53
+ });
54
+ }
55
+
30
56
  const deepseekTools: Array<DeepSeekFunctionTool> = [];
31
57
 
32
58
  for (const tool of tools) {
@@ -61,9 +61,8 @@ export interface DeepSeekProvider extends ProviderV3 {
61
61
  export function createDeepSeek(
62
62
  options: DeepSeekProviderSettings = {},
63
63
  ): DeepSeekProvider {
64
- const baseURL = withoutTrailingSlash(
65
- options.baseURL ?? 'https://api.deepseek.com',
66
- );
64
+ const baseURL =
65
+ withoutTrailingSlash(options.baseURL) ?? 'https://api.deepseek.com';
67
66
 
68
67
  const getHeaders = () =>
69
68
  withUserAgentSuffix(
@@ -84,6 +83,8 @@ export function createDeepSeek(
84
83
  url: ({ path }) => `${baseURL}${path}`,
85
84
  headers: getHeaders,
86
85
  fetch: options.fetch,
86
+ supportsAssistantPrefixCompletion: baseURL.endsWith('/beta'),
87
+ supportsStrictToolCalls: baseURL.endsWith('/beta'),
87
88
  });
88
89
  };
89
90