@ai-sdk/deepseek 3.0.30 → 3.0.32

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.
@@ -34,6 +34,7 @@ import {
34
34
  deepseekChatChunkSchema,
35
35
  deepseekChatResponseSchema,
36
36
  deepSeekErrorSchema,
37
+ type DeepSeekChatLogprob,
37
38
  type DeepSeekChatTokenUsage,
38
39
  } from './deepseek-chat-api-types';
39
40
  import {
@@ -49,10 +50,38 @@ export type DeepSeekChatConfig = {
49
50
  headers?: () => Record<string, string | undefined>;
50
51
  url: (options: { modelId: string; path: string }) => string;
51
52
  fetch?: FetchFunction;
53
+ supportsAssistantPrefixCompletion?: boolean;
54
+ supportsStrictToolCalls?: boolean;
55
+ supportsPenaltySampling?: boolean;
52
56
  supportsThinking?: boolean;
53
57
  supportsStructuredOutputs?: boolean;
54
58
  };
55
59
 
60
+ function mapDeepSeekProviderReasoningEffort({
61
+ reasoningEffort,
62
+ warnings,
63
+ }: {
64
+ reasoningEffort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
65
+ warnings: SharedV4Warning[];
66
+ }): 'low' | 'high' | 'max' {
67
+ const mapped =
68
+ reasoningEffort === 'medium'
69
+ ? 'high'
70
+ : reasoningEffort === 'xhigh'
71
+ ? 'max'
72
+ : reasoningEffort;
73
+
74
+ if (mapped !== reasoningEffort) {
75
+ warnings.push({
76
+ type: 'compatibility',
77
+ feature: 'reasoningEffort',
78
+ details: `reasoningEffort "${reasoningEffort}" is not a canonical DeepSeek value. mapped to "${mapped}".`,
79
+ });
80
+ }
81
+
82
+ return mapped;
83
+ }
84
+
56
85
  export class DeepSeekChatLanguageModel implements LanguageModelV4 {
57
86
  readonly specificationVersion = 'v4';
58
87
 
@@ -123,11 +152,16 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
123
152
 
124
153
  const supportsStructuredOutputs =
125
154
  this.config.supportsStructuredOutputs === true;
155
+ const supportsPenaltySampling =
156
+ this.config.supportsPenaltySampling === true;
126
157
 
127
- const { messages, warnings } = convertToDeepSeekChatMessages({
158
+ const { messages, warnings } = await convertToDeepSeekChatMessages({
128
159
  prompt,
129
160
  responseFormat,
130
161
  modelId: this.modelId,
162
+ providerOptionsName: this.providerOptionsName,
163
+ supportsAssistantPrefixCompletion:
164
+ this.config.supportsAssistantPrefixCompletion,
131
165
  supportsStructuredOutputs,
132
166
  });
133
167
  const allWarnings: SharedV4Warning[] = [...warnings];
@@ -140,6 +174,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
140
174
  allWarnings.push({ type: 'unsupported', feature: 'seed' });
141
175
  }
142
176
 
177
+ if (!supportsPenaltySampling && frequencyPenalty != null) {
178
+ allWarnings.push({
179
+ type: 'deprecated',
180
+ setting: 'frequencyPenalty',
181
+ message:
182
+ 'frequencyPenalty is deprecated by DeepSeek and has been omitted. Remove frequencyPenalty from the request.',
183
+ });
184
+ }
185
+
186
+ if (!supportsPenaltySampling && presencePenalty != null) {
187
+ allWarnings.push({
188
+ type: 'deprecated',
189
+ setting: 'presencePenalty',
190
+ message:
191
+ 'presencePenalty is deprecated by DeepSeek and has been omitted. Remove presencePenalty from the request.',
192
+ });
193
+ }
194
+
143
195
  const {
144
196
  tools: deepseekTools,
145
197
  toolChoice: deepseekToolChoices,
@@ -147,41 +199,88 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
147
199
  } = prepareTools({
148
200
  tools,
149
201
  toolChoice,
202
+ supportsStrictToolCalls: this.config.supportsStrictToolCalls,
150
203
  });
151
204
 
205
+ const thinkingType = deepseekOptions.thinking?.type;
206
+ if (thinkingType === 'adaptive') {
207
+ allWarnings.push({
208
+ type: 'compatibility',
209
+ feature: 'thinking.type',
210
+ details:
211
+ 'thinking.type "adaptive" is not a canonical DeepSeek value. mapped to "enabled".',
212
+ });
213
+ }
214
+
152
215
  const thinking =
153
216
  this.config.supportsThinking === false
154
217
  ? undefined
155
- : deepseekOptions.thinking?.type != null
156
- ? { type: deepseekOptions.thinking.type }
218
+ : thinkingType != null
219
+ ? { type: thinkingType === 'adaptive' ? 'enabled' : thinkingType }
157
220
  : isCustomReasoning(reasoning)
158
221
  ? { type: reasoning === 'none' ? 'disabled' : 'enabled' }
159
222
  : undefined;
160
223
 
224
+ const isThinkingEnabled =
225
+ this.config.supportsThinking !== false &&
226
+ thinking?.type !== 'disabled' &&
227
+ (thinking != null ||
228
+ this.modelId === 'deepseek-reasoner' ||
229
+ this.modelId.includes('deepseek-v4'));
230
+
231
+ if (isThinkingEnabled && temperature != null) {
232
+ allWarnings.push({
233
+ type: 'unsupported',
234
+ feature: 'temperature',
235
+ details:
236
+ "temperature has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use temperature.",
237
+ });
238
+ }
239
+
240
+ if (isThinkingEnabled && topP != null) {
241
+ allWarnings.push({
242
+ type: 'unsupported',
243
+ feature: 'topP',
244
+ details:
245
+ "topP has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use topP.",
246
+ });
247
+ }
248
+
161
249
  const reasoningEffort =
162
- deepseekOptions.reasoningEffort ??
163
- (isCustomReasoning(reasoning) && reasoning !== 'none'
164
- ? mapReasoningToProviderEffort({
165
- reasoning,
166
- effortMap: {
167
- minimal: 'low',
168
- low: 'low',
169
- medium: 'medium',
170
- high: 'high',
171
- xhigh: 'max',
172
- },
250
+ deepseekOptions.reasoningEffort != null
251
+ ? mapDeepSeekProviderReasoningEffort({
252
+ reasoningEffort: deepseekOptions.reasoningEffort,
173
253
  warnings: allWarnings,
174
254
  })
175
- : undefined);
255
+ : isCustomReasoning(reasoning) && reasoning !== 'none'
256
+ ? mapReasoningToProviderEffort({
257
+ reasoning,
258
+ effortMap: {
259
+ minimal: 'low',
260
+ low: 'low',
261
+ medium: 'high',
262
+ high: 'high',
263
+ xhigh: 'max',
264
+ },
265
+ warnings: allWarnings,
266
+ })
267
+ : undefined;
176
268
 
177
269
  return {
178
270
  args: {
179
271
  model: this.modelId,
272
+ ...((deepseekOptions.logprobs === true ||
273
+ deepseekOptions.topLogprobs != null) && { logprobs: true }),
274
+ ...(deepseekOptions.topLogprobs != null && {
275
+ top_logprobs: deepseekOptions.topLogprobs,
276
+ }),
180
277
  max_tokens: maxOutputTokens,
181
- temperature,
182
- top_p: topP,
183
- frequency_penalty: frequencyPenalty,
184
- presence_penalty: presencePenalty,
278
+ temperature: isThinkingEnabled ? undefined : temperature,
279
+ top_p: isThinkingEnabled ? undefined : topP,
280
+ frequency_penalty: supportsPenaltySampling
281
+ ? frequencyPenalty
282
+ : undefined,
283
+ presence_penalty: supportsPenaltySampling ? presencePenalty : undefined,
185
284
  response_format:
186
285
  responseFormat?.type === 'json'
187
286
  ? supportsStructuredOutputs && responseFormat.schema != null
@@ -201,6 +300,9 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
201
300
  tools: deepseekTools,
202
301
  tool_choice: deepseekToolChoices,
203
302
  thinking,
303
+ ...(deepseekOptions.userId != null && {
304
+ user_id: deepseekOptions.userId,
305
+ }),
204
306
  ...(thinking?.type !== 'disabled' &&
205
307
  reasoningEffort != null && {
206
308
  reasoning_effort: reasoningEffort,
@@ -275,6 +377,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
275
377
  [this.providerOptionsName]: {
276
378
  promptCacheHitTokens: responseBody.usage?.prompt_cache_hit_tokens,
277
379
  promptCacheMissTokens: responseBody.usage?.prompt_cache_miss_tokens,
380
+ ...(responseBody.object != null && {
381
+ responseObject: responseBody.object,
382
+ }),
383
+ ...(choice.index != null && { choiceIndex: choice.index }),
384
+ ...(choice.message.role != null && {
385
+ messageRole: choice.message.role,
386
+ }),
387
+ ...(choice.message.tool_calls != null && {
388
+ toolCallTypes: choice.message.tool_calls
389
+ .map(toolCall => toolCall.type)
390
+ .filter(type => type != null),
391
+ }),
392
+ ...(choice.logprobs != null && { logprobs: choice.logprobs }),
393
+ ...(responseBody.system_fingerprint != null && {
394
+ systemFingerprint: responseBody.system_fingerprint,
395
+ }),
278
396
  },
279
397
  },
280
398
  request: { body: args },
@@ -320,10 +438,17 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
320
438
  raw: undefined,
321
439
  };
322
440
  let usage: DeepSeekChatTokenUsage | undefined = undefined;
441
+ let systemFingerprint: string | undefined = undefined;
323
442
  let isFirstChunk = true;
324
443
  const providerOptionsName = this.providerOptionsName;
325
444
  let isActiveReasoning = false;
326
445
  let isActiveText = false;
446
+ let responseObject: 'chat.completion.chunk' | undefined;
447
+ let choiceIndex: number | undefined;
448
+ let messageRole: 'assistant' | undefined;
449
+ const toolCallTypes = new Map<number, 'function'>();
450
+ const contentLogprobs: DeepSeekChatLogprob[] = [];
451
+ const reasoningLogprobs: DeepSeekChatLogprob[] = [];
327
452
 
328
453
  return {
329
454
  stream: response.pipeThrough(
@@ -372,8 +497,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
372
497
  usage = value.usage;
373
498
  }
374
499
 
500
+ if (value.object != null) {
501
+ responseObject = value.object;
502
+ }
503
+
504
+ // The fingerprint is repeated on stream chunks; keep the latest
505
+ // non-null value in case it changes during the response.
506
+ if (value.system_fingerprint != null) {
507
+ systemFingerprint = value.system_fingerprint;
508
+ }
509
+
375
510
  const choice = value.choices[0];
376
511
 
512
+ if (choice?.index != null) {
513
+ choiceIndex = choice.index;
514
+ }
515
+
377
516
  if (choice?.finish_reason != null) {
378
517
  finishReason = {
379
518
  unified: mapDeepSeekFinishReason(choice.finish_reason),
@@ -381,12 +520,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
381
520
  };
382
521
  }
383
522
 
523
+ if (choice?.logprobs?.content != null) {
524
+ contentLogprobs.push(...choice.logprobs.content);
525
+ }
526
+
527
+ if (choice?.logprobs?.reasoning_content != null) {
528
+ reasoningLogprobs.push(...choice.logprobs.reasoning_content);
529
+ }
530
+
384
531
  if (choice?.delta == null) {
385
532
  return;
386
533
  }
387
534
 
388
535
  const delta = choice.delta;
389
536
 
537
+ if (delta.role != null) {
538
+ messageRole = delta.role;
539
+ }
540
+
390
541
  // enqueue reasoning before text deltas:
391
542
  const reasoningContent = delta.reasoning_content;
392
543
  if (reasoningContent) {
@@ -438,6 +589,9 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
438
589
  }
439
590
 
440
591
  for (const toolCallDelta of delta.tool_calls) {
592
+ if (toolCallDelta.type != null) {
593
+ toolCallTypes.set(toolCallDelta.index, toolCallDelta.type);
594
+ }
441
595
  toolCallTracker.processDelta(toolCallDelta);
442
596
  }
443
597
  }
@@ -464,6 +618,26 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
464
618
  usage?.prompt_cache_hit_tokens ?? undefined,
465
619
  promptCacheMissTokens:
466
620
  usage?.prompt_cache_miss_tokens ?? undefined,
621
+ ...(responseObject != null && { responseObject }),
622
+ ...(choiceIndex != null && { choiceIndex }),
623
+ ...(messageRole != null && { messageRole }),
624
+ ...(toolCallTypes.size > 0 && {
625
+ toolCallTypes: [...toolCallTypes.entries()]
626
+ .sort(([left], [right]) => left - right)
627
+ .map(([, type]) => type),
628
+ }),
629
+ ...((contentLogprobs.length > 0 ||
630
+ reasoningLogprobs.length > 0) && {
631
+ logprobs: {
632
+ ...(contentLogprobs.length > 0 && {
633
+ content: contentLogprobs,
634
+ }),
635
+ ...(reasoningLogprobs.length > 0 && {
636
+ reasoning_content: reasoningLogprobs,
637
+ }),
638
+ },
639
+ }),
640
+ ...(systemFingerprint != null && { systemFingerprint }),
467
641
  },
468
642
  },
469
643
  });
@@ -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
- LanguageModelV4CallOptions,
3
- SharedV4Warning,
1
+ import {
2
+ UnsupportedFunctionalityError,
3
+ type LanguageModelV4CallOptions,
4
+ type SharedV4Warning,
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: LanguageModelV4CallOptions['tools'];
15
17
  toolChoice?: LanguageModelV4CallOptions['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) {
@@ -91,6 +91,8 @@ export function createDeepSeek(
91
91
  url: ({ path }) => `${baseURL}${path}`,
92
92
  headers: getHeaders,
93
93
  fetch: options.fetch,
94
+ supportsAssistantPrefixCompletion: baseURL.endsWith('/beta'),
95
+ supportsStrictToolCalls: baseURL.endsWith('/beta'),
94
96
  });
95
97
  };
96
98
 
@@ -5,12 +5,16 @@ export const deepSeekFilesResponseSchema = lazySchema(() =>
5
5
  zodSchema(
6
6
  z.object({
7
7
  id: z.string(),
8
- object: z.string().nullish(),
9
- bytes: z.number().nullish(),
10
- created_at: z.number().nullish(),
8
+ // These fields are required by DeepSeek's OpenAPI schema, but they are
9
+ // not needed to construct the provider reference. Keep them nullish so
10
+ // uploads remain resilient to incomplete responses while validating any
11
+ // returned values precisely enough to avoid misleading metadata.
12
+ object: z.literal('file').nullish(),
13
+ bytes: z.number().int().nonnegative().nullish(),
14
+ created_at: z.number().int().nonnegative().nullish(),
11
15
  filename: z.string().nullish(),
12
- purpose: z.string().nullish(),
13
- expires_at: z.number().nullish(),
16
+ purpose: z.literal('user_data').nullish(),
17
+ expires_at: z.number().int().nonnegative().nullish(),
14
18
  }),
15
19
  ),
16
20
  );
@@ -1,13 +1,15 @@
1
- import type {
2
- FilesV4,
3
- FilesV4UploadFileCallOptions,
4
- FilesV4UploadFileResult,
1
+ import {
2
+ InvalidArgumentError,
3
+ type FilesV4,
4
+ type FilesV4UploadFileCallOptions,
5
+ type FilesV4UploadFileResult,
5
6
  } from '@ai-sdk/provider';
6
7
  import {
7
8
  combineHeaders,
8
9
  convertInlineFileDataToUint8Array,
9
10
  createJsonErrorResponseHandler,
10
11
  createJsonResponseHandler,
12
+ detectMediaType,
11
13
  parseProviderOptions,
12
14
  postFormDataToApi,
13
15
  type FetchFunction,
@@ -33,6 +35,34 @@ const deepSeekFailedResponseHandler = createJsonErrorResponseHandler({
33
35
  error.error.message,
34
36
  });
35
37
 
38
+ const MAX_FILE_SIZE_BYTES = 64 * 1024 * 1024;
39
+ const MAX_FILENAME_LENGTH = 512;
40
+
41
+ const supportedMediaTypes = new Set([
42
+ 'image/gif',
43
+ 'image/jpeg',
44
+ 'image/jpg',
45
+ 'image/png',
46
+ 'image/webp',
47
+ ]);
48
+
49
+ const genericMediaTypes = new Set([
50
+ '',
51
+ 'application/binary',
52
+ 'application/octet-stream',
53
+ 'binary/octet-stream',
54
+ 'image',
55
+ 'image/*',
56
+ ]);
57
+
58
+ const supportedFilenameExtensions = new Set([
59
+ 'gif',
60
+ 'jpeg',
61
+ 'jpg',
62
+ 'png',
63
+ 'webp',
64
+ ]);
65
+
36
66
  export class DeepSeekFiles implements FilesV4 {
37
67
  readonly specificationVersion = 'v4';
38
68
 
@@ -55,6 +85,8 @@ export class DeepSeekFiles implements FilesV4 {
55
85
  })) as DeepSeekFilesOptions | undefined;
56
86
 
57
87
  const fileBytes = convertInlineFileDataToUint8Array(data);
88
+ validateFileUpload({ fileBytes, mediaType, filename });
89
+
58
90
  const blob = new Blob([fileBytes], { type: mediaType });
59
91
 
60
92
  const formData = new FormData();
@@ -93,6 +125,7 @@ export class DeepSeekFiles implements FilesV4 {
93
125
  ...(mediaType != null ? { mediaType } : {}),
94
126
  providerMetadata: {
95
127
  deepseek: {
128
+ ...(response.object != null ? { object: response.object } : {}),
96
129
  ...(response.filename != null ? { filename: response.filename } : {}),
97
130
  ...(response.purpose != null ? { purpose: response.purpose } : {}),
98
131
  ...(response.bytes != null ? { bytes: response.bytes } : {}),
@@ -107,3 +140,95 @@ export class DeepSeekFiles implements FilesV4 {
107
140
  };
108
141
  }
109
142
  }
143
+
144
+ function validateFileUpload({
145
+ fileBytes,
146
+ mediaType,
147
+ filename,
148
+ }: {
149
+ fileBytes: Uint8Array;
150
+ mediaType: string;
151
+ filename: string | undefined;
152
+ }) {
153
+ if (fileBytes.length > MAX_FILE_SIZE_BYTES) {
154
+ throw new InvalidArgumentError({
155
+ argument: 'data',
156
+ message:
157
+ `DeepSeek file uploads must not exceed 64 MiB ` +
158
+ `(${MAX_FILE_SIZE_BYTES.toLocaleString('en-US')} bytes). ` +
159
+ `Received ${fileBytes.length.toLocaleString('en-US')} bytes.`,
160
+ });
161
+ }
162
+
163
+ if (filename != null) {
164
+ const filenameLength = Array.from(filename).length;
165
+
166
+ if (filenameLength > MAX_FILENAME_LENGTH) {
167
+ throw new InvalidArgumentError({
168
+ argument: 'filename',
169
+ message:
170
+ `DeepSeek filenames must not exceed ${MAX_FILENAME_LENGTH} characters. ` +
171
+ `Received ${filenameLength} characters.`,
172
+ });
173
+ }
174
+ }
175
+
176
+ const normalizedMediaType = normalizeMediaType(mediaType);
177
+ const detectedMediaType = detectMediaType({ data: fileBytes });
178
+
179
+ if (
180
+ detectedMediaType != null &&
181
+ !supportedMediaTypes.has(detectedMediaType)
182
+ ) {
183
+ throw new InvalidArgumentError({
184
+ argument: 'data',
185
+ message:
186
+ `DeepSeek file uploads support JPEG, PNG, GIF, and WebP images. ` +
187
+ `Detected unsupported file content type "${detectedMediaType}".`,
188
+ });
189
+ }
190
+
191
+ if (supportedMediaTypes.has(normalizedMediaType)) {
192
+ return;
193
+ }
194
+
195
+ if (!genericMediaTypes.has(normalizedMediaType)) {
196
+ throw new InvalidArgumentError({
197
+ argument: 'mediaType',
198
+ message:
199
+ `DeepSeek file uploads support JPEG, PNG, GIF, and WebP images. ` +
200
+ `Received unsupported media type "${mediaType}".`,
201
+ });
202
+ }
203
+
204
+ if (detectedMediaType != null || hasSupportedFilenameExtension(filename)) {
205
+ return;
206
+ }
207
+
208
+ throw new InvalidArgumentError({
209
+ argument: 'mediaType',
210
+ message:
211
+ `DeepSeek file uploads support JPEG, PNG, GIF, and WebP images. ` +
212
+ `Provide a supported media type or a filename ending in ` +
213
+ `.jpg, .jpeg, .png, .gif, or .webp. Received "${mediaType}".`,
214
+ });
215
+ }
216
+
217
+ function normalizeMediaType(mediaType: string): string {
218
+ return mediaType.split(';', 1)[0].trim().toLowerCase();
219
+ }
220
+
221
+ function hasSupportedFilenameExtension(filename: string | undefined): boolean {
222
+ if (filename == null) {
223
+ return false;
224
+ }
225
+
226
+ const extensionSeparatorIndex = filename.lastIndexOf('.');
227
+
228
+ return (
229
+ extensionSeparatorIndex !== -1 &&
230
+ supportedFilenameExtensions.has(
231
+ filename.slice(extensionSeparatorIndex + 1).toLowerCase(),
232
+ )
233
+ );
234
+ }
package/src/index.ts CHANGED
@@ -10,11 +10,14 @@ export type {
10
10
  } from './deepseek-provider';
11
11
  export { VERSION } from './version';
12
12
  export type {
13
+ DeepSeekAssistantMessageProviderOptions,
13
14
  DeepSeekLanguageModelChatOptions,
15
+ DeepSeekMessageProviderOptions,
14
16
  /** @deprecated Use `DeepSeekLanguageModelChatOptions` instead. */
15
17
  DeepSeekLanguageModelChatOptions as DeepSeekLanguageModelOptions,
16
18
  /** @deprecated Use `DeepSeekLanguageModelChatOptions` instead. */
17
19
  DeepSeekLanguageModelChatOptions as DeepSeekChatOptions,
18
20
  } from './chat/deepseek-chat-language-model-options';
19
21
  export type { DeepSeekErrorData } from './chat/deepseek-chat-api-types';
22
+ export type { DeepSeekFilePartProviderOptions } from './chat/deepseek-file-part-options';
20
23
  export type { DeepSeekFilesOptions } from './files/deepseek-files-options';