@ai-sdk/openai 4.0.0-beta.2 → 4.0.0-beta.21

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 (48) hide show
  1. package/CHANGELOG.md +234 -22
  2. package/README.md +2 -0
  3. package/dist/index.d.mts +134 -35
  4. package/dist/index.d.ts +134 -35
  5. package/dist/index.js +1700 -1139
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +1697 -1117
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/internal/index.d.mts +107 -41
  10. package/dist/internal/index.d.ts +107 -41
  11. package/dist/internal/index.js +1380 -939
  12. package/dist/internal/index.js.map +1 -1
  13. package/dist/internal/index.mjs +1371 -917
  14. package/dist/internal/index.mjs.map +1 -1
  15. package/docs/03-openai.mdx +274 -9
  16. package/package.json +3 -5
  17. package/src/chat/convert-openai-chat-usage.ts +2 -2
  18. package/src/chat/convert-to-openai-chat-messages.ts +26 -15
  19. package/src/chat/map-openai-finish-reason.ts +2 -2
  20. package/src/chat/openai-chat-language-model.ts +32 -24
  21. package/src/chat/openai-chat-options.ts +5 -0
  22. package/src/chat/openai-chat-prepare-tools.ts +6 -6
  23. package/src/completion/convert-openai-completion-usage.ts +2 -2
  24. package/src/completion/convert-to-openai-completion-prompt.ts +2 -2
  25. package/src/completion/map-openai-finish-reason.ts +2 -2
  26. package/src/completion/openai-completion-language-model.ts +20 -20
  27. package/src/embedding/openai-embedding-model.ts +5 -5
  28. package/src/files/openai-files-api.ts +17 -0
  29. package/src/files/openai-files-options.ts +18 -0
  30. package/src/files/openai-files.ts +102 -0
  31. package/src/image/openai-image-model.ts +9 -9
  32. package/src/index.ts +2 -0
  33. package/src/openai-config.ts +5 -5
  34. package/src/openai-language-model-capabilities.ts +3 -2
  35. package/src/openai-provider.ts +39 -21
  36. package/src/openai-tools.ts +12 -1
  37. package/src/responses/convert-openai-responses-usage.ts +2 -2
  38. package/src/responses/convert-to-openai-responses-input.ts +188 -14
  39. package/src/responses/map-openai-responses-finish-reason.ts +2 -2
  40. package/src/responses/openai-responses-api.ts +136 -2
  41. package/src/responses/openai-responses-language-model.ts +233 -37
  42. package/src/responses/openai-responses-options.ts +24 -2
  43. package/src/responses/openai-responses-prepare-tools.ts +34 -9
  44. package/src/responses/openai-responses-provider-metadata.ts +10 -0
  45. package/src/speech/openai-speech-model.ts +7 -7
  46. package/src/tool/custom.ts +0 -6
  47. package/src/tool/tool-search.ts +98 -0
  48. package/src/transcription/openai-transcription-model.ts +8 -8
@@ -1,13 +1,16 @@
1
1
  import {
2
- LanguageModelV3Prompt,
3
- LanguageModelV3ToolApprovalResponsePart,
4
- SharedV3Warning,
2
+ LanguageModelV4Prompt,
3
+ LanguageModelV4ToolApprovalResponsePart,
4
+ SharedV4Warning,
5
5
  UnsupportedFunctionalityError,
6
6
  } from '@ai-sdk/provider';
7
7
  import {
8
8
  convertToBase64,
9
9
  isNonNullable,
10
+ isProviderReference,
11
+ parseJSON,
10
12
  parseProviderOptions,
13
+ resolveProviderReference,
11
14
  ToolNameMapping,
12
15
  validateTypes,
13
16
  } from '@ai-sdk/provider-utils';
@@ -22,15 +25,22 @@ import {
22
25
  } from '../tool/local-shell';
23
26
  import { shellInputSchema, shellOutputSchema } from '../tool/shell';
24
27
  import {
28
+ OpenAIResponsesCompactionItem,
25
29
  OpenAIResponsesCustomToolCallOutput,
26
30
  OpenAIResponsesFunctionCallOutput,
27
31
  OpenAIResponsesInput,
28
32
  OpenAIResponsesReasoning,
29
33
  } from './openai-responses-api';
34
+ import {
35
+ toolSearchInputSchema,
36
+ toolSearchOutputSchema,
37
+ } from '../tool/tool-search';
30
38
 
31
39
  /**
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)
40
+ * This is soft-deprecated. Use provider references instead. Kept for backward compatibility
41
+ * with the `fileIdPrefixes` option.
42
+ *
43
+ * TODO: remove in v8
34
44
  */
