@ai-sdk/openai 3.0.14 → 3.0.15

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.
Files changed (110) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/package.json +6 -5
  5. package/src/chat/__fixtures__/azure-model-router.1.chunks.txt +8 -0
  6. package/src/chat/__snapshots__/openai-chat-language-model.test.ts.snap +88 -0
  7. package/src/chat/convert-openai-chat-usage.ts +57 -0
  8. package/src/chat/convert-to-openai-chat-messages.test.ts +516 -0
  9. package/src/chat/convert-to-openai-chat-messages.ts +225 -0
  10. package/src/chat/get-response-metadata.ts +15 -0
  11. package/src/chat/map-openai-finish-reason.ts +19 -0
  12. package/src/chat/openai-chat-api.ts +198 -0
  13. package/src/chat/openai-chat-language-model.test.ts +3496 -0
  14. package/src/chat/openai-chat-language-model.ts +700 -0
  15. package/src/chat/openai-chat-options.ts +186 -0
  16. package/src/chat/openai-chat-prepare-tools.test.ts +322 -0
  17. package/src/chat/openai-chat-prepare-tools.ts +84 -0
  18. package/src/chat/openai-chat-prompt.ts +70 -0
  19. package/src/completion/convert-openai-completion-usage.ts +46 -0
  20. package/src/completion/convert-to-openai-completion-prompt.ts +93 -0
  21. package/src/completion/get-response-metadata.ts +15 -0
  22. package/src/completion/map-openai-finish-reason.ts +19 -0
  23. package/src/completion/openai-completion-api.ts +81 -0
  24. package/src/completion/openai-completion-language-model.test.ts +752 -0
  25. package/src/completion/openai-completion-language-model.ts +336 -0
  26. package/src/completion/openai-completion-options.ts +58 -0
  27. package/src/embedding/__snapshots__/openai-embedding-model.test.ts.snap +43 -0
  28. package/src/embedding/openai-embedding-api.ts +13 -0
  29. package/src/embedding/openai-embedding-model.test.ts +146 -0
  30. package/src/embedding/openai-embedding-model.ts +95 -0
  31. package/src/embedding/openai-embedding-options.ts +30 -0
  32. package/src/image/openai-image-api.ts +35 -0
  33. package/src/image/openai-image-model.test.ts +722 -0
  34. package/src/image/openai-image-model.ts +305 -0
  35. package/src/image/openai-image-options.ts +28 -0
  36. package/src/index.ts +9 -0
  37. package/src/internal/index.ts +19 -0
  38. package/src/openai-config.ts +18 -0
  39. package/src/openai-error.test.ts +34 -0
  40. package/src/openai-error.ts +22 -0
  41. package/src/openai-language-model-capabilities.test.ts +93 -0
  42. package/src/openai-language-model-capabilities.ts +54 -0
  43. package/src/openai-provider.test.ts +98 -0
  44. package/src/openai-provider.ts +270 -0
  45. package/src/openai-tools.ts +114 -0
  46. package/src/responses/__fixtures__/openai-apply-patch-tool-delete.1.chunks.txt +5 -0
  47. package/src/responses/__fixtures__/openai-apply-patch-tool.1.chunks.txt +38 -0
  48. package/src/responses/__fixtures__/openai-apply-patch-tool.1.json +69 -0
  49. package/src/responses/__fixtures__/openai-code-interpreter-tool.1.chunks.txt +393 -0
  50. package/src/responses/__fixtures__/openai-code-interpreter-tool.1.json +137 -0
  51. package/src/responses/__fixtures__/openai-error.1.chunks.txt +4 -0
  52. package/src/responses/__fixtures__/openai-error.1.json +8 -0
  53. package/src/responses/__fixtures__/openai-file-search-tool.1.chunks.txt +94 -0
  54. package/src/responses/__fixtures__/openai-file-search-tool.1.json +89 -0
  55. package/src/responses/__fixtures__/openai-file-search-tool.2.chunks.txt +93 -0
  56. package/src/responses/__fixtures__/openai-file-search-tool.2.json +112 -0
  57. package/src/responses/__fixtures__/openai-image-generation-tool.1.chunks.txt +16 -0
  58. package/src/responses/__fixtures__/openai-image-generation-tool.1.json +96 -0
  59. package/src/responses/__fixtures__/openai-local-shell-tool.1.chunks.txt +7 -0
  60. package/src/responses/__fixtures__/openai-local-shell-tool.1.json +70 -0
  61. package/src/responses/__fixtures__/openai-mcp-tool-approval.1.chunks.txt +11 -0
  62. package/src/responses/__fixtures__/openai-mcp-tool-approval.1.json +169 -0
  63. package/src/responses/__fixtures__/openai-mcp-tool-approval.2.chunks.txt +123 -0
  64. package/src/responses/__fixtures__/openai-mcp-tool-approval.2.json +176 -0
  65. package/src/responses/__fixtures__/openai-mcp-tool-approval.3.chunks.txt +11 -0
  66. package/src/responses/__fixtures__/openai-mcp-tool-approval.3.json +169 -0
  67. package/src/responses/__fixtures__/openai-mcp-tool-approval.4.chunks.txt +84 -0
  68. package/src/responses/__fixtures__/openai-mcp-tool-approval.4.json +182 -0
  69. package/src/responses/__fixtures__/openai-mcp-tool.1.chunks.txt +373 -0
  70. package/src/responses/__fixtures__/openai-mcp-tool.1.json +159 -0
  71. package/src/responses/__fixtures__/openai-reasoning-encrypted-content.1.chunks.txt +110 -0
  72. package/src/responses/__fixtures__/openai-reasoning-encrypted-content.1.json +117 -0
  73. package/src/responses/__fixtures__/openai-shell-tool.1.chunks.txt +182 -0
  74. package/src/responses/__fixtures__/openai-shell-tool.1.json +73 -0
  75. package/src/responses/__fixtures__/openai-web-search-tool.1.chunks.txt +185 -0
  76. package/src/responses/__fixtures__/openai-web-search-tool.1.json +266 -0
  77. package/src/responses/__snapshots__/openai-responses-language-model.test.ts.snap +10955 -0
  78. package/src/responses/convert-openai-responses-usage.ts +53 -0
  79. package/src/responses/convert-to-openai-responses-input.test.ts +2976 -0
  80. package/src/responses/convert-to-openai-responses-input.ts +578 -0
  81. package/src/responses/map-openai-responses-finish-reason.ts +22 -0
  82. package/src/responses/openai-responses-api.test.ts +89 -0
  83. package/src/responses/openai-responses-api.ts +1086 -0
  84. package/src/responses/openai-responses-language-model.test.ts +6927 -0
  85. package/src/responses/openai-responses-language-model.ts +1932 -0
  86. package/src/responses/openai-responses-options.ts +312 -0
  87. package/src/responses/openai-responses-prepare-tools.test.ts +924 -0
  88. package/src/responses/openai-responses-prepare-tools.ts +264 -0
  89. package/src/responses/openai-responses-provider-metadata.ts +39 -0
  90. package/src/speech/openai-speech-api.ts +38 -0
  91. package/src/speech/openai-speech-model.test.ts +202 -0
  92. package/src/speech/openai-speech-model.ts +137 -0
  93. package/src/speech/openai-speech-options.ts +22 -0
  94. package/src/tool/apply-patch.ts +141 -0
  95. package/src/tool/code-interpreter.ts +104 -0
  96. package/src/tool/file-search.ts +145 -0
  97. package/src/tool/image-generation.ts +126 -0
  98. package/src/tool/local-shell.test-d.ts +20 -0
  99. package/src/tool/local-shell.ts +72 -0
  100. package/src/tool/mcp.ts +125 -0
  101. package/src/tool/shell.ts +85 -0
  102. package/src/tool/web-search-preview.ts +139 -0
  103. package/src/tool/web-search.test-d.ts +13 -0
  104. package/src/tool/web-search.ts +179 -0
  105. package/src/transcription/openai-transcription-api.ts +37 -0
  106. package/src/transcription/openai-transcription-model.test.ts +507 -0
  107. package/src/transcription/openai-transcription-model.ts +232 -0
  108. package/src/transcription/openai-transcription-options.ts +50 -0
  109. package/src/transcription/transcription-test.mp3 +0 -0
  110. package/src/version.ts +6 -0
