@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.
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/dist/index.d.ts +57 -19
- package/dist/index.js +561 -123
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +54 -4
- package/dist/internal/index.js +455 -108
- package/dist/internal/index.js.map +1 -1
- package/docs/30-deepseek.mdx +306 -23
- package/package.json +6 -6
- package/src/chat/convert-to-deepseek-chat-messages.ts +166 -19
- package/src/chat/convert-to-deepseek-usage.ts +1 -1
- package/src/chat/deepseek-chat-api-types.ts +50 -5
- package/src/chat/deepseek-chat-language-model-options.ts +102 -11
- package/src/chat/deepseek-chat-language-model.ts +193 -19
- package/src/chat/deepseek-file-part-options.ts +24 -0
- package/src/chat/deepseek-prepare-tools.ts +29 -3
- package/src/deepseek-provider.ts +2 -0
- package/src/files/deepseek-files-api.ts +9 -5
- package/src/files/deepseek-files.ts +129 -4
- package/src/index.ts +3 -0
|
@@ -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
|
-
:
|
|
156
|
-
? { 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
|
-
|
|
164
|
-
|
|
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
|
-
:
|
|
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:
|
|
184
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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) {
|
package/src/deepseek-provider.ts
CHANGED
|
@@ -5,12 +5,16 @@ export const deepSeekFilesResponseSchema = lazySchema(() =>
|
|
|
5
5
|
zodSchema(
|
|
6
6
|
z.object({
|
|
7
7
|
id: z.string(),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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';
|