35
45
  function isFileId(data: string, prefixes?: readonly string[]): boolean {
36
46
  if (!prefixes) return false;
@@ -50,10 +60,11 @@ export async function convertToOpenAIResponsesInput({
50
60
  hasApplyPatchTool = false,
51
61
  customProviderToolNames,
52
62
  }: {
53
- prompt: LanguageModelV3Prompt;
63
+ prompt: LanguageModelV4Prompt;
54
64
  toolNameMapping: ToolNameMapping;
55
65
  systemMessageMode: 'system' | 'developer' | 'remove';
56
66
  providerOptionsName: string;
67
+ /** @deprecated Use provider references instead. */
57
68
  fileIdPrefixes?: readonly string[];
58
69
  store: boolean;
59
70
  hasConversation?: boolean; // when true, skip assistant messages that already have item IDs
@@ -63,10 +74,10 @@ export async function convertToOpenAIResponsesInput({
63
74
  customProviderToolNames?: Set<string>;
64
75
  }): Promise<{
65
76
  input: OpenAIResponsesInput;
66
- warnings: Array<SharedV3Warning>;
77
+ warnings: Array<SharedV4Warning>;
67
78
  }> {
68
- const input: OpenAIResponsesInput = [];
69
- const warnings: Array<SharedV3Warning> = [];
79
+ let input: OpenAIResponsesInput = [];
80
+ const warnings: Array<SharedV4Warning> = [];
70
81
  const processedApprovalIds = new Set<string>();
71
82
 
72
83
  for (const { role, content } of prompt) {
@@ -107,6 +118,28 @@ export async function convertToOpenAIResponsesInput({
107
118
  return { type: 'input_text', text: part.text };
108
119
  }
109
120
  case 'file': {
121
+ if (isProviderReference(part.data)) {
122
+ const fileId = resolveProviderReference({
123
+ reference: part.data,
124
+ provider: providerOptionsName,
125
+ });
126
+
127
+ if (part.mediaType.startsWith('image/')) {
128
+ return {
129
+ type: 'input_image',
130
+ file_id: fileId,
131
+ detail:
132
+ part.providerOptions?.[providerOptionsName]
133
+ ?.imageDetail,
134
+ };
135
+ }
136
+
137
+ return {
138
+ type: 'input_file',
139
+ file_id: fileId,
140
+ };
141
+ }
142
+
110
143
  if (part.mediaType.startsWith('image/')) {
111
144
  const mediaType =
112
145
  part.mediaType === 'image/*'
@@ -206,6 +239,41 @@ export async function convertToOpenAIResponsesInput({
206
239
  break;
207
240
  }
208
241
 
242
+ const resolvedToolName = toolNameMapping.toProviderToolName(
243
+ part.toolName,
244
+ );
245
+
246
+ if (resolvedToolName === 'tool_search') {
247
+ if (store && id != null) {
248
+ input.push({ type: 'item_reference', id });
249
+ break;
250
+ }
251
+
252
+ const parsedInput =
253
+ typeof part.input === 'string'
254
+ ? await parseJSON({
255
+ text: part.input,
256
+ schema: toolSearchInputSchema,
257
+ })
258
+ : await validateTypes({
259
+ value: part.input,
260
+ schema: toolSearchInputSchema,
261
+ });
262
+
263
+ const execution =
264
+ parsedInput.call_id != null ? 'client' : 'server';
265
+
266
+ input.push({
267
+ type: 'tool_search_call',
268
+ id: id ?? part.toolCallId,
269
+ execution,
270
+ call_id: parsedInput.call_id ?? null,
271
+ status: 'completed',
272
+ arguments: parsedInput.arguments,
273
+ });
274
+ break;
275
+ }
276
+
209
277
  if (part.providerExecuted) {
210
278
  if (store && id != null) {
211
279
  input.push({ type: 'item_reference', id });
@@ -218,10 +286,6 @@ export async function convertToOpenAIResponsesInput({
218
286
  break;
219
287
  }
220
288
 
221
- const resolvedToolName = toolNameMapping.toProviderToolName(
222
- part.toolName,
223
- );
224
-
225
289
  if (hasLocalShellTool && resolvedToolName === 'local_shell') {
226
290
  const parsedInput = await validateTypes({
227
291
  value: part.input,
@@ -328,6 +392,35 @@ export async function convertToOpenAIResponsesInput({
328
392
  part.toolName,
329
393
  );
330
394
 
395
+ if (resolvedResultToolName === 'tool_search') {
396
+ const itemId =
397
+ (
398
+ part.providerOptions?.[providerOptionsName] as
399
+ | { itemId?: string }
400
+ | undefined
401
+ )?.itemId ?? part.toolCallId;
402
+
403
+ if (store) {
404
+ input.push({ type: 'item_reference', id: itemId });
405
+ } else if (part.output.type === 'json') {
406
+ const parsedOutput = await validateTypes({
407
+ value: part.output.value,
408
+ schema: toolSearchOutputSchema,
409
+ });
410
+
411
+ input.push({
412
+ type: 'tool_search_output',
413
+ id: itemId,
414
+ execution: 'server',
415
+ call_id: null,
416
+ status: 'completed',
417
+ tools: parsedOutput.tools,
418
+ });
419
+ }
420
+
421
+ break;
422
+ }
423
+
331
424
  /*
332
425
  * Shell tool results are separate output items (shell_call_output)
333
426
  * with their own item IDs distinct from the shell_call's item ID.
@@ -478,6 +571,36 @@ export async function convertToOpenAIResponsesInput({
478
571
  }
479
572
  break;
480
573
  }
574
+
575
+ case 'custom': {
576
+ if (part.kind === 'openai.compaction') {
577
+ const providerOpts =
578
+ part.providerOptions?.[providerOptionsName];
579
+ const id = providerOpts?.itemId as string | undefined;
580
+
581
+ if (hasConversation && id != null) {
582
+ break;
583
+ }
584
+
585
+ if (store && id != null) {
586
+ input.push({ type: 'item_reference', id });
587
+ break;
588
+ }
589
+
590
+ const encryptedContent = providerOpts?.encryptedContent as
591
+ | string
592
+ | undefined;
593
+
594
+ if (id != null) {
595
+ input.push({
596
+ type: 'compaction',
597
+ id,
598
+ encrypted_content: encryptedContent!,
599
+ } satisfies OpenAIResponsesCompactionItem);
600
+ }
601
+ }
602
+ break;
603
+ }
481
604
  }
482
605
  }
483
606
 
@@ -488,7 +611,7 @@ export async function convertToOpenAIResponsesInput({
488
611
  for (const part of content) {
489
612
  if (part.type === 'tool-approval-response') {
490
613
  const approvalResponse =
491
- part as LanguageModelV3ToolApprovalResponsePart;
614
+ part as LanguageModelV4ToolApprovalResponsePart;
492
615
 
493
616
  if (processedApprovalIds.has(approvalResponse.approvalId)) {
494
617
  continue;
@@ -527,6 +650,22 @@ export async function convertToOpenAIResponsesInput({
527
650
  part.toolName,
528
651
  );
529
652
 
653
+ if (resolvedToolName === 'tool_search' && output.type === 'json') {
654
+ const parsedOutput = await validateTypes({
655
+ value: output.value,
656
+ schema: toolSearchOutputSchema,
657
+ });
658
+
659
+ input.push({
660
+ type: 'tool_search_output',
661
+ execution: 'client',
662
+ call_id: part.toolCallId,
663
+ status: 'completed',
664
+ tools: parsedOutput.tools,
665
+ });
666
+ continue;
667
+ }
668
+
530
669
  if (
531
670
  hasLocalShellTool &&
532
671
  resolvedToolName === 'local_shell' &&
@@ -628,6 +767,11 @@ export async function convertToOpenAIResponsesInput({
628
767
  filename: item.filename ?? 'data',
629
768
  file_data: `data:${item.mediaType};base64,${item.data}`,
630
769
  };
770
+ case 'file-url':
771
+ return {
772
+ type: 'input_file' as const,
773
+ file_url: item.url,
774
+ };
631
775
  default:
632
776
  warnings.push({
633
777
  type: 'other',
@@ -692,6 +836,13 @@ export async function convertToOpenAIResponsesInput({
692
836
  };
693
837
  }
694
838
 
839
+ case 'file-url': {
840
+ return {
841
+ type: 'input_file' as const,
842
+ file_url: item.url,
843
+ };
844
+ }
845
+
695
846
  default: {
696
847
  warnings.push({
697
848
  type: 'other',
@@ -722,6 +873,29 @@ export async function convertToOpenAIResponsesInput({
722
873
  }
723
874
  }
724
875
 
876
+ // when store is false, remove reasoning parts without encrypted content
877
+ if (
878
+ !store &&
879
+ input.some(
880
+ item =>
881
+ 'type' in item &&
882
+ item.type === 'reasoning' &&
883
+ item.encrypted_content == null,
884
+ )
885
+ ) {
886
+ warnings.push({
887
+ type: 'other',
888
+ message:
889
+ 'Reasoning parts without encrypted content are not supported when store is false. Skipping reasoning parts.',
890
+ });
891
+ input = input.filter(
892
+ item =>
893
+ !('type' in item) ||
894
+ item.type !== 'reasoning' ||
895
+ item.encrypted_content != null,
896
+ );
897
+ }
898
+
725
899
  return { input, warnings };
726
900
  }
727
901
 
@@ -1,4 +1,4 @@
1
- import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
1
+ import { 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:
@@ -1,7 +1,18 @@
1
- import { JSONSchema7 } from '@ai-sdk/provider';
1
+ import { JSONObject, JSONSchema7, JSONValue } from '@ai-sdk/provider';
2
2
  import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
3
3
  import { z } from 'zod/v4';
4
4
 
5
+ const jsonValueSchema: z.ZodType<JSONValue> = z.lazy(() =>
6
+ z.union([
7
+ z.string(),
8
+ z.number(),
9
+ z.boolean(),
10
+ z.null(),
11
+ z.array(jsonValueSchema),
12
+ z.record(z.string(), jsonValueSchema.optional()),
13
+ ]),
14
+ );
15
+
5
16
  export type OpenAIResponsesInput = Array<OpenAIResponsesInputItem>;
6
17
 
7
18
  export type OpenAIResponsesInputItem =
@@ -20,8 +31,11 @@ export type OpenAIResponsesInputItem =
20
31
  | OpenAIResponsesShellCallOutput
21
32
  | OpenAIResponsesApplyPatchCall
22
33
  | OpenAIResponsesApplyPatchCallOutput
34
+ | OpenAIResponsesToolSearchCall
35
+ | OpenAIResponsesToolSearchOutput
23
36
  | OpenAIResponsesReasoning
24
- | OpenAIResponsesItemReference;
37
+ | OpenAIResponsesItemReference
38
+ | OpenAIResponsesCompactionItem;
25
39
 
26
40
  export type OpenAIResponsesIncludeValue =
27
41
  | 'web_search_call.action.sources'
@@ -93,6 +107,7 @@ export type OpenAIResponsesFunctionCallOutput = {
93
107
  | { type: 'input_text'; text: string }
94
108
  | { type: 'input_image'; image_url: string }
95
109
  | { type: 'input_file'; filename: string; file_data: string }
110
+ | { type: 'input_file'; file_url: string }
96
111
  >;
97
112
  };
98
113
 
@@ -199,11 +214,35 @@ export type OpenAIResponsesApplyPatchCallOutput = {
199
214
  output?: string;
200
215
  };
201
216
 
217
+ export type OpenAIResponsesToolSearchCall = {
218
+ type: 'tool_search_call';
219
+ id: string;
220
+ execution: 'server' | 'client';
221
+ call_id: string | null;
222
+ status: 'in_progress' | 'completed' | 'incomplete';
223
+ arguments: unknown;
224
+ };
225
+
226
+ export type OpenAIResponsesToolSearchOutput = {
227
+ type: 'tool_search_output';
228
+ id?: string;
229
+ execution: 'server' | 'client';
230
+ call_id: string | null;
231
+ status: 'in_progress' | 'completed' | 'incomplete';
232
+ tools: Array<JSONObject>;
233
+ };
234
+
202
235
  export type OpenAIResponsesItemReference = {
203
236
  type: 'item_reference';
204
237
  id: string;
205
238
  };
206
239
 
240
+ export type OpenAIResponsesCompactionItem = {
241
+ type: 'compaction';
242
+ id: string;
243
+ encrypted_content: string;
244
+ };
245
+
207
246
  /**
208
247
  * A filter used to compare a specified attribute key to a given value using a defined comparison operation.
209
248
  */
@@ -249,6 +288,7 @@ export type OpenAIResponsesTool =
249
288
  description: string | undefined;
250
289
  parameters: JSONSchema7;
251
290
  strict?: boolean;
291
+ defer_loading?: boolean;
252
292
  }
253
293
  | {
254
294
  type: 'apply_patch';
@@ -407,6 +447,12 @@ export type OpenAIResponsesTool =
407
447
  path: string;
408
448
  }>;
409
449
  };
450
+ }
451
+ | {
452
+ type: 'tool_search';
453
+ execution?: 'server' | 'client';
454
+ description?: string;
455
+ parameters?: Record<string, unknown>;
410
456
  };
411
457
 
412
458
  export type OpenAIResponsesReasoning = {
@@ -458,6 +504,31 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
458
504
  service_tier: z.string().nullish(),
459
505
  }),
460
506
  }),
507
+ z.object({
508
+ type: z.literal('response.failed'),
509
+ response: z.object({
510
+ error: z
511
+ .object({
512
+ code: z.string().nullish(),
513
+ message: z.string(),
514
+ })
515
+ .nullish(),
516
+ incomplete_details: z.object({ reason: z.string() }).nullish(),
517
+ usage: z
518
+ .object({
519
+ input_tokens: z.number(),
520
+ input_tokens_details: z
521
+ .object({ cached_tokens: z.number().nullish() })
522
+ .nullish(),
523
+ output_tokens: z.number(),
524
+ output_tokens_details: z
525
+ .object({ reasoning_tokens: z.number().nullish() })
526
+ .nullish(),
527
+ })
528
+ .nullish(),
529
+ service_tier: z.string().nullish(),
530
+ }),
531
+ }),
461
532
  z.object({
462
533
  type: z.literal('response.created'),
463
534
  response: z.object({
@@ -573,6 +644,11 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
573
644
  commands: z.array(z.string()),
574
645
  }),
575
646
  }),
647
+ z.object({
648
+ type: z.literal('compaction'),
649
+ id: z.string(),
650
+ encrypted_content: z.string().nullish(),
651
+ }),
576
652
  z.object({
577
653
  type: z.literal('shell_call_output'),
578
654
  id: z.string(),
@@ -592,6 +668,22 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
592
668
  }),
593
669
  ),
594
670
  }),
671
+ z.object({
672
+ type: z.literal('tool_search_call'),
673
+ id: z.string(),
674
+ execution: z.enum(['server', 'client']),
675
+ call_id: z.string().nullable(),
676
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
677
+ arguments: z.unknown(),
678
+ }),
679
+ z.object({
680
+ type: z.literal('tool_search_output'),
681
+ id: z.string(),
682
+ execution: z.enum(['server', 'client']),
683
+ call_id: z.string().nullable(),
684
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
685
+ tools: z.array(z.record(z.string(), jsonValueSchema.optional())),
686
+ }),
595
687
  ]),
596
688
  }),
597
689
  z.object({
@@ -796,6 +888,11 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
796
888
  commands: z.array(z.string()),
797
889
  }),
798
890
  }),
891
+ z.object({
892
+ type: z.literal('compaction'),
893
+ id: z.string(),
894
+ encrypted_content: z.string(),
895
+ }),
799
896
  z.object({
800
897
  type: z.literal('shell_call_output'),
801
898
  id: z.string(),
@@ -815,6 +912,22 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
815
912
  }),
816
913
  ),