@@ -0,0 +1,516 @@
1
+ import { convertToOpenAIChatMessages } from './convert-to-openai-chat-messages';
2
+ import { describe, it, expect } from 'vitest';
3
+
4
+ describe('system messages', () => {
5
+ it('should forward system messages', async () => {
6
+ const result = convertToOpenAIChatMessages({
7
+ prompt: [{ role: 'system', content: 'You are a helpful assistant.' }],
8
+ });
9
+
10
+ expect(result.messages).toEqual([
11
+ { role: 'system', content: 'You are a helpful assistant.' },
12
+ ]);
13
+ });
14
+
15
+ it('should convert system messages to developer messages when requested', async () => {
16
+ const result = convertToOpenAIChatMessages({
17
+ prompt: [{ role: 'system', content: 'You are a helpful assistant.' }],
18
+ systemMessageMode: 'developer',
19
+ });
20
+
21
+ expect(result.messages).toEqual([
22
+ { role: 'developer', content: 'You are a helpful assistant.' },
23
+ ]);
24
+ });
25
+
26
+ it('should remove system messages when requested', async () => {
27
+ const result = convertToOpenAIChatMessages({
28
+ prompt: [{ role: 'system', content: 'You are a helpful assistant.' }],
29
+ systemMessageMode: 'remove',
30
+ });
31
+
32
+ expect(result.messages).toEqual([]);
33
+ });
34
+ });
35
+
36
+ describe('user messages', () => {
37
+ it('should convert messages with only a text part to a string content', async () => {
38
+ const result = convertToOpenAIChatMessages({
39
+ prompt: [
40
+ {
41
+ role: 'user',
42
+ content: [{ type: 'text', text: 'Hello' }],
43
+ },
44
+ ],
45
+ });
46
+
47
+ expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }]);
48
+ });
49
+
50
+ it('should convert messages with image parts', async () => {
51
+ const result = convertToOpenAIChatMessages({
52
+ prompt: [
53
+ {
54
+ role: 'user',
55
+ content: [
56
+ { type: 'text', text: 'Hello' },
57
+ {
58
+ type: 'file',
59
+ mediaType: 'image/png',
60
+ data: Buffer.from([0, 1, 2, 3]).toString('base64'),
61
+ },
62
+ ],
63
+ },
64
+ ],
65
+ });
66
+
67
+ expect(result.messages).toEqual([
68
+ {
69
+ role: 'user',
70
+ content: [
71
+ { type: 'text', text: 'Hello' },
72
+ {
73
+ type: 'image_url',
74
+ image_url: { url: 'data:image/png;base64,AAECAw==' },
75
+ },
76
+ ],
77
+ },
78
+ ]);
79
+ });
80
+
81
+ it('should add image detail when specified through extension', async () => {
82
+ const result = convertToOpenAIChatMessages({
83
+ prompt: [
84
+ {
85
+ role: 'user',
86
+ content: [
87
+ {
88
+ type: 'file',
89
+ mediaType: 'image/png',
90
+ data: Buffer.from([0, 1, 2, 3]).toString('base64'),
91
+ providerOptions: {
92
+ openai: {
93
+ imageDetail: 'low',
94
+ },
95
+ },
96
+ },
97
+ ],
98
+ },
99
+ ],
100
+ });
101
+
102
+ expect(result.messages).toEqual([
103
+ {
104
+ role: 'user',
105
+ content: [
106
+ {
107
+ type: 'image_url',
108
+ image_url: {
109
+ url: 'data:image/png;base64,AAECAw==',
110
+ detail: 'low',
111
+ },
112
+ },
113
+ ],
114
+ },
115
+ ]);
116
+ });
117
+
118
+ describe('file parts', () => {
119
+ it('should throw for unsupported mime types', () => {
120
+ expect(() =>
121
+ convertToOpenAIChatMessages({
122
+ prompt: [
123
+ {
124
+ role: 'user',
125
+ content: [
126
+ {
127
+ type: 'file',
128
+ data: 'AAECAw==',
129
+ mediaType: 'application/something',
130
+ },
131
+ ],
132
+ },
133
+ ],
134
+ }),
135
+ ).toThrow('file part media type application/something');
136
+ });
137
+
138
+ it('should throw for URL data', () => {
139
+ expect(() =>
140
+ convertToOpenAIChatMessages({
141
+ prompt: [
142
+ {
143
+ role: 'user',
144
+ content: [
145
+ {
146
+ type: 'file',
147
+ data: new URL('https://example.com/foo.wav'),
148
+ mediaType: 'audio/wav',
149
+ },
150
+ ],
151
+ },
152
+ ],
153
+ }),
154
+ ).toThrow('audio file parts with URLs');
155
+ });
156
+
157
+ it('should add audio content for audio/wav file parts', () => {
158
+ const result = convertToOpenAIChatMessages({
159
+ prompt: [
160
+ {
161
+ role: 'user',
162
+ content: [
163
+ {
164
+ type: 'file',
165
+ data: 'AAECAw==',
166
+ mediaType: 'audio/wav',
167
+ },
168
+ ],
169
+ },
170
+ ],
171
+ });
172
+
173
+ expect(result.messages).toEqual([
174
+ {
175
+ role: 'user',
176
+ content: [
177
+ {
178
+ type: 'input_audio',
179
+ input_audio: { data: 'AAECAw==', format: 'wav' },
180
+ },
181
+ ],
182
+ },
183
+ ]);
184
+ });
185
+
186
+ it('should add audio content for audio/mpeg file parts', () => {
187
+ const result = convertToOpenAIChatMessages({
188
+ prompt: [
189
+ {
190
+ role: 'user',
191
+ content: [
192
+ {
193
+ type: 'file',
194
+ data: 'AAECAw==',
195
+ mediaType: 'audio/mpeg',
196
+ },
197
+ ],
198
+ },
199
+ ],
200
+ });
201
+
202
+ expect(result.messages).toEqual([
203
+ {
204
+ role: 'user',
205
+ content: [
206
+ {
207
+ type: 'input_audio',
208
+ input_audio: { data: 'AAECAw==', format: 'mp3' },
209
+ },
210
+ ],
211
+ },
212
+ ]);
213
+ });
214
+
215
+ it('should add audio content for audio/mp3 file parts', () => {
216
+ const result = convertToOpenAIChatMessages({
217
+ prompt: [
218
+ {
219
+ role: 'user',
220
+ content: [
221
+ {
222
+ type: 'file',
223
+ data: 'AAECAw==',
224
+ mediaType: 'audio/mp3', // not official but sometimes used
225
+ },
226
+ ],
227
+ },
228
+ ],
229
+ });
230
+
231
+ expect(result.messages).toEqual([
232
+ {
233
+ role: 'user',
234
+ content: [
235
+ {
236
+ type: 'input_audio',
237
+ input_audio: { data: 'AAECAw==', format: 'mp3' },
238
+ },
239
+ ],
240
+ },
241
+ ]);
242
+ });
243
+
244
+ it('should convert messages with PDF file parts', async () => {
245
+ const base64Data = 'AQIDBAU='; // Base64 encoding of pdfData
246
+
247
+ const result = convertToOpenAIChatMessages({
248
+ prompt: [
249
+ {
250
+ role: 'user',
251
+ content: [
252
+ {
253
+ type: 'file',
254
+ mediaType: 'application/pdf',
255
+ data: base64Data,
256
+ filename: 'document.pdf',
257
+ },
258
+ ],
259
+ },
260
+ ],
261
+ systemMessageMode: 'system',
262
+ });
263
+
264
+ expect(result.messages).toEqual([
265
+ {
266
+ role: 'user',
267
+ content: [
268
+ {
269
+ type: 'file',
270
+ file: {
271
+ filename: 'document.pdf',
272
+ file_data: 'data:application/pdf;base64,AQIDBAU=',
273
+ },
274
+ },
275
+ ],
276
+ },
277
+ ]);
278
+ });
279
+
280
+ it('should convert messages with binary PDF file parts', async () => {
281
+ const data = Uint8Array.from([1, 2, 3, 4, 5]);
282
+
283
+ const result = convertToOpenAIChatMessages({
284
+ prompt: [
285
+ {
286
+ role: 'user',
287
+ content: [
288
+ {
289
+ type: 'file',
290
+ mediaType: 'application/pdf',
291
+ data,
292
+ filename: 'document.pdf',
293
+ },
294
+ ],
295
+ },
296
+ ],
297
+ systemMessageMode: 'system',
298
+ });
299
+
300
+ expect(result.messages).toEqual([
301
+ {
302
+ role: 'user',
303
+ content: [
304
+ {
305
+ type: 'file',
306
+ file: {
307
+ filename: 'document.pdf',
308
+ file_data: 'data:application/pdf;base64,AQIDBAU=',
309
+ },
310
+ },
311
+ ],
312
+ },
313
+ ]);
314
+ });
315
+
316
+ it('should convert messages with PDF file parts using file_id', async () => {
317
+ const result = convertToOpenAIChatMessages({
318
+ prompt: [
319
+ {
320
+ role: 'user',
321
+ content: [
322
+ {
323
+ type: 'file',
324
+ mediaType: 'application/pdf',
325
+ data: 'file-pdf-12345',
326
+ },
327
+ ],
328
+ },
329
+ ],
330
+ });
331
+
332
+ expect(result.messages).toEqual([
333
+ {
334
+ role: 'user',
335
+ content: [
336
+ {
337
+ type: 'file',
338
+ file: {
339
+ file_id: 'file-pdf-12345',
340
+ },
341
+ },
342
+ ],
343
+ },
344
+ ]);
345
+ });
346
+
347
+ it('should use default filename for PDF file parts when not provided', async () => {
348
+ const base64Data = 'AQIDBAU=';
349
+
350
+ const result = convertToOpenAIChatMessages({
351
+ prompt: [
352
+ {
353
+ role: 'user',
354
+ content: [
355
+ {
356
+ type: 'file',
357
+ mediaType: 'application/pdf',
358
+ data: base64Data,
359
+ },
360
+ ],
361
+ },
362
+ ],
363
+ systemMessageMode: 'system',
364
+ });
365
+
366
+ expect(result.messages).toEqual([
367
+ {
368
+ role: 'user',
369
+ content: [
370
+ {
371
+ type: 'file',
372
+ file: {
373
+ filename: 'part-0.pdf',
374
+ file_data: 'data:application/pdf;base64,AQIDBAU=',
375
+ },
376
+ },
377
+ ],
378
+ },
379
+ ]);
380
+ });
381
+
382
+ it('should throw error for unsupported file types', async () => {
383
+ const base64Data = 'AQIDBAU=';
384
+
385
+ expect(() => {
386
+ convertToOpenAIChatMessages({
387
+ prompt: [
388
+ {
389
+ role: 'user',
390
+ content: [
391
+ {
392
+ type: 'file',
393
+ mediaType: 'text/plain',
394
+ data: base64Data,
395
+ },
396
+ ],
397
+ },
398
+ ],
399
+ systemMessageMode: 'system',
400
+ });
401
+ }).toThrow('file part media type text/plain');
402
+ });
403
+
404
+ it('should throw error for file URLs', async () => {
405
+ expect(() => {
406
+ convertToOpenAIChatMessages({
407
+ prompt: [
408
+ {
409
+ role: 'user',
410
+ content: [
411
+ {
412
+ type: 'file',
413
+ mediaType: 'application/pdf',
414
+ data: new URL('https://example.com/document.pdf'),
415
+ },
416
+ ],
417
+ },
418
+ ],
419
+ systemMessageMode: 'system',
420
+ });
421
+ }).toThrow('PDF file parts with URLs');
422
+ });
423
+ });
424
+ });
425
+
426
+ describe('tool calls', () => {
427
+ it('should stringify arguments to tool calls', () => {
428
+ const result = convertToOpenAIChatMessages({
429
+ prompt: [
430
+ {
431
+ role: 'assistant',
432
+ content: [
433
+ {
434
+ type: 'tool-call',
435
+ input: { foo: 'bar123' },
436
+ toolCallId: 'quux',
437
+ toolName: 'thwomp',
438
+ },
439
+ ],
440
+ },
441
+ {
442
+ role: 'tool',
443
+ content: [
444
+ {
445
+ type: 'tool-result',
446
+ toolCallId: 'quux',
447
+ toolName: 'thwomp',
448
+ output: { type: 'json', value: { oof: '321rab' } },
449
+ },
450
+ ],
451
+ },
452
+ ],
453
+ });
454
+
455
+ expect(result.messages).toEqual([
456
+ {
457
+ role: 'assistant',
458
+ content: '',
459
+ tool_calls: [
460
+ {
461
+ type: 'function',
462
+ id: 'quux',
463
+ function: {
464
+ name: 'thwomp',
465
+ arguments: JSON.stringify({ foo: 'bar123' }),
466
+ },
467
+ },
468
+ ],
469
+ },
470
+ {
471
+ role: 'tool',
472
+ content: JSON.stringify({ oof: '321rab' }),
473
+ tool_call_id: 'quux',
474
+ },
475
+ ]);
476
+ });
477
+
478
+ it('should handle different tool output types', () => {
479
+ const result = convertToOpenAIChatMessages({
480
+ prompt: [
481
+ {
482
+ role: 'tool',
483
+ content: [
484
+ {
485
+ type: 'tool-result',
486
+ toolCallId: 'text-tool',
487
+ toolName: 'text-tool',
488
+ output: { type: 'text', value: 'Hello world' },
489
+ },
490
+ {
491
+ type: 'tool-result',
492
+ toolCallId: 'error-tool',
493
+ toolName: 'error-tool',
494
+ output: { type: 'error-text', value: 'Something went wrong' },
495
+ },
496
+ ],
497
+ },
498
+ ],
499
+ });
500
+
501
+ expect(result.messages).toMatchInlineSnapshot(`
502
+ [
503
+ {
504
+ "content": "Hello world",
505
+ "role": "tool",
506
+ "tool_call_id": "text-tool",
507
+ },
508
+ {
509
+ "content": "Something went wrong",
510
+ "role": "tool",
511
+ "tool_call_id": "error-tool",
512
+ },
513
+ ]
514
+ `);
515
+ });
516
+ });