@ai-sdk/mistral 0.0.0-70e0935a-20260114150030 → 0.0.0-98261322-20260122142521

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.
@@ -0,0 +1,372 @@
1
+ import { convertToMistralChatMessages } from './convert-to-mistral-chat-messages';
2
+ import { describe, it, expect } from 'vitest';
3
+
4
+ describe('user messages', () => {
5
+ it('should convert messages with image parts', async () => {
6
+ const result = convertToMistralChatMessages([
7
+ {
8
+ role: 'user',
9
+ content: [
10
+ { type: 'text', text: 'Hello' },
11
+ {
12
+ type: 'file',
13
+ data: 'AAECAw==',
14
+ mediaType: 'image/png',
15
+ },
16
+ ],
17
+ },
18
+ ]);
19
+
20
+ expect(result).toMatchSnapshot();
21
+ });
22
+
23
+ it('should convert messages with image parts from Uint8Array', async () => {
24
+ const result = convertToMistralChatMessages([
25
+ {
26
+ role: 'user',
27
+ content: [
28
+ { type: 'text', text: 'Hi' },
29
+ {
30
+ type: 'file',
31
+ data: new Uint8Array([0, 1, 2, 3]),
32
+ mediaType: 'image/png',
33
+ },
34
+ ],
35
+ },
36
+ ]);
37
+
38
+ expect(result).toMatchInlineSnapshot(`
39
+ [
40
+ {
41
+ "content": [
42
+ {
43
+ "text": "Hi",
44
+ "type": "text",
45
+ },
46
+ {
47
+ "image_url": "data:image/png;base64,AAECAw==",
48
+ "type": "image_url",
49
+ },
50
+ ],
51
+ "role": "user",
52
+ },
53
+ ]
54
+ `);
55
+ });
56
+
57
+ it('should convert messages with PDF file parts using URL', () => {
58
+ const result = convertToMistralChatMessages([
59
+ {
60
+ role: 'user',
61
+ content: [
62
+ { type: 'text', text: 'Please analyze this document' },
63
+ {
64
+ type: 'file',
65
+ data: new URL('https://example.com/document.pdf'),
66
+ mediaType: 'application/pdf',
67
+ },
68
+ ],
69
+ },
70
+ ]);
71
+
72
+ expect(result).toMatchSnapshot();
73
+ });
74
+
75
+ it('should convert messages with PDF file parts from Uint8Array', () => {
76
+ const result = convertToMistralChatMessages([
77
+ {
78
+ role: 'user',
79
+ content: [
80
+ { type: 'text', text: 'Analyze this PDF' },
81
+ {
82
+ type: 'file',
83
+ data: new Uint8Array([0, 1, 2, 3]),
84
+ mediaType: 'application/pdf',
85
+ },
86
+ ],
87
+ },
88
+ ]);
89
+
90
+ expect(result).toMatchInlineSnapshot(`
91
+ [
92
+ {
93
+ "content": [
94
+ {
95
+ "text": "Analyze this PDF",
96
+ "type": "text",
97
+ },
98
+ {
99
+ "document_url": "data:application/pdf;base64,AAECAw==",
100
+ "type": "document_url",
101
+ },
102
+ ],
103
+ "role": "user",
104
+ },
105
+ ]
106
+ `);
107
+ });
108
+
109
+ it('should convert messages with reasoning content', () => {
110
+ const result = convertToMistralChatMessages([
111
+ {
112
+ role: 'assistant',
113
+ content: [
114
+ { type: 'reasoning', text: 'Let me think about this...' },
115
+ { type: 'text', text: 'The answer is 42.' },
116
+ ],
117
+ },
118
+ ]);
119
+
120
+ expect(result).toMatchInlineSnapshot(`
121
+ [
122
+ {
123
+ "content": "Let me think about this...The answer is 42.",
124
+ "prefix": true,
125
+ "role": "assistant",
126
+ "tool_calls": undefined,
127
+ },
128
+ ]
129
+ `);
130
+ });
131
+ });
132
+
133
+ describe('tool calls', () => {
134
+ it('should stringify arguments to tool calls', () => {
135
+ const result = convertToMistralChatMessages([
136
+ {
137
+ role: 'assistant',
138
+ content: [
139
+ {
140
+ type: 'tool-call',
141
+ input: { key: 'arg-value' },
142
+ toolCallId: 'tool-call-id-1',
143
+ toolName: 'tool-1',
144
+ },
145
+ ],
146
+ },
147
+ {
148
+ role: 'tool',
149
+ content: [
150
+ {
151
+ type: 'tool-result',
152
+ toolCallId: 'tool-call-id-1',
153
+ toolName: 'tool-1',
154
+ output: { type: 'json', value: { key: 'result-value' } },
155
+ },
156
+ ],
157
+ },
158
+ ]);
159
+
160
+ expect(result).toMatchInlineSnapshot(`
161
+ [
162
+ {
163
+ "content": "",
164
+ "prefix": undefined,
165
+ "role": "assistant",
166
+ "tool_calls": [
167
+ {
168
+ "function": {
169
+ "arguments": "{"key":"arg-value"}",
170
+ "name": "tool-1",
171
+ },
172
+ "id": "tool-call-id-1",
173
+ "type": "function",
174
+ },
175
+ ],
176
+ },
177
+ {
178
+ "content": "{"key":"result-value"}",
179
+ "name": "tool-1",
180
+ "role": "tool",
181
+ "tool_call_id": "tool-call-id-1",
182
+ },
183
+ ]
184
+ `);
185
+ });
186
+
187
+ it('should handle text output format', () => {
188
+ const result = convertToMistralChatMessages([
189
+ {
190
+ role: 'assistant',
191
+ content: [
192
+ {
193
+ type: 'tool-call',
194
+ input: { query: 'test' },
195
+ toolCallId: 'tool-call-id-2',
196
+ toolName: 'text-tool',
197
+ },
198
+ ],
199
+ },
200
+ {
201
+ role: 'tool',
202
+ content: [
203
+ {
204
+ type: 'tool-result',
205
+ toolCallId: 'tool-call-id-2',
206
+ toolName: 'text-tool',
207
+ output: { type: 'text', value: 'This is a text response' },
208
+ },
209
+ ],
210
+ },
211
+ ]);
212
+
213
+ expect(result).toMatchInlineSnapshot(`
214
+ [
215
+ {
216
+ "content": "",
217
+ "prefix": undefined,
218
+ "role": "assistant",
219
+ "tool_calls": [
220
+ {
221
+ "function": {
222
+ "arguments": "{"query":"test"}",
223
+ "name": "text-tool",
224
+ },
225
+ "id": "tool-call-id-2",
226
+ "type": "function",
227
+ },
228
+ ],
229
+ },
230
+ {
231
+ "content": "This is a text response",
232
+ "name": "text-tool",
233
+ "role": "tool",
234
+ "tool_call_id": "tool-call-id-2",
235
+ },
236
+ ]
237
+ `);
238
+ });
239
+
240
+ it('should handle content output format', () => {
241
+ const result = convertToMistralChatMessages([
242
+ {
243
+ role: 'assistant',
244
+ content: [
245
+ {
246
+ type: 'tool-call',
247
+ input: { query: 'generate image' },
248
+ toolCallId: 'tool-call-id-3',
249
+ toolName: 'image-tool',
250
+ },
251
+ ],
252
+ },
253
+ {
254
+ role: 'tool',
255
+ content: [
256
+ {
257
+ type: 'tool-result',
258
+ toolCallId: 'tool-call-id-3',
259
+ toolName: 'image-tool',
260
+ output: {
261
+ type: 'content',
262
+ value: [
263
+ { type: 'text', text: 'Here is the result:' },
264
+ {
265
+ type: 'image-data',
266
+ data: 'base64data',
267
+ mediaType: 'image/png',
268
+ },
269
+ ],
270
+ },
271
+ },
272
+ ],
273
+ },
274
+ ]);
275
+
276
+ expect(result).toMatchInlineSnapshot(`
277
+ [
278
+ {
279
+ "content": "",
280
+ "prefix": undefined,
281
+ "role": "assistant",
282
+ "tool_calls": [
283
+ {
284
+ "function": {
285
+ "arguments": "{"query":"generate image"}",
286
+ "name": "image-tool",
287
+ },
288
+ "id": "tool-call-id-3",
289
+ "type": "function",
290
+ },
291
+ ],
292
+ },
293
+ {
294
+ "content": "[{"type":"text","text":"Here is the result:"},{"type":"image-data","data":"base64data","mediaType":"image/png"}]",
295
+ "name": "image-tool",
296
+ "role": "tool",
297
+ "tool_call_id": "tool-call-id-3",
298
+ },
299
+ ]
300
+ `);
301
+ });
302
+
303
+ it('should handle error output format', () => {
304
+ const result = convertToMistralChatMessages([
305
+ {
306
+ role: 'assistant',
307
+ content: [
308
+ {
309
+ type: 'tool-call',
310
+ input: { query: 'test' },
311
+ toolCallId: 'tool-call-id-4',
312
+ toolName: 'error-tool',
313
+ },
314
+ ],
315
+ },
316
+ {
317
+ role: 'tool',
318
+ content: [
319
+ {
320
+ type: 'tool-result',
321
+ toolCallId: 'tool-call-id-4',
322
+ toolName: 'error-tool',
323
+ output: { type: 'error-text', value: 'Invalid input provided' },
324
+ },
325
+ ],
326
+ },
327
+ ]);
328
+
329
+ expect(result).toMatchInlineSnapshot(`
330
+ [
331
+ {
332
+ "content": "",
333
+ "prefix": undefined,
334
+ "role": "assistant",
335
+ "tool_calls": [
336
+ {
337
+ "function": {
338
+ "arguments": "{"query":"test"}",
339
+ "name": "error-tool",
340
+ },
341
+ "id": "tool-call-id-4",
342
+ "type": "function",
343
+ },
344
+ ],
345
+ },
346
+ {
347
+ "content": "Invalid input provided",
348
+ "name": "error-tool",
349
+ "role": "tool",
350
+ "tool_call_id": "tool-call-id-4",
351
+ },
352
+ ]
353
+ `);
354
+ });
355
+ });
356
+
357
+ describe('assistant messages', () => {
358
+ it('should add prefix true to trailing assistant messages', () => {
359
+ const result = convertToMistralChatMessages([
360
+ {
361
+ role: 'user',
362
+ content: [{ type: 'text', text: 'Hello' }],
363
+ },
364
+ {
365
+ role: 'assistant',
366
+ content: [{ type: 'text', text: 'Hello!' }],
367
+ },
368
+ ]);
369
+
370
+ expect(result).toMatchSnapshot();
371
+ });
372
+ });
@@ -0,0 +1,163 @@
1
+ import {
2
+ LanguageModelV3DataContent,
3
+ LanguageModelV3Prompt,
4
+ UnsupportedFunctionalityError,
5
+ } from '@ai-sdk/provider';
6
+ import { MistralPrompt } from './mistral-chat-prompt';
7
+ import { convertToBase64 } from '@ai-sdk/provider-utils';
8
+
9
+ function formatFileUrl({
10
+ data,
11
+ mediaType,
12
+ }: {
13
+ data: LanguageModelV3DataContent;
14
+ mediaType: string;
15
+ }): string {
16
+ return data instanceof URL
17
+ ? data.toString()
18
+ : `data:${mediaType};base64,${convertToBase64(data as Uint8Array)}`;
19
+ }
20
+
21
+ export function convertToMistralChatMessages(
22
+ prompt: LanguageModelV3Prompt,
23
+ ): MistralPrompt {
24
+ const messages: MistralPrompt = [];
25
+
26
+ for (let i = 0; i < prompt.length; i++) {
27
+ const { role, content } = prompt[i];
28
+ const isLastMessage = i === prompt.length - 1;
29
+
30
+ switch (role) {
31
+ case 'system': {
32
+ messages.push({ role: 'system', content });
33
+ break;
34
+ }
35
+
36
+ case 'user': {
37
+ messages.push({
38
+ role: 'user',
39
+ content: content.map(part => {
40
+ switch (part.type) {
41
+ case 'text': {
42
+ return { type: 'text', text: part.text };
43
+ }
44
+
45
+ case 'file': {
46
+ if (part.mediaType.startsWith('image/')) {
47
+ const mediaType =
48
+ part.mediaType === 'image/*'
49
+ ? 'image/jpeg'
50
+ : part.mediaType;
51
+
52
+ return {
53
+ type: 'image_url',
54
+ image_url: formatFileUrl({ data: part.data, mediaType }),
55
+ };
56
+ } else if (part.mediaType === 'application/pdf') {
57
+ return {
58
+ type: 'document_url',
59
+ document_url: formatFileUrl({
60
+ data: part.data,
61
+ mediaType: 'application/pdf',
62
+ }),
63
+ };
64
+ } else {
65
+ throw new UnsupportedFunctionalityError({
66
+ functionality:
67
+ 'Only images and PDF file parts are supported',
68
+ });
69
+ }
70
+ }
71
+ }
72
+ }),
73
+ });
74
+ break;
75
+ }
76
+
77
+ case 'assistant': {
78
+ let text = '';
79
+ const toolCalls: Array<{
80
+ id: string;
81
+ type: 'function';
82
+ function: { name: string; arguments: string };
83
+ }> = [];
84
+
85
+ for (const part of content) {
86
+ switch (part.type) {
87
+ case 'text': {
88
+ text += part.text;
89
+ break;
90
+ }
91
+ case 'tool-call': {
92
+ toolCalls.push({
93
+ id: part.toolCallId,
94
+ type: 'function',
95
+ function: {
96
+ name: part.toolName,
97
+ arguments: JSON.stringify(part.input),
98
+ },
99
+ });
100
+ break;
101
+ }
102
+ case 'reasoning': {
103
+ text += part.text;
104
+ break;
105
+ }
106
+ default: {
107
+ throw new Error(
108
+ `Unsupported content type in assistant message: ${part.type}`,
109
+ );
110
+ }
111
+ }
112
+ }
113
+
114
+ messages.push({
115
+ role: 'assistant',
116
+ content: text,
117
+ prefix: isLastMessage ? true : undefined,
118
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
119
+ });
120
+
121
+ break;
122
+ }
123
+ case 'tool': {
124
+ for (const toolResponse of content) {
125
+ if (toolResponse.type === 'tool-approval-response') {
126
+ continue;
127
+ }
128
+ const output = toolResponse.output;
129
+
130
+ let contentValue: string;
131
+ switch (output.type) {
132
+ case 'text':
133
+ case 'error-text':
134
+ contentValue = output.value;
135
+ break;
136
+ case 'execution-denied':
137
+ contentValue = output.reason ?? 'Tool execution denied.';
138
+ break;
139
+ case 'content':
140
+ case 'json':
141
+ case 'error-json':
142
+ contentValue = JSON.stringify(output.value);
143
+ break;
144
+ }
145
+
146
+ messages.push({
147
+ role: 'tool',
148
+ name: toolResponse.toolName,
149
+ tool_call_id: toolResponse.toolCallId,
150
+ content: contentValue,
151
+ });
152
+ }
153
+ break;
154
+ }
155
+ default: {
156
+ const _exhaustiveCheck: never = role;
157
+ throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
158
+ }
159
+ }
160
+ }
161
+
162
+ return messages;
163
+ }
@@ -0,0 +1,15 @@
1
+ export function getResponseMetadata({
2
+ id,
3
+ model,
4
+ created,
5
+ }: {
6
+ id?: string | undefined | null;
7
+ created?: number | undefined | null;
8
+ model?: string | undefined | null;
9
+ }) {
10
+ return {
11
+ id: id ?? undefined,
12
+ modelId: model ?? undefined,
13
+ timestamp: created != null ? new Date(created * 1000) : undefined,
14
+ };
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { createMistral, mistral } from './mistral-provider';
2
+ export type {
3
+ MistralProvider,
4
+ MistralProviderSettings,
5
+ } from './mistral-provider';
6
+ export type { MistralLanguageModelOptions } from './mistral-chat-options';
7
+ export { VERSION } from './version';
@@ -0,0 +1,17 @@
1
+ import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
2
+
3
+ export function mapMistralFinishReason(
4
+ finishReason: string | null | undefined,
5
+ ): LanguageModelV3FinishReason['unified'] {
6
+ switch (finishReason) {
7
+ case 'stop':
8
+ return 'stop';
9
+ case 'length':
10
+ case 'model_length':
11
+ return 'length';
12
+ case 'tool_calls':
13
+ return 'tool-calls';
14
+ default:
15
+ return 'other';
16
+ }
17
+ }