817
914
  }),
915
+ z.object({
916
+ type: z.literal('tool_search_call'),
917
+ id: z.string(),
918
+ execution: z.enum(['server', 'client']),
919
+ call_id: z.string().nullable(),
920
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
921
+ arguments: z.unknown(),
922
+ }),
923
+ z.object({
924
+ type: z.literal('tool_search_output'),
925
+ id: z.string(),
926
+ execution: z.enum(['server', 'client']),
927
+ call_id: z.string().nullable(),
928
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
929
+ tools: z.array(z.record(z.string(), jsonValueSchema.optional())),
930
+ }),
818
931
  ]),
819
932
  }),
820
933
  z.object({
@@ -1219,6 +1332,11 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1219
1332
  commands: z.array(z.string()),
1220
1333
  }),
1221
1334
  }),
1335
+ z.object({
1336
+ type: z.literal('compaction'),
1337
+ id: z.string(),
1338
+ encrypted_content: z.string(),
1339
+ }),
1222
1340
  z.object({
1223
1341
  type: z.literal('shell_call_output'),
1224
1342
  id: z.string(),
@@ -1238,6 +1356,22 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1238
1356
  }),
1239
1357
  ),
1240
1358
  }),
1359
+ z.object({
1360
+ type: z.literal('tool_search_call'),
1361
+ id: z.string(),
1362
+ execution: z.enum(['server', 'client']),
1363
+ call_id: z.string().nullable(),
1364
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
1365
+ arguments: z.unknown(),
1366
+ }),
1367
+ z.object({
1368
+ type: z.literal('tool_search_output'),
1369
+ id: z.string(),
1370
+ execution: z.enum(['server', 'client']),
1371
+ call_id: z.string().nullable(),
1372
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
1373
+ tools: z.array(z.record(z.string(), jsonValueSchema.optional())),
1374
+ }),
1241
1375
  ]),
1242
1376
  )
1243
1377
  .optional(),