@ai-sdk/deepseek 2.0.7 → 2.0.9
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 +14 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +5 -4
- package/src/chat/__fixtures__/deepseek-json.json +32 -0
- package/src/chat/__fixtures__/deepseek-reasoning.chunks.txt +220 -0
- package/src/chat/__fixtures__/deepseek-reasoning.json +32 -0
- package/src/chat/__fixtures__/deepseek-text.chunks.txt +402 -0
- package/src/chat/__fixtures__/deepseek-text.json +28 -0
- package/src/chat/__fixtures__/deepseek-tool-call.chunks.txt +52 -0
- package/src/chat/__fixtures__/deepseek-tool-call.json +43 -0
- package/src/chat/__snapshots__/deepseek-chat-language-model.test.ts.snap +4106 -0
- package/src/chat/convert-to-deepseek-chat-messages.test.ts +332 -0
- package/src/chat/convert-to-deepseek-chat-messages.ts +177 -0
- package/src/chat/convert-to-deepseek-usage.ts +56 -0
- package/src/chat/deepseek-chat-api-types.ts +157 -0
- package/src/chat/deepseek-chat-language-model.test.ts +524 -0
- package/src/chat/deepseek-chat-language-model.ts +534 -0
- package/src/chat/deepseek-chat-options.ts +20 -0
- package/src/chat/deepseek-prepare-tools.test.ts +178 -0
- package/src/chat/deepseek-prepare-tools.ts +82 -0
- package/src/chat/get-response-metadata.ts +15 -0
- package/src/chat/map-deepseek-finish-reason.ts +20 -0
- package/src/deepseek-provider.ts +108 -0
- package/src/index.ts +8 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { convertToDeepSeekChatMessages } from './convert-to-deepseek-chat-messages';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('convertToDeepSeekChatMessages', () => {
|
|
5
|
+
describe('user messages', () => {
|
|
6
|
+
it('should convert messages with only a text part to a string content', async () => {
|
|
7
|
+
const result = convertToDeepSeekChatMessages({
|
|
8
|
+
prompt: [
|
|
9
|
+
{
|
|
10
|
+
role: 'user',
|
|
11
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
responseFormat: undefined,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(result).toMatchInlineSnapshot(`
|
|
18
|
+
{
|
|
19
|
+
"messages": [
|
|
20
|
+
{
|
|
21
|
+
"content": "Hello",
|
|
22
|
+
"role": "user",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
"warnings": [],
|
|
26
|
+
}
|
|
27
|
+
`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should warn about unsupported file parts', async () => {
|
|
31
|
+
const result = convertToDeepSeekChatMessages({
|
|
32
|
+
prompt: [
|
|
33
|
+
{
|
|
34
|
+
role: 'user',
|
|
35
|
+
content: [
|
|
36
|
+
{ type: 'text', text: 'Hello' },
|
|
37
|
+
{
|
|
38
|
+
type: 'file',
|
|
39
|
+
data: Buffer.from([0, 1, 2, 3]).toString('base64'),
|
|
40
|
+
mediaType: 'image/png',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
responseFormat: undefined,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(result).toMatchInlineSnapshot(`
|
|
49
|
+
{
|
|
50
|
+
"messages": [
|
|
51
|
+
{
|
|
52
|
+
"content": "Hello",
|
|
53
|
+
"role": "user",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
"warnings": [
|
|
57
|
+
{
|
|
58
|
+
"feature": "user message part type: file",
|
|
59
|
+
"type": "unsupported",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
`);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('tool calls', () => {
|
|
68
|
+
it('should stringify arguments to tool calls', () => {
|
|
69
|
+
const result = convertToDeepSeekChatMessages({
|
|
70
|
+
prompt: [
|
|
71
|
+
{
|
|
72
|
+
role: 'assistant',
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'tool-call',
|
|
76
|
+
input: { foo: 'bar123' },
|
|
77
|
+
toolCallId: 'quux',
|
|
78
|
+
toolName: 'thwomp',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
role: 'tool',
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: 'tool-result',
|
|
87
|
+
toolCallId: 'quux',
|
|
88
|
+
toolName: 'thwomp',
|
|
89
|
+
output: { type: 'json', value: { oof: '321rab' } },
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
responseFormat: undefined,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(result).toMatchInlineSnapshot(`
|
|
98
|
+
{
|
|
99
|
+
"messages": [
|
|
100
|
+
{
|
|
101
|
+
"content": "",
|
|
102
|
+
"reasoning_content": undefined,
|
|
103
|
+
"role": "assistant",
|
|
104
|
+
"tool_calls": [
|
|
105
|
+
{
|
|
106
|
+
"function": {
|
|
107
|
+
"arguments": "{"foo":"bar123"}",
|
|
108
|
+
"name": "thwomp",
|
|
109
|
+
},
|
|
110
|
+
"id": "quux",
|
|
111
|
+
"type": "function",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"content": "{"oof":"321rab"}",
|
|
117
|
+
"role": "tool",
|
|
118
|
+
"tool_call_id": "quux",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
"warnings": [],
|
|
122
|
+
}
|
|
123
|
+
`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle text output type in tool results', () => {
|
|
127
|
+
const result = convertToDeepSeekChatMessages({
|
|
128
|
+
prompt: [
|
|
129
|
+
{
|
|
130
|
+
role: 'assistant',
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: 'tool-call',
|
|
134
|
+
input: { query: 'weather' },
|
|
135
|
+
toolCallId: 'call-1',
|
|
136
|
+
toolName: 'getWeather',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
role: 'tool',
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'tool-result',
|
|
145
|
+
toolCallId: 'call-1',
|
|
146
|
+
toolName: 'getWeather',
|
|
147
|
+
output: { type: 'text', value: 'It is sunny today' },
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
responseFormat: undefined,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(result).toMatchInlineSnapshot(`
|
|
156
|
+
{
|
|
157
|
+
"messages": [
|
|
158
|
+
{
|
|
159
|
+
"content": "",
|
|
160
|
+
"reasoning_content": undefined,
|
|
161
|
+
"role": "assistant",
|
|
162
|
+
"tool_calls": [
|
|
163
|
+
{
|
|
164
|
+
"function": {
|
|
165
|
+
"arguments": "{"query":"weather"}",
|
|
166
|
+
"name": "getWeather",
|
|
167
|
+
},
|
|
168
|
+
"id": "call-1",
|
|
169
|
+
"type": "function",
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"content": "It is sunny today",
|
|
175
|
+
"role": "tool",
|
|
176
|
+
"tool_call_id": "call-1",
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
"warnings": [],
|
|
180
|
+
}
|
|
181
|
+
`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should support reasoning content in tool calls', () => {
|
|
185
|
+
const result = convertToDeepSeekChatMessages({
|
|
186
|
+
prompt: [
|
|
187
|
+
{
|
|
188
|
+
role: 'user',
|
|
189
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
role: 'assistant',
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: 'reasoning',
|
|
196
|
+
text: 'I think the tool will return the correct value.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'tool-call',
|
|
200
|
+
input: { foo: 'bar123' },
|
|
201
|
+
toolCallId: 'quux',
|
|
202
|
+
toolName: 'thwomp',
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
role: 'tool',
|
|
208
|
+
content: [
|
|
209
|
+
{
|
|
210
|
+
type: 'tool-result',
|
|
211
|
+
toolCallId: 'quux',
|
|
212
|
+
toolName: 'thwomp',
|
|
213
|
+
output: { type: 'json', value: { oof: '321rab' } },
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
responseFormat: undefined,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(result).toMatchInlineSnapshot(`
|
|
222
|
+
{
|
|
223
|
+
"messages": [
|
|
224
|
+
{
|
|
225
|
+
"content": "Hello",
|
|
226
|
+
"role": "user",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"content": "",
|
|
230
|
+
"reasoning_content": "I think the tool will return the correct value.",
|
|
231
|
+
"role": "assistant",
|
|
232
|
+
"tool_calls": [
|
|
233
|
+
{
|
|
234
|
+
"function": {
|
|
235
|
+
"arguments": "{"foo":"bar123"}",
|
|
236
|
+
"name": "thwomp",
|
|
237
|
+
},
|
|
238
|
+
"id": "quux",
|
|
239
|
+
"type": "function",
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"content": "{"oof":"321rab"}",
|
|
245
|
+
"role": "tool",
|
|
246
|
+
"tool_call_id": "quux",
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
"warnings": [],
|
|
250
|
+
}
|
|
251
|
+
`);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should filter out reasoning content from turns before the last user message', () => {
|
|
255
|
+
const result = convertToDeepSeekChatMessages({
|
|
256
|
+
prompt: [
|
|
257
|
+
{
|
|
258
|
+
role: 'user',
|
|
259
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
role: 'assistant',
|
|
263
|
+
content: [
|
|
264
|
+
{
|
|
265
|
+
type: 'reasoning',
|
|
266
|
+
text: 'I think the tool will return the correct value.',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
type: 'tool-call',
|
|
270
|
+
input: { foo: 'bar123' },
|
|
271
|
+
toolCallId: 'quux',
|
|
272
|
+
toolName: 'thwomp',
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
role: 'tool',
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: 'tool-result',
|
|
281
|
+
toolCallId: 'quux',
|
|
282
|
+
toolName: 'thwomp',
|
|
283
|
+
output: { type: 'json', value: { oof: '321rab' } },
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
role: 'user',
|
|
289
|
+
content: [{ type: 'text', text: 'Goodbye' }],
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
responseFormat: undefined,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
expect(result).toMatchInlineSnapshot(`
|
|
296
|
+
{
|
|
297
|
+
"messages": [
|
|
298
|
+
{
|
|
299
|
+
"content": "Hello",
|
|
300
|
+
"role": "user",
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"content": "",
|
|
304
|
+
"reasoning_content": undefined,
|
|
305
|
+
"role": "assistant",
|
|
306
|
+
"tool_calls": [
|
|
307
|
+
{
|
|
308
|
+
"function": {
|
|
309
|
+
"arguments": "{"foo":"bar123"}",
|
|
310
|
+
"name": "thwomp",
|
|
311
|
+
},
|
|
312
|
+
"id": "quux",
|
|
313
|
+
"type": "function",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"content": "{"oof":"321rab"}",
|
|
319
|
+
"role": "tool",
|
|
320
|
+
"tool_call_id": "quux",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"content": "Goodbye",
|
|
324
|
+
"role": "user",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
"warnings": [],
|
|
328
|
+
}
|
|
329
|
+
`);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LanguageModelV3CallOptions,
|
|
3
|
+
LanguageModelV3Prompt,
|
|
4
|
+
SharedV3Warning,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import { DeepSeekChatPrompt } from './deepseek-chat-api-types';
|
|
7
|
+
|
|
8
|
+
export function convertToDeepSeekChatMessages({
|
|
9
|
+
prompt,
|
|
10
|
+
responseFormat,
|
|
11
|
+
}: {
|
|
12
|
+
prompt: LanguageModelV3Prompt;
|
|
13
|
+
responseFormat: LanguageModelV3CallOptions['responseFormat'];
|
|
14
|
+
}): {
|
|
15
|
+
messages: DeepSeekChatPrompt;
|
|
16
|
+
warnings: Array<SharedV3Warning>;
|
|
17
|
+
} {
|
|
18
|
+
const messages: DeepSeekChatPrompt = [];
|
|
19
|
+
const warnings: Array<SharedV3Warning> = [];
|
|
20
|
+
|
|
21
|
+
// Inject system message if response format is JSON
|
|
22
|
+
if (responseFormat?.type === 'json') {
|
|
23
|
+
if (responseFormat.schema == null) {
|
|
24
|
+
messages.push({
|
|
25
|
+
role: 'system',
|
|
26
|
+
content: 'Return JSON.',
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
messages.push({
|
|
30
|
+
role: 'system',
|
|
31
|
+
content:
|
|
32
|
+
'Return JSON that conforms to the following schema: ' +
|
|
33
|
+
JSON.stringify(responseFormat.schema),
|
|
34
|
+
});
|
|
35
|
+
warnings.push({
|
|
36
|
+
type: 'compatibility',
|
|
37
|
+
feature: 'responseFormat JSON schema',
|
|
38
|
+
details: 'JSON response schema is injected into the system message.',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// TODO use findLastIndex once we use ES2023
|
|
44
|
+
let lastUserMessageIndex = -1;
|
|
45
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
46
|
+
if (prompt[i].role === 'user') {
|
|
47
|
+
lastUserMessageIndex = i;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let index = -1;
|
|
53
|
+
for (const { role, content } of prompt) {
|
|
54
|
+
index++;
|
|
55
|
+
|
|
56
|
+
switch (role) {
|
|
57
|
+
case 'system': {
|
|
58
|
+
messages.push({ role: 'system', content });
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case 'user': {
|
|
63
|
+
let userContent = '';
|
|
64
|
+
for (const part of content) {
|
|
65
|
+
if (part.type === 'text') {
|
|
66
|
+
userContent += part.text;
|
|
67
|
+
} else {
|
|
68
|
+
warnings.push({
|
|
69
|
+
type: 'unsupported',
|
|
70
|
+
feature: `user message part type: ${part.type}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
messages.push({
|
|
76
|
+
role: 'user',
|
|
77
|
+
content: userContent,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'assistant': {
|
|
83
|
+
let text = '';
|
|
84
|
+
let reasoning: string | undefined;
|
|
85
|
+
|
|
86
|
+
const toolCalls: Array<{
|
|
87
|
+
id: string;
|
|
88
|
+
type: 'function';
|
|
89
|
+
function: { name: string; arguments: string };
|
|
90
|
+
}> = [];
|
|
91
|
+
|
|
92
|
+
for (const part of content) {
|
|
93
|
+
switch (part.type) {
|
|
94
|
+
case 'text': {
|
|
95
|
+
text += part.text;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'reasoning': {
|
|
99
|
+
if (index <= lastUserMessageIndex) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (reasoning == null) {
|
|
104
|
+
reasoning = part.text;
|
|
105
|
+
} else {
|
|
106
|
+
reasoning += part.text;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'tool-call': {
|
|
111
|
+
toolCalls.push({
|
|
112
|
+
id: part.toolCallId,
|
|
113
|
+
type: 'function',
|
|
114
|
+
function: {
|
|
115
|
+
name: part.toolName,
|
|
116
|
+
arguments: JSON.stringify(part.input),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
messages.push({
|
|
125
|
+
role: 'assistant',
|
|
126
|
+
content: text,
|
|
127
|
+
reasoning_content: reasoning,
|
|
128
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case 'tool': {
|
|
135
|
+
for (const toolResponse of content) {
|
|
136
|
+
if (toolResponse.type === 'tool-approval-response') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const output = toolResponse.output;
|
|
140
|
+
|
|
141
|
+
let contentValue: string;
|
|
142
|
+
switch (output.type) {
|
|
143
|
+
case 'text':
|
|
144
|
+
case 'error-text':
|
|
145
|
+
contentValue = output.value;
|
|
146
|
+
break;
|
|
147
|
+
case 'execution-denied':
|
|
148
|
+
contentValue = output.reason ?? 'Tool execution denied.';
|
|
149
|
+
break;
|
|
150
|
+
case 'content':
|
|
151
|
+
case 'json':
|
|
152
|
+
case 'error-json':
|
|
153
|
+
contentValue = JSON.stringify(output.value);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
messages.push({
|
|
158
|
+
role: 'tool',
|
|
159
|
+
tool_call_id: toolResponse.toolCallId,
|
|
160
|
+
content: contentValue,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
default: {
|
|
167
|
+
warnings.push({
|
|
168
|
+
type: 'unsupported',
|
|
169
|
+
feature: `message role: ${role}`,
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { messages, warnings };
|
|
177
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LanguageModelV3Usage } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
export function convertDeepSeekUsage(
|
|
4
|
+
usage:
|
|
5
|
+
| {
|
|
6
|
+
prompt_tokens?: number | null | undefined;
|
|
7
|
+
completion_tokens?: number | null | undefined;
|
|
8
|
+
prompt_cache_hit_tokens?: number | null | undefined;
|
|
9
|
+
completion_tokens_details?:
|
|
10
|
+
| {
|
|
11
|
+
reasoning_tokens?: number | null | undefined;
|
|
12
|
+
}
|
|
13
|
+
| null
|
|
14
|
+
| undefined;
|
|
15
|
+
}
|
|
16
|
+
| undefined
|
|
17
|
+
| null,
|
|
18
|
+
): LanguageModelV3Usage {
|
|
19
|
+
if (usage == null) {
|
|
20
|
+
return {
|
|
21
|
+
inputTokens: {
|
|
22
|
+
total: undefined,
|
|
23
|
+
noCache: undefined,
|
|
24
|
+
cacheRead: undefined,
|
|
25
|
+
cacheWrite: undefined,
|
|
26
|
+
},
|
|
27
|
+
outputTokens: {
|
|
28
|
+
total: undefined,
|
|
29
|
+
text: undefined,
|
|
30
|
+
reasoning: undefined,
|
|
31
|
+
},
|
|
32
|
+
raw: undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const promptTokens = usage.prompt_tokens ?? 0;
|
|
37
|
+
const completionTokens = usage.completion_tokens ?? 0;
|
|
38
|
+
const cacheReadTokens = usage.prompt_cache_hit_tokens ?? 0;
|
|
39
|
+
const reasoningTokens =
|
|
40
|
+
usage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
inputTokens: {
|
|
44
|
+
total: promptTokens,
|
|
45
|
+
noCache: promptTokens - cacheReadTokens,
|
|
46
|
+
cacheRead: cacheReadTokens,
|
|
47
|
+
cacheWrite: undefined,
|
|
48
|
+
},
|
|
49
|
+
outputTokens: {
|
|
50
|
+
total: completionTokens,
|
|
51
|
+
text: completionTokens - reasoningTokens,
|
|
52
|
+
reasoning: reasoningTokens,
|
|
53
|
+
},
|
|
54
|
+
raw: usage,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
export type DeepSeekChatPrompt = Array<DeepSeekMessage>;
|
|
5
|
+
|
|
6
|
+
export type DeepSeekMessage =
|
|
7
|
+
| DeepSeekSystemMessage
|
|
8
|
+
| DeepSeekUserMessage
|
|
9
|
+
| DeepSeekAssistantMessage
|
|
10
|
+
| DeepSeekToolMessage;
|
|
11
|
+
|
|
12
|
+
export interface DeepSeekSystemMessage {
|
|
13
|
+
role: 'system';
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeepSeekUserMessage {
|
|
18
|
+
role: 'user';
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DeepSeekAssistantMessage {
|
|
23
|
+
role: 'assistant';
|
|
24
|
+
content?: string | null;
|
|
25
|
+
reasoning_content?: string;
|
|
26
|
+
tool_calls?: Array<DeepSeekMessageToolCall>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DeepSeekMessageToolCall {
|
|
30
|
+
type: 'function';
|
|
31
|
+
id: string;
|
|
32
|
+
function: {
|
|
33
|
+
arguments: string;
|
|
34
|
+
name: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DeepSeekToolMessage {
|
|
39
|
+
role: 'tool';
|
|
40
|
+
content: string;
|
|
41
|
+
tool_call_id: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DeepSeekFunctionTool {
|
|
45
|
+
type: 'function';
|
|
46
|
+
function: {
|
|
47
|
+
name: string;
|
|
48
|
+
description: string | undefined;
|
|
49
|
+
parameters: unknown;
|
|
50
|
+
strict?: boolean;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type DeepSeekToolChoice =
|
|
55
|
+
| { type: 'function'; function: { name: string } }
|
|
56
|
+
| 'auto'
|
|
57
|
+
| 'none'
|
|
58
|
+
| 'required'
|
|
59
|
+
| undefined;
|
|
60
|
+
|
|
61
|
+
const tokenUsageSchema = z
|
|
62
|
+
.object({
|
|
63
|
+
prompt_tokens: z.number().nullish(),
|
|
64
|
+
completion_tokens: z.number().nullish(),
|
|
65
|
+
prompt_cache_hit_tokens: z.number().nullish(),
|
|
66
|
+
prompt_cache_miss_tokens: z.number().nullish(),
|
|
67
|
+
total_tokens: z.number().nullish(),
|
|
68
|
+
completion_tokens_details: z
|
|
69
|
+
.object({
|
|
70
|
+
reasoning_tokens: z.number().nullish(),
|
|
71
|
+
})
|
|
72
|
+
.nullish(),
|
|
73
|
+
})
|
|
74
|
+
.nullish();
|
|
75
|
+
|
|
76
|
+
export type DeepSeekChatTokenUsage = z.infer<typeof tokenUsageSchema>;
|
|
77
|
+
|
|
78
|
+
export const deepSeekErrorSchema = z.object({
|
|
79
|
+
error: z.object({
|
|
80
|
+
message: z.string(),
|
|
81
|
+
type: z.string().nullish(),
|
|
82
|
+
param: z.any().nullish(),
|
|
83
|
+
code: z.union([z.string(), z.number()]).nullish(),
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type DeepSeekErrorData = z.infer<typeof deepSeekErrorSchema>;
|
|
88
|
+
|
|
89
|
+
// limited version of the schema, focussed on what is needed for the implementation
|
|
90
|
+
// this approach limits breakages when the API changes and increases efficiency
|
|
91
|
+
export const deepseekChatResponseSchema = z.object({
|
|
92
|
+
id: z.string().nullish(),
|
|
93
|
+
created: z.number().nullish(),
|
|
94
|
+
model: z.string().nullish(),
|
|
95
|
+
choices: z.array(
|
|
96
|
+
z.object({
|
|
97
|
+
message: z.object({
|
|
98
|
+
role: z.literal('assistant').nullish(),
|
|
99
|
+
content: z.string().nullish(),
|
|
100
|
+
reasoning_content: z.string().nullish(),
|
|
101
|
+
tool_calls: z
|
|
102
|
+
.array(
|
|
103
|
+
z.object({
|
|
104
|
+
id: z.string().nullish(),
|
|
105
|
+
function: z.object({
|
|
106
|
+
name: z.string(),
|
|
107
|
+
arguments: z.string(),
|
|
108
|
+
}),
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
.nullish(),
|
|
112
|
+
}),
|
|
113
|
+
finish_reason: z.string().nullish(),
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
usage: tokenUsageSchema,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// limited version of the schema, focussed on what is needed for the implementation
|
|
120
|
+
// this approach limits breakages when the API changes and increases efficiency
|
|
121
|
+
export const deepseekChatChunkSchema = lazySchema(() =>
|
|
122
|
+
zodSchema(
|
|
123
|
+
z.union([
|
|
124
|
+
z.object({
|
|
125
|
+
id: z.string().nullish(),
|
|
126
|
+
created: z.number().nullish(),
|
|
127
|
+
model: z.string().nullish(),
|
|
128
|
+
choices: z.array(
|
|
129
|
+
z.object({
|
|
130
|
+
delta: z
|
|
131
|
+
.object({
|
|
132
|
+
role: z.enum(['assistant']).nullish(),
|
|
133
|
+
content: z.string().nullish(),
|
|
134
|
+
reasoning_content: z.string().nullish(),
|
|
135
|
+
tool_calls: z
|
|
136
|
+
.array(
|
|
137
|
+
z.object({
|
|
138
|
+
index: z.number(),
|
|
139
|
+
id: z.string().nullish(),
|
|
140
|
+
function: z.object({
|
|
141
|
+
name: z.string().nullish(),
|
|
142
|
+
arguments: z.string().nullish(),
|
|
143
|
+
}),
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
.nullish(),
|
|
147
|
+
})
|
|
148
|
+
.nullish(),
|
|
149
|
+
finish_reason: z.string().nullish(),
|
|
150
|
+
}),
|
|
151
|
+
),
|
|
152
|
+
usage: tokenUsageSchema,
|
|
153
|
+
}),
|
|
154
|
+
deepSeekErrorSchema,
|
|
155
|
+
]),
|
|
156
|
+
),
|
|
157
|
+
);
|