@ai-sdk/openai 4.0.59 → 4.0.61

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.
@@ -1,6 +1,6 @@
1
1
  import type {
2
+ Experimental_BatchV4 as BatchV4,
2
3
  EmbeddingModelV4,
3
- Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
4
4
  FilesV4,
5
5
  ImageModelV4,
6
6
  LanguageModelV4,
@@ -31,7 +31,8 @@ import type { OpenAIEmbeddingModelId } from './embedding/openai-embedding-model-
31
31
  import { OpenAIImageModel } from './image/openai-image-model';
32
32
  import type { OpenAIImageModelId } from './image/openai-image-model-options';
33
33
  import { openaiTools } from './openai-tools';
34
- import { OpenAIResponsesBatchLanguageModel } from './openai-responses-batch';
34
+ import { OpenAIBatch } from './openai-batch';
35
+ import { OpenAIResponsesLanguageModel } from './responses/openai-responses-language-model';
35
36
  import { OpenAIRealtimeModel } from './realtime/openai-realtime-model';
36
37
  import type { OpenAIResponsesModelId } from './responses/openai-responses-language-model-options';
37
38
  import { OpenAISpeechModel } from './speech/openai-speech-model';
@@ -44,12 +45,12 @@ import { OpenAISkills } from './skills/openai-skills';
44
45
  import { VERSION } from './version';
45
46
 
46
47
  export interface OpenAIProvider extends ProviderV4 {
47
- (modelId: OpenAIResponsesModelId): BatchLanguageModelV4;
48
+ (modelId: OpenAIResponsesModelId): LanguageModelV4;
48
49
 
49
50
  /**
50
51
  * Creates an OpenAI model for text generation.
51
52
  */
52
- languageModel(modelId: OpenAIResponsesModelId): BatchLanguageModelV4;
53
+ languageModel(modelId: OpenAIResponsesModelId): LanguageModelV4;
53
54
 
54
55
  /**
55
56
  * Creates an OpenAI chat model for text generation.
@@ -59,7 +60,7 @@ export interface OpenAIProvider extends ProviderV4 {
59
60
  /**
60
61
  * Creates an OpenAI responses API model for text generation.
61
62
  */
62
- responses(modelId: OpenAIResponsesModelId): BatchLanguageModelV4;
63
+ responses(modelId: OpenAIResponsesModelId): LanguageModelV4;
63
64
 
64
65
  /**
65
66
  * Creates an OpenAI completion model for text generation.
@@ -136,6 +137,11 @@ export interface OpenAIProvider extends ProviderV4 {
136
137
  */
137
138
  skills(): SkillsV4;
138
139
 
140
+ /**
141
+ * Returns a BatchV4 interface for processing batches with OpenAI.
142
+ */
143
+ experimental_batch(): BatchV4<{ text: OpenAIResponsesModelId }>;
144
+
139
145
  /**
140
146
  * OpenAI-specific tools.
141
147
  */
@@ -306,7 +312,7 @@ export function createOpenAI(
306
312
  };
307
313
 
308
314
  const createResponsesModel = (modelId: OpenAIResponsesModelId) => {
309
- return new OpenAIResponsesBatchLanguageModel(modelId, {
315
+ return new OpenAIResponsesLanguageModel(modelId, {
310
316
  provider: `${providerName}.responses`,
311
317
  baseURL,
312
318
  url: ({ path }) => `${baseURL}${path}`,
@@ -317,6 +323,20 @@ export function createOpenAI(
317
323
  });
318
324
  };
319
325
 
326
+ const createBatch = () =>
327
+ new OpenAIBatch({
328
+ provider: `${providerName}.batch`,
329
+ config: {
330
+ provider: `${providerName}.responses`,
331
+ baseURL,
332
+ url: ({ path }) => `${baseURL}${path}`,
333
+ headers: getHeaders,
334
+ fetch: options.fetch,
335
+ // Soft-deprecated. TODO: remove in v8
336
+ fileIdPrefixes: ['file-'],
337
+ },
338
+ });
339
+
320
340
  const createRealtimeModel = (modelId: string) =>
321
341
  new OpenAIRealtimeModel(modelId, {
322
342
  provider: `${providerName}.realtime`,
@@ -371,6 +391,7 @@ export function createOpenAI(
371
391
  provider.speechModel = createSpeechModel;
372
392
  provider.files = createFiles;
373
393
  provider.skills = createSkills;
394
+ provider.experimental_batch = createBatch;
374
395
 
375
396
  provider.experimental_realtime = experimentalRealtimeFactory;
376
397
 
@@ -28,6 +28,7 @@ export const openaiTools = {
28
28
  * `input` field is a string matching the specified grammar.
29
29
  *
30
30
  * @param description - An optional description of the tool.
31
+ * @param async - Whether the model can continue without waiting for the tool result.
31
32
  * @param format - The output format constraint (grammar type, syntax, and definition).
32
33
  */
33
34
  customTool,
@@ -677,6 +677,17 @@ export async function convertToOpenAIResponsesInput({
677
677
  ).providerMetadata?.[providerOptionsName]?.namespace) as
678
678
  | string
679
679
  | undefined;
680
+ const isAsync = (part.providerOptions?.[providerOptionsName]
681
+ ?.async ??
682
+ (
683
+ part as {
684
+ providerMetadata?: {
685
+ [providerOptionsName]?: { async?: boolean };
686
+ };
687
+ }
688
+ ).providerMetadata?.[providerOptionsName]?.async) as
689
+ | boolean
690
+ | undefined;
680
691
  const caller = part.providerOptions?.[providerOptionsName]
681
692
  ?.caller as
682
693
  | { type: 'direct' }
@@ -904,6 +915,7 @@ export async function convertToOpenAIResponsesInput({
904
915
  typeof part.input === 'string'
905
916
  ? part.input
906
917
  : JSON.stringify(part.input),
918
+ ...(isAsync != null && { async: isAsync }),
907
919
  id,
908
920
  });
909
921
  break;
@@ -914,6 +926,7 @@ export async function convertToOpenAIResponsesInput({
914
926
  call_id: part.toolCallId,
915
927
  name: resolvedToolName,
916
928
  arguments: serializeToolCallArguments(part.input),
929
+ ...(isAsync != null && { async: isAsync }),
917
930
  ...(namespace != null && { namespace }),
918
931
  ...(caller != null && {
919
932
  caller: mapToolCaller(caller),
@@ -180,6 +180,7 @@ export type OpenAIResponsesInputItem =
180
180
  | OpenAIResponsesReasoning
181
181
  | OpenAIResponsesItemReference
182
182
  | OpenAIResponsesCompactionItem
183
+ | OpenAIResponsesConfigurationUpdate
183
184
  | OpenAIResponsesCompactionTrigger;
184
185
 
185
186
  export type OpenAIResponsesIncludeValue =
@@ -272,6 +273,7 @@ export type OpenAIResponsesFunctionCall = {
272
273
  call_id: string;
273
274
  name: string;
274
275
  arguments: string;
276
+ async?: boolean;
275
277
  id?: string;
276
278
  namespace?: string;
277
279
  caller?: OpenAIResponsesToolCaller;
@@ -334,6 +336,7 @@ export type OpenAIResponsesCustomToolCall = {
334
336
  call_id: string;
335
337
  name: string;
336
338
  input: string;
339
+ async?: boolean;
337
340
  };
338
341
 
339
342
  export type OpenAIResponsesCustomToolCallOutput = {
@@ -468,6 +471,13 @@ export type OpenAIResponsesCompactionItem = {
468
471
  encrypted_content: string;
469
472
  };
470
473
 
474
+ export type OpenAIResponsesConfigurationUpdate = {
475
+ type: 'configuration_update';
476
+ reasoning: {
477
+ effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
478
+ };
479
+ };
480
+
471
481
  export type OpenAIResponsesCompactionTrigger = {
472
482
  type: 'compaction_trigger';
473
483
  };
@@ -515,6 +525,7 @@ export type OpenAIResponsesFunctionTool = {
515
525
  name: string;
516
526
  description: string | undefined;
517
527
  parameters: JSONSchema7;
528
+ async?: boolean;
518
529
  strict?: boolean;
519
530
  defer_loading?: boolean;
520
531
  allowed_callers?: Array<'direct' | 'programmatic'>;
@@ -655,6 +666,7 @@ export type OpenAIResponsesTool =
655
666
  type: 'custom';
656
667
  name: string;
657
668
  description?: string;
669
+ async?: boolean;
658
670
  format?:
659
671
  | {
660
672
  type: 'grammar';
@@ -936,6 +948,7 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
936
948
  call_id: z.string(),
937
949
  name: z.string(),
938
950
  arguments: z.string(),
951
+ async: z.boolean().nullish(),
939
952
  namespace: z.string().nullish(),
940
953
  caller: openaiResponsesToolCallerSchema.nullish(),
941
954
  }),
@@ -1013,6 +1026,7 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
1013
1026
  call_id: z.string(),
1014
1027
  name: z.string(),
1015
1028
  input: z.string(),
1029
+ async: z.boolean().nullish(),
1016
1030
  }),
1017
1031
  z.object({
1018
1032
  type: z.literal('shell_call'),
@@ -1085,6 +1099,7 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
1085
1099
  call_id: z.string(),
1086
1100
  name: z.string(),
1087
1101
  arguments: z.string(),
1102
+ async: z.boolean().nullish(),
1088
1103
  status: z.enum(['in_progress', 'completed', 'incomplete']),
1089
1104
  namespace: z.string().nullish(),
1090
1105
  caller: openaiResponsesToolCallerSchema.nullish(),
@@ -1097,6 +1112,7 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
1097
1112
  call_id: z.string(),
1098
1113
  name: z.string(),
1099
1114
  input: z.string(),
1115
+ async: z.boolean().nullish(),
1100
1116
  status: z.literal('completed'),
1101
1117
  }),
1102
1118
  z.object({
@@ -1585,6 +1601,7 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1585
1601
  name: z.string(),
1586
1602
  arguments: z.string(),
1587
1603
  id: z.string(),
1604
+ async: z.boolean().nullish(),
1588
1605
  namespace: z.string().nullish(),
1589
1606
  caller: openaiResponsesToolCallerSchema.nullish(),
1590
1607
  }),
@@ -1596,6 +1613,7 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1596
1613
  name: z.string(),
1597
1614
  input: z.string(),
1598
1615
  id: z.string(),
1616
+ async: z.boolean().nullish(),
1599
1617
  }),
1600
1618
  openaiResponsesComputerCallSchema,
1601
1619
  z.object({
@@ -264,6 +264,18 @@ export const openaiLanguageModelResponsesOptionsSchema = lazySchema(() =>
264
264
  */
265
265
  reasoningEffort: z.string().nullish(),
266
266
 
267
+ /**
268
+ * Updates the reasoning effort for GPT-6 and later models starting with this response
269
+ * without changing the request-level reasoning effort. This preserves the
270
+ * request prefix for prompt caching.
271
+ *
272
+ * Only supported by GPT-6 and later models in standard, single-agent mode. Cannot be
273
+ * combined with automatic compaction or automatic truncation.
274
+ */
275
+ reasoningEffortUpdate: z
276
+ .enum(['low', 'medium', 'high', 'xhigh', 'max'])
277
+ .optional(),
278
+
267
279
  /**
268
280
  * Controls how much model work GPT-5.6 performs before returning a final answer.
269
281
  * `standard` is the default. `pro` increases quality, latency, and token usage.
@@ -94,6 +94,7 @@ import type {
94
94
  ResponsesReasoningProviderMetadata,
95
95
  ResponsesSourceDocumentProviderMetadata,
96
96
  ResponsesTextProviderMetadata,
97
+ ResponsesToolCallProviderMetadata,
97
98
  } from './openai-responses-provider-metadata';
98
99
 
99
100
  /**
@@ -202,6 +203,11 @@ function mapComputerCallInput({
202
203
  };
203
204
  }
204
205
 
206
+ export const openaiResponsesSupportedUrls: Record<string, RegExp[]> = {
207
+ 'image/*': [/^https?:\/\/.*$/],
208
+ 'application/pdf': [/^https?:\/\/.*$/],
209
+ };
210
+
205
211
  export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
206
212
  readonly specificationVersion = 'v4';
207
213
 
@@ -231,33 +237,38 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
231
237
  this.config = config;
232
238
  }
233
239
 
234
- readonly supportedUrls: Record<string, RegExp[]> = {
235
- 'image/*': [/^https?:\/\/.*$/],
236
- 'application/pdf': [/^https?:\/\/.*$/],
237
- };
240
+ readonly supportedUrls = openaiResponsesSupportedUrls;
238
241
 
239
242
  get provider(): string {
240
243
  return this.config.provider;
241
244
  }
242
245
 
243
- protected async getArgs({
244
- maxOutputTokens,
245
- temperature,
246
- stopSequences,
247
- topP,
248
- topK,
249
- presencePenalty,
250
- frequencyPenalty,
251
- seed,
252
- prompt,
253
- reasoning,
254
- providerOptions,
255
- tools,
256
- toolChoice,
257
- responseFormat,
258
- }: LanguageModelV4CallOptions) {
246
+ static async prepareRequest({
247
+ modelId,
248
+ config,
249
+ options: {
250
+ maxOutputTokens,
251
+ temperature,
252
+ stopSequences,
253
+ topP,
254
+ topK,
255
+ presencePenalty,
256
+ frequencyPenalty,
257
+ seed,
258
+ prompt,
259
+ reasoning,
260
+ providerOptions,
261
+ tools,
262
+ toolChoice,
263
+ responseFormat,
264
+ },
265
+ }: {
266
+ modelId: OpenAIResponsesModelId;
267
+ config: OpenAIConfig;
268
+ options: LanguageModelV4CallOptions;
269
+ }) {
259
270
  const warnings: SharedV4Warning[] = [];
260
- const modelCapabilities = getOpenAILanguageModelCapabilities(this.modelId);
271
+ const modelCapabilities = getOpenAILanguageModelCapabilities(modelId);
261
272
 
262
273
  if (topK != null) {
263
274
  warnings.push({ type: 'unsupported', feature: 'topK' });
@@ -279,7 +290,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
279
290
  warnings.push({ type: 'unsupported', feature: 'stopSequences' });
280
291
  }
281
292
 
282
- const providerOptionsName = this.config.provider.includes('azure')
293
+ const providerOptionsName = config.provider.includes('azure')
283
294
  ? 'azure'
284
295
  : 'openai';
285
296
  let openaiOptions = await parseProviderOptions({
@@ -296,9 +307,25 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
296
307
  });
297
308
  }
298
309
 
299
- const resolvedReasoningEffort =
310
+ let resolvedReasoningEffort =
300
311
  openaiOptions?.reasoningEffort ??
301
312
  (isCustomReasoning(reasoning) ? reasoning : undefined);
313
+
314
+ if (
315
+ resolvedReasoningEffort != null &&
316
+ modelCapabilities.supportedReasoningEfforts != null &&
317
+ !modelCapabilities.supportedReasoningEfforts.includes(
318
+ resolvedReasoningEffort,
319
+ )
320
+ ) {
321
+ warnings.push({
322
+ type: 'unsupported',
323
+ feature: 'reasoningEffort',
324
+ details: `${modelId} only supports the following reasoning efforts: ${modelCapabilities.supportedReasoningEfforts.join(', ')}`,
325
+ });
326
+ resolvedReasoningEffort = undefined;
327
+ }
328
+
302
329
  const resolvedReasoningSummary =
303
330
  openaiOptions?.reasoningSummary !== undefined
304
331
  ? openaiOptions.reasoningSummary
@@ -351,6 +378,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
351
378
  toolNameMapping,
352
379
  customProviderToolNames,
353
380
  outputSchemaToolNames,
381
+ supportsAsyncToolCalling: modelCapabilities.supportsAsyncToolCalling,
354
382
  });
355
383
 
356
384
  const { input, warnings: inputWarnings } =
@@ -363,7 +391,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
363
391
  ? 'developer'
364
392
  : modelCapabilities.systemMessageMode),
365
393
  providerOptionsName,
366
- fileIdPrefixes: this.config.fileIdPrefixes,
394
+ fileIdPrefixes: config.fileIdPrefixes,
367
395
  passThroughUnsupportedFiles:
368
396
  openaiOptions?.passThroughUnsupportedFiles ?? false,
369
397
  store: openaiOptions?.store ?? true,
@@ -384,6 +412,29 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
384
412
 
385
413
  warnings.push(...inputWarnings);
386
414
 
415
+ const reasoningEffortUpdate = openaiOptions?.reasoningEffortUpdate;
416
+ const configurationUpdateIsSupported =
417
+ reasoningEffortUpdate == null ||
418
+ (modelCapabilities.supportsConfigurationUpdate &&
419
+ openaiOptions?.reasoningMode !== 'pro' &&
420
+ openaiOptions?.contextManagement == null &&
421
+ openaiOptions?.truncation !== 'auto');
422
+
423
+ if (reasoningEffortUpdate != null && !configurationUpdateIsSupported) {
424
+ warnings.push({
425
+ type: 'unsupported',
426
+ feature: 'reasoningEffortUpdate',
427
+ details: !modelCapabilities.supportsConfigurationUpdate
428
+ ? 'reasoningEffortUpdate is only supported by GPT-6 and later models'
429
+ : 'reasoningEffortUpdate requires standard reasoning mode without automatic compaction or automatic truncation',
430
+ });
431
+ } else if (reasoningEffortUpdate != null) {
432
+ input.unshift({
433
+ type: 'configuration_update',
434
+ reasoning: { effort: reasoningEffortUpdate },
435
+ });
436
+ }
437
+
387
438
  // A compaction trigger is a request control, not conversation history.
388
439
  // OpenAI requires it to be the final input item, so append it only after
389
440
  // the complete prompt has been converted.
@@ -451,7 +502,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
451
502
  }
452
503
 
453
504
  const baseArgs = {
454
- model: this.modelId,
505
+ model: modelId,
455
506
  input,
456
507
  temperature,
457
508
  top_p: topP,
@@ -526,6 +577,19 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
526
577
  }),
527
578
  };
528
579
 
580
+ if (
581
+ modelCapabilities.supportsConfigurationUpdate &&
582
+ baseArgs.prompt_cache_retention != null
583
+ ) {
584
+ baseArgs.prompt_cache_retention = undefined;
585
+ warnings.push({
586
+ type: 'unsupported',
587
+ feature: 'promptCacheRetention',
588
+ details:
589
+ 'promptCacheRetention is not supported by GPT-6 and later models; use promptCacheOptions instead',
590
+ });
591
+ }
592
+
529
593
  // remove unsupported settings for reasoning models
530
594
  // see https://platform.openai.com/docs/guides/reasoning#limitations
531
595
  if (isReasoningModel) {
@@ -554,6 +618,26 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
554
618
  details: 'topP is not supported for reasoning models',
555
619
  });
556
620
  }
621
+
622
+ if (
623
+ modelCapabilities.supportedReasoningEfforts != null &&
624
+ (baseArgs.top_logprobs != null ||
625
+ baseArgs.include?.includes('message.output_text.logprobs'))
626
+ ) {
627
+ baseArgs.top_logprobs = undefined;
628
+ const filteredInclude = baseArgs.include?.filter(
629
+ value => value !== 'message.output_text.logprobs',
630
+ );
631
+ baseArgs.include =
632
+ filteredInclude != null && filteredInclude.length > 0
633
+ ? filteredInclude
634
+ : undefined;
635
+ warnings.push({
636
+ type: 'unsupported',
637
+ feature: 'logprobs',
638
+ details: 'logprobs is not supported for reasoning models',
639
+ });
640
+ }
557
641
  }
558
642
  } else {
559
643
  if (openaiOptions?.reasoningEffort != null) {
@@ -645,6 +729,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
645
729
  };
646
730
  }
647
731
 
732
+ private getArgs(options: LanguageModelV4CallOptions) {
733
+ return OpenAIResponsesLanguageModel.prepareRequest({
734
+ modelId: this.modelId,
735
+ config: this.config,
736
+ options,
737
+ });
738
+ }
739
+
648
740
  async doGenerate(
649
741
  options: LanguageModelV4CallOptions,
650
742
  ): Promise<LanguageModelV4GenerateResult> {
@@ -997,6 +1089,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
997
1089
  providerMetadata: {
998
1090
  [providerOptionsName]: {
999
1091
  itemId: part.id,
1092
+ ...(part.async != null && { async: part.async }),
1000
1093
  ...(part.namespace != null && { namespace: part.namespace }),
1001
1094
  ...(part.caller != null && {
1002
1095
  caller:
@@ -1007,7 +1100,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1007
1100
  }
1008
1101
  : part.caller,
1009
1102
  }),
1010
- },
1103
+ } satisfies ResponsesToolCallProviderMetadata,
1011
1104
  },
1012
1105
  });
1013
1106
  break;
@@ -1066,7 +1159,8 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1066
1159
  providerMetadata: {
1067
1160
  [providerOptionsName]: {
1068
1161
  itemId: part.id,
1069
- },
1162
+ ...(part.async != null && { async: part.async }),
1163
+ } satisfies ResponsesToolCallProviderMetadata,
1070
1164
  },
1071
1165
  });
1072
1166
  break;
@@ -1413,6 +1507,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1413
1507
  toolSearchExecution?: 'server' | 'client';
1414
1508
  suppressInputStreaming?: boolean;
1415
1509
  bufferedInputDeltas?: string[];
1510
+ async?: boolean | null;
1416
1511
  }
1417
1512
  | undefined
1418
1513
  > = {};
@@ -1506,6 +1601,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1506
1601
  toolCallId: value.item.call_id,
1507
1602
  suppressInputStreaming,
1508
1603
  bufferedInputDeltas: suppressInputStreaming ? [] : undefined,
1604
+ async: value.item.async,
1509
1605
  };
1510
1606
 
1511
1607
  if (!suppressInputStreaming) {
@@ -1522,6 +1618,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1522
1618
  ongoingToolCalls[value.output_index] = {
1523
1619
  toolName,
1524
1620
  toolCallId: value.item.call_id,
1621
+ async: value.item.async,
1525
1622
  };
1526
1623
 
1527
1624
  controller.enqueue({
@@ -1812,6 +1909,11 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1812
1909
  providerMetadata: {
1813
1910
  [providerOptionsName]: {
1814
1911
  itemId: item.id,
1912
+ ...(item.async != null
1913
+ ? { async: item.async }
1914
+ : ongoingToolCall?.async != null
1915
+ ? { async: ongoingToolCall.async }
1916
+ : {}),
1815
1917
  ...(item.namespace != null && {
1816
1918
  namespace: item.namespace,
1817
1919
  }),
@@ -1824,7 +1926,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1824
1926
  }
1825
1927
  : item.caller,
1826
1928
  }),
1827
- },
1929
+ } satisfies ResponsesToolCallProviderMetadata,
1828
1930
  },
1829
1931
  });
1830
1932
  };
@@ -1907,6 +2009,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1907
2009
  },
1908
2010
  });
1909
2011
  } else if (value.item.type === 'custom_tool_call') {
2012
+ const ongoingToolCall = ongoingToolCalls[value.output_index];
1910
2013
  ongoingToolCalls[value.output_index] = undefined;
1911
2014
  hasFunctionCall = true;
1912
2015
  const toolName = toolNameMapping.toCustomToolName(
@@ -1926,7 +2029,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1926
2029
  providerMetadata: {
1927
2030
  [providerOptionsName]: {
1928
2031
  itemId: value.item.id,
1929
- },
2032
+ ...(value.item.async != null
2033
+ ? { async: value.item.async }
2034
+ : ongoingToolCall?.async != null
2035
+ ? { async: ongoingToolCall.async }
2036
+ : {}),
2037
+ } satisfies ResponsesToolCallProviderMetadata,
1930
2038
  },
1931
2039
  });
1932
2040
  } else if (value.item.type === 'web_search_call') {
@@ -33,6 +33,11 @@ type AllowedToolResolution =
33
33
 
34
34
  export type OpenAIToolOptions = {
35
35
  allowedCallers?: Array<'direct' | 'programmatic'>;
36
+ /**
37
+ * Whether the model can continue generating after calling this tool without
38
+ * waiting for its result.
39
+ */
40
+ async?: boolean;
36
41
  deferLoading?: boolean;
37
42
  outputSchema?: JSONObject;
38
43
  namespace?: {
@@ -48,6 +53,7 @@ export async function prepareResponsesTools({
48
53
  toolNameMapping,
49
54
  customProviderToolNames,
50
55
  outputSchemaToolNames,
56
+ supportsAsyncToolCalling = true,
51
57
  }: {
52
58
  tools: LanguageModelV4CallOptions['tools'];
53
59
  toolChoice: LanguageModelV4CallOptions['toolChoice'] | undefined;
@@ -58,6 +64,7 @@ export async function prepareResponsesTools({
58
64
  toolNameMapping?: ToolNameMapping;
59
65
  customProviderToolNames?: Set<string>;
60
66
  outputSchemaToolNames?: Set<string>;
67
+ supportsAsyncToolCalling?: boolean;
61
68
  }): Promise<{
62
69
  tools?: Array<OpenAIResponsesTool>;
63
70
  toolChoice?:
@@ -140,6 +147,12 @@ export async function prepareResponsesTools({
140
147
  const openaiFunctionTool = prepareFunctionTool({
141
148
  tool,
142
149
  options: openaiOptions,
150
+ async: resolveAsyncToolOption({
151
+ value: openaiOptions?.async,
152
+ supportsAsyncToolCalling,
153
+ toolName: tool.name,
154
+ toolWarnings,
155
+ }),
143
156
  });
144
157
  const namespace = openaiOptions?.namespace;
145
158
 
@@ -378,6 +391,14 @@ export async function prepareResponsesTools({
378
391
  type: 'custom',
379
392
  name: tool.name,
380
393
  description: args.description,
394
+ ...(resolveAsyncToolOption({
395
+ value: args.async,
396
+ supportsAsyncToolCalling,
397
+ toolName: tool.name,
398
+ toolWarnings,
399
+ }) != null
400
+ ? { async: args.async }
401
+ : {}),
381
402
  format: args.format,
382
403
  });
383
404
  resolvedCustomProviderToolNames.add(tool.name);
@@ -606,9 +627,11 @@ function toAllowedToolResolution(
606
627
  function prepareFunctionTool({
607
628
  tool,
608
629
  options,
630
+ async,
609
631
  }: {
610
632
  tool: LanguageModelV4FunctionTool;
611
633
  options: OpenAIToolOptions | undefined;
634
+ async: boolean | undefined;
612
635
  }): OpenAIResponsesFunctionTool {
613
636
  const deferLoading = options?.deferLoading;
614
637
 
@@ -617,6 +640,7 @@ function prepareFunctionTool({
617
640
  name: tool.name,
618
641
  description: tool.description,
619
642
  parameters: tool.inputSchema,
643
+ ...(async != null ? { async } : {}),
620
644
  ...(tool.strict != null ? { strict: tool.strict } : {}),
621
645
  ...(deferLoading != null ? { defer_loading: deferLoading } : {}),
622
646
  ...(options?.allowedCallers != null
@@ -628,6 +652,29 @@ function prepareFunctionTool({
628
652
  };
629
653
  }
630
654
 
655
+ function resolveAsyncToolOption({
656
+ value,
657
+ supportsAsyncToolCalling,
658
+ toolName,
659
+ toolWarnings,
660
+ }: {
661
+ value: boolean | undefined;
662
+ supportsAsyncToolCalling: boolean;
663
+ toolName: string;
664
+ toolWarnings: SharedV4Warning[];
665
+ }): boolean | undefined {
666
+ if (value !== true || supportsAsyncToolCalling) {
667
+ return value;
668
+ }
669
+
670
+ toolWarnings.push({
671
+ type: 'unsupported',
672
+ feature: `async tool calling for "${toolName}"`,
673
+ details: 'Async tool calling is only supported by GPT-6 and later models.',
674
+ });
675
+ return undefined;
676
+ }
677
+
631
678
  function mapShellEnvironment(environment: {
632
679
  type?: string;
633
680
  [key: string]: unknown;