@ai-sdk/openai 4.0.0-beta.4 → 4.0.0-beta.41

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 (66) hide show
  1. package/CHANGELOG.md +399 -22
  2. package/README.md +2 -0
  3. package/dist/index.d.ts +166 -49
  4. package/dist/index.js +2454 -1627
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +176 -53
  7. package/dist/internal/index.js +2220 -1648
  8. package/dist/internal/index.js.map +1 -1
  9. package/docs/03-openai.mdx +292 -22
  10. package/package.json +13 -14
  11. package/src/chat/convert-openai-chat-usage.ts +2 -2
  12. package/src/chat/convert-to-openai-chat-messages.ts +99 -71
  13. package/src/chat/map-openai-finish-reason.ts +2 -2
  14. package/src/chat/openai-chat-api.ts +6 -2
  15. package/src/chat/openai-chat-language-model.ts +68 -164
  16. package/src/chat/openai-chat-options.ts +10 -1
  17. package/src/chat/openai-chat-prepare-tools.ts +7 -7
  18. package/src/completion/convert-openai-completion-usage.ts +2 -2
  19. package/src/completion/convert-to-openai-completion-prompt.ts +2 -3
  20. package/src/completion/map-openai-finish-reason.ts +2 -2
  21. package/src/completion/openai-completion-api.ts +5 -2
  22. package/src/completion/openai-completion-language-model.ts +46 -30
  23. package/src/completion/openai-completion-options.ts +5 -1
  24. package/src/embedding/openai-embedding-model.ts +25 -8
  25. package/src/embedding/openai-embedding-options.ts +5 -1
  26. package/src/files/openai-files-api.ts +17 -0
  27. package/src/files/openai-files-options.ts +22 -0
  28. package/src/files/openai-files.ts +100 -0
  29. package/src/image/openai-image-model.ts +31 -15
  30. package/src/image/openai-image-options.ts +3 -0
  31. package/src/index.ts +2 -0
  32. package/src/openai-config.ts +7 -7
  33. package/src/openai-language-model-capabilities.ts +3 -2
  34. package/src/openai-provider.ts +63 -30
  35. package/src/openai-tools.ts +12 -1
  36. package/src/responses/convert-openai-responses-usage.ts +2 -2
  37. package/src/responses/convert-to-openai-responses-input.ts +244 -77
  38. package/src/responses/map-openai-responses-finish-reason.ts +2 -2
  39. package/src/responses/openai-responses-api.ts +141 -3
  40. package/src/responses/openai-responses-language-model.ts +274 -61
  41. package/src/responses/openai-responses-options.ts +29 -3
  42. package/src/responses/openai-responses-prepare-tools.ts +48 -15
  43. package/src/responses/openai-responses-provider-metadata.ts +12 -2
  44. package/src/skills/openai-skills-api.ts +31 -0
  45. package/src/skills/openai-skills.ts +83 -0
  46. package/src/speech/openai-speech-model.ts +28 -12
  47. package/src/speech/openai-speech-options.ts +5 -1
  48. package/src/tool/apply-patch.ts +33 -32
  49. package/src/tool/code-interpreter.ts +40 -41
  50. package/src/tool/custom.ts +2 -8
  51. package/src/tool/file-search.ts +3 -3
  52. package/src/tool/image-generation.ts +2 -2
  53. package/src/tool/local-shell.ts +2 -2
  54. package/src/tool/mcp.ts +3 -3
  55. package/src/tool/shell.ts +9 -4
  56. package/src/tool/tool-search.ts +98 -0
  57. package/src/tool/web-search-preview.ts +2 -2
  58. package/src/tool/web-search.ts +2 -2
  59. package/src/transcription/openai-transcription-model.ts +30 -14
  60. package/src/transcription/openai-transcription-options.ts +5 -1
  61. package/dist/index.d.mts +0 -1107
  62. package/dist/index.mjs +0 -6508
  63. package/dist/index.mjs.map +0 -1
  64. package/dist/internal/index.d.mts +0 -1137
  65. package/dist/internal/index.mjs +0 -6321
  66. package/dist/internal/index.mjs.map +0 -1
@@ -1,15 +1,19 @@
1
1
  import {
2
- LanguageModelV3Prompt,
3
- LanguageModelV3ToolApprovalResponsePart,
4
- SharedV3Warning,
5
2
  UnsupportedFunctionalityError,
3
+ type LanguageModelV4Prompt,
4
+ type LanguageModelV4ToolApprovalResponsePart,
5
+ type SharedV4Warning,
6
6
  } from '@ai-sdk/provider';
7
7
  import {
8
8
  convertToBase64,
9
+ getTopLevelMediaType,
9
10
  isNonNullable,
11
+ parseJSON,
10
12
  parseProviderOptions,
11
- ToolNameMapping,
13
+ resolveFullMediaType,
14
+ resolveProviderReference,
12
15
  validateTypes,
16
+ type ToolNameMapping,
13
17
  } from '@ai-sdk/provider-utils';
14
18
  import { z } from 'zod/v4';
15
19
  import {
@@ -21,16 +25,27 @@ import {
21
25
  localShellOutputSchema,
22
26
  } from '../tool/local-shell';
23
27
  import { shellInputSchema, shellOutputSchema } from '../tool/shell';
24
- import {
28
+ import type {
29
+ OpenAIResponsesCompactionItem,
25
30
  OpenAIResponsesCustomToolCallOutput,
26
31
  OpenAIResponsesFunctionCallOutput,
27
32
  OpenAIResponsesInput,
28
33
  OpenAIResponsesReasoning,
29
34
  } from './openai-responses-api';
35
+ import {
36
+ toolSearchInputSchema,
37
+ toolSearchOutputSchema,
38
+ } from '../tool/tool-search';
39
+
40
+ function serializeToolCallArguments(input: unknown): string {
41
+ return JSON.stringify(input === undefined ? {} : input);
42
+ }
30
43
 
31
44
  /**
32
- * Check if a string is a file ID based on the given prefixes
33
- * Returns false if prefixes is undefined (disables file ID detection)
45
+ * This is soft-deprecated. Use provider references instead. Kept for backward compatibility
46
+ * with the `fileIdPrefixes` option.
47
+ *
48
+ * TODO: remove in v8
34
49
  */
35
50
  function isFileId(data: string, prefixes?: readonly string[]): boolean {
36
51
  if (!prefixes) return false;
@@ -50,10 +65,11 @@ export async function convertToOpenAIResponsesInput({
50
65
  hasApplyPatchTool = false,
51
66
  customProviderToolNames,
52
67
  }: {
53
- prompt: LanguageModelV3Prompt;
68
+ prompt: LanguageModelV4Prompt;
54
69
  toolNameMapping: ToolNameMapping;
55
70
  systemMessageMode: 'system' | 'developer' | 'remove';
56
71
  providerOptionsName: string;
72
+ /** @deprecated Use provider references instead. */
57
73
  fileIdPrefixes?: readonly string[];
58
74
  store: boolean;
59
75
  hasConversation?: boolean; // when true, skip assistant messages that already have item IDs
@@ -63,10 +79,10 @@ export async function convertToOpenAIResponsesInput({
63
79
  customProviderToolNames?: Set<string>;
64
80
  }): Promise<{
65
81
  input: OpenAIResponsesInput;
66
- warnings: Array<SharedV3Warning>;
82
+ warnings: Array<SharedV4Warning>;
67
83
  }> {
68
84
  let input: OpenAIResponsesInput = [];
69
- const warnings: Array<SharedV3Warning> = [];
85
+ const warnings: Array<SharedV4Warning> = [];
70
86
  const processedApprovalIds = new Set<string>();
71
87
 
72
88
  for (const { role, content } of prompt) {
@@ -107,46 +123,79 @@ export async function convertToOpenAIResponsesInput({
107
123
  return { type: 'input_text', text: part.text };
108
124
  }
109
125
  case 'file': {
110
- if (part.mediaType.startsWith('image/')) {
111
- const mediaType =
112
- part.mediaType === 'image/*'
113
- ? 'image/jpeg'
114
- : part.mediaType;
115
-
116
- return {
117
- type: 'input_image',
118
- ...(part.data instanceof URL
119
- ? { image_url: part.data.toString() }
120
- : typeof part.data === 'string' &&
121
- isFileId(part.data, fileIdPrefixes)
122
- ? { file_id: part.data }
123
- : {
124
- image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
125
- }),
126
- detail:
127
- part.providerOptions?.[providerOptionsName]?.imageDetail,
128
- };
129
- } else if (part.mediaType === 'application/pdf') {
130
- if (part.data instanceof URL) {
126
+ switch (part.data.type) {
127
+ case 'reference': {
128
+ const fileId = resolveProviderReference({
129
+ reference: part.data.reference,
130
+ provider: providerOptionsName,
131
+ });
132
+
133
+ if (getTopLevelMediaType(part.mediaType) === 'image') {
134
+ return {
135
+ type: 'input_image',
136
+ file_id: fileId,
137
+ detail:
138
+ part.providerOptions?.[providerOptionsName]
139
+ ?.imageDetail,
140
+ };
141
+ }
142
+
131
143
  return {
132
144
  type: 'input_file',
133
- file_url: part.data.toString(),
145
+ file_id: fileId,
134
146
  };
135
147
  }
136
- return {
137
- type: 'input_file',
138
- ...(typeof part.data === 'string' &&
139
- isFileId(part.data, fileIdPrefixes)
140
- ? { file_id: part.data }
141
- : {
142
- filename: part.filename ?? `part-${index}.pdf`,
143
- file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
144
- }),
145
- };
146
- } else {
147
- throw new UnsupportedFunctionalityError({
148
- functionality: `file part media type ${part.mediaType}`,
149
- });
148
+ case 'text': {
149
+ throw new UnsupportedFunctionalityError({
150
+ functionality: 'text file parts',
151
+ });
152
+ }
153
+ case 'url':
154
+ case 'data': {
155
+ const topLevel = getTopLevelMediaType(part.mediaType);
156
+
157
+ if (topLevel === 'image') {
158
+ return {
159
+ type: 'input_image',
160
+ ...(part.data.type === 'url'
161
+ ? { image_url: part.data.url.toString() }
162
+ : typeof part.data.data === 'string' &&
163
+ isFileId(part.data.data, fileIdPrefixes)
164
+ ? { file_id: part.data.data }
165
+ : {
166
+ image_url: `data:${resolveFullMediaType({ part })};base64,${convertToBase64(part.data.data)}`,
167
+ }),
168
+ detail:
169
+ part.providerOptions?.[providerOptionsName]
170
+ ?.imageDetail,
171
+ };
172
+ } else {
173
+ if (part.data.type === 'url') {
174
+ return {
175
+ type: 'input_file',
176
+ file_url: part.data.url.toString(),
177
+ };
178
+ }
179
+
180
+ const fullMediaType = resolveFullMediaType({ part });
181
+ if (fullMediaType !== 'application/pdf') {
182
+ throw new UnsupportedFunctionalityError({
183
+ functionality: `file part media type ${fullMediaType}`,
184
+ });
185
+ }
186
+
187
+ return {
188
+ type: 'input_file',
189
+ ...(typeof part.data.data === 'string' &&
190
+ isFileId(part.data.data, fileIdPrefixes)
191
+ ? { file_id: part.data.data }
192
+ : {
193
+ filename: part.filename ?? `part-${index}.pdf`,
194
+ file_data: `data:application/pdf;base64,${convertToBase64(part.data.data)}`,
195
+ }),
196
+ };
197
+ }
198
+ }
150
199
  }
151
200
  }
152
201
  }
@@ -206,6 +255,41 @@ export async function convertToOpenAIResponsesInput({
206
255
  break;
207
256
  }
208
257
 
258
+ const resolvedToolName = toolNameMapping.toProviderToolName(
259
+ part.toolName,
260
+ );
261
+
262
+ if (resolvedToolName === 'tool_search') {
263
+ if (store && id != null) {
264
+ input.push({ type: 'item_reference', id });
265
+ break;
266
+ }
267
+
268
+ const parsedInput =
269
+ typeof part.input === 'string'
270
+ ? await parseJSON({
271
+ text: part.input,
272
+ schema: toolSearchInputSchema,
273
+ })
274
+ : await validateTypes({
275
+ value: part.input,
276
+ schema: toolSearchInputSchema,
277
+ });
278
+
279
+ const execution =
280
+ parsedInput.call_id != null ? 'client' : 'server';
281
+
282
+ input.push({
283
+ type: 'tool_search_call',
284
+ id: id ?? part.toolCallId,
285
+ execution,
286
+ call_id: parsedInput.call_id ?? null,
287
+ status: 'completed',
288
+ arguments: parsedInput.arguments,
289
+ });
290
+ break;
291
+ }
292
+
209
293
  if (part.providerExecuted) {
210
294
  if (store && id != null) {
211
295
  input.push({ type: 'item_reference', id });
@@ -218,10 +302,6 @@ export async function convertToOpenAIResponsesInput({
218
302
  break;
219
303
  }
220
304
 
221
- const resolvedToolName = toolNameMapping.toProviderToolName(
222
- part.toolName,
223
- );
224
-
225
305
  if (hasLocalShellTool && resolvedToolName === 'local_shell') {
226
306
  const parsedInput = await validateTypes({
227
307
  value: part.input,
@@ -298,7 +378,7 @@ export async function convertToOpenAIResponsesInput({
298
378
  type: 'function_call',
299
379
  call_id: part.toolCallId,
300
380
  name: resolvedToolName,
301
- arguments: JSON.stringify(part.input),
381
+ arguments: serializeToolCallArguments(part.input),
302
382
  id,
303
383
  });
304
384
  break;
@@ -328,6 +408,35 @@ export async function convertToOpenAIResponsesInput({
328
408
  part.toolName,
329
409
  );
330
410
 
411
+ if (resolvedResultToolName === 'tool_search') {
412
+ const itemId =
413
+ (
414
+ part.providerOptions?.[providerOptionsName] as
415
+ | { itemId?: string }
416
+ | undefined
417
+ )?.itemId ?? part.toolCallId;
418
+
419
+ if (store) {
420
+ input.push({ type: 'item_reference', id: itemId });
421
+ } else if (part.output.type === 'json') {
422
+ const parsedOutput = await validateTypes({
423
+ value: part.output.value,
424
+ schema: toolSearchOutputSchema,
425
+ });
426
+
427
+ input.push({
428
+ type: 'tool_search_output',
429
+ id: itemId,
430
+ execution: 'server',
431
+ call_id: null,
432
+ status: 'completed',
433
+ tools: parsedOutput.tools,
434
+ });
435
+ }
436
+
437
+ break;
438
+ }
439
+
331
440
  /*
332
441
  * Shell tool results are separate output items (shell_call_output)
333
442
  * with their own item IDs distinct from the shell_call's item ID.
@@ -478,6 +587,36 @@ export async function convertToOpenAIResponsesInput({
478
587
  }
479
588
  break;
480
589
  }
590
+
591
+ case 'custom': {
592
+ if (part.kind === 'openai.compaction') {
593
+ const providerOpts =
594
+ part.providerOptions?.[providerOptionsName];
595
+ const id = providerOpts?.itemId as string | undefined;
596
+
597
+ if (hasConversation && id != null) {
598
+ break;
599
+ }
600
+
601
+ if (store && id != null) {
602
+ input.push({ type: 'item_reference', id });
603
+ break;
604
+ }
605
+
606
+ const encryptedContent = providerOpts?.encryptedContent as
607
+ | string
608
+ | undefined;
609
+
610
+ if (id != null) {
611
+ input.push({
612
+ type: 'compaction',
613
+ id,
614
+ encrypted_content: encryptedContent!,
615
+ } satisfies OpenAIResponsesCompactionItem);
616
+ }
617
+ }
618
+ break;
619
+ }
481
620
  }
482
621
  }
483
622
 
@@ -488,7 +627,7 @@ export async function convertToOpenAIResponsesInput({
488
627
  for (const part of content) {
489
628
  if (part.type === 'tool-approval-response') {
490
629
  const approvalResponse =
491
- part as LanguageModelV3ToolApprovalResponsePart;
630
+ part as LanguageModelV4ToolApprovalResponsePart;
492
631
 
493
632
  if (processedApprovalIds.has(approvalResponse.approvalId)) {
494
633
  continue;
@@ -527,6 +666,22 @@ export async function convertToOpenAIResponsesInput({
527
666
  part.toolName,
528
667
  );
529
668
 
669
+ if (resolvedToolName === 'tool_search' && output.type === 'json') {
670
+ const parsedOutput = await validateTypes({
671
+ value: output.value,
672
+ schema: toolSearchOutputSchema,
673
+ });
674
+
675
+ input.push({
676
+ type: 'tool_search_output',
677
+ execution: 'client',
678
+ call_id: part.toolCallId,
679
+ status: 'completed',
680
+ tools: parsedOutput.tools,
681
+ });
682
+ continue;
683
+ }
684
+
530
685
  if (
531
686
  hasLocalShellTool &&
532
687
  resolvedToolName === 'local_shell' &&
@@ -600,7 +755,7 @@ export async function convertToOpenAIResponsesInput({
600
755
  outputValue = output.value;
601
756
  break;
602
757
  case 'execution-denied':
603
- outputValue = output.reason ?? 'Tool execution denied.';
758
+ outputValue = output.reason ?? 'Tool call execution denied.';
604
759
  break;
605
760
  case 'json':
606
761
  case 'error-json':
@@ -612,22 +767,29 @@ export async function convertToOpenAIResponsesInput({
612
767
  switch (item.type) {
613
768
  case 'text':
614
769
  return { type: 'input_text' as const, text: item.text };
615
- case 'image-data':
616
- return {
617
- type: 'input_image' as const,
618
- image_url: `data:${item.mediaType};base64,${item.data}`,
619
- };
620
- case 'image-url':
621
- return {
622
- type: 'input_image' as const,
623
- image_url: item.url,
624
- };
625
770
  case 'file-data':
771
+ if (item.mediaType.startsWith('image/')) {
772
+ return {
773
+ type: 'input_image' as const,
774
+ image_url: `data:${item.mediaType};base64,${item.data}`,
775
+ };
776
+ }
626
777
  return {
627
778
  type: 'input_file' as const,
628
779
  filename: item.filename ?? 'data',
629
780
  file_data: `data:${item.mediaType};base64,${item.data}`,
630
781
  };
782
+ case 'file-url':
783
+ if (item.mediaType.startsWith('image/')) {
784
+ return {
785
+ type: 'input_image' as const,
786
+ image_url: item.url,
787
+ };
788
+ }
789
+ return {
790
+ type: 'input_file' as const,
791
+ file_url: item.url,
792
+ };
631
793
  default:
632
794
  warnings.push({
633
795
  type: 'other',
@@ -656,7 +818,7 @@ export async function convertToOpenAIResponsesInput({
656
818
  contentValue = output.value;
657
819
  break;
658
820
  case 'execution-denied':
659
- contentValue = output.reason ?? 'Tool execution denied.';
821
+ contentValue = output.reason ?? 'Tool call execution denied.';
660
822
  break;
661
823
  case 'json':
662
824
  case 'error-json':
@@ -670,25 +832,30 @@ export async function convertToOpenAIResponsesInput({
670
832
  return { type: 'input_text' as const, text: item.text };
671
833
  }
672
834
 
673
- case 'image-data': {
674
- return {
675
- type: 'input_image' as const,
676
- image_url: `data:${item.mediaType};base64,${item.data}`,
677
- };
678
- }
679
-
680
- case 'image-url': {
835
+ case 'file-data': {
836
+ if (item.mediaType.startsWith('image/')) {
837
+ return {
838
+ type: 'input_image' as const,
839
+ image_url: `data:${item.mediaType};base64,${item.data}`,
840
+ };
841
+ }
681
842
  return {
682
- type: 'input_image' as const,
683
- image_url: item.url,
843
+ type: 'input_file' as const,
844
+ filename: item.filename ?? 'data',
845
+ file_data: `data:${item.mediaType};base64,${item.data}`,
684
846
  };
685
847
  }
686
848
 
687
- case 'file-data': {
849
+ case 'file-url': {
850
+ if (item.mediaType.startsWith('image/')) {
851
+ return {
852
+ type: 'input_image' as const,
853
+ image_url: item.url,
854
+ };
855
+ }
688
856
  return {
689
857
  type: 'input_file' as const,
690
- filename: item.filename ?? 'data',
691
- file_data: `data:${item.mediaType};base64,${item.data}`,
858
+ file_url: item.url,
692
859
  };
693
860
  }
694
861
 
@@ -1,4 +1,4 @@
1
- import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
1
+ import type { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
3
  export function mapOpenAIResponseFinishReason({
4
4
  finishReason,
@@ -7,7 +7,7 @@ export function mapOpenAIResponseFinishReason({
7
7
  finishReason: string | null | undefined;
8
8
  // flag that checks if there have been client-side tool calls (not executed by openai)
9
9
  hasFunctionCall: boolean;
10
- }): LanguageModelV3FinishReason['unified'] {
10
+ }): LanguageModelV4FinishReason['unified'] {
11
11
  switch (finishReason) {
12
12
  case undefined:
13
13
  case null: