@ai-sdk/openai 4.0.52 → 4.0.54

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai",
3
- "version": "4.0.52",
3
+ "version": "4.0.54",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -3,10 +3,17 @@ import type {
3
3
  WebSocketConstructor,
4
4
  } from '@ai-sdk/provider-utils';
5
5
 
6
+ type OpenAIHeaders = Record<string, string | undefined>;
7
+
8
+ type SerializedOpenAIConfig = Omit<Partial<OpenAIConfig>, 'headers'> & {
9
+ headers?: (() => OpenAIHeaders) | OpenAIHeaders;
10
+ };
11
+
6
12
  export type OpenAIConfig = {
7
13
  provider: string;
14
+ baseURL?: string;
8
15
  url: (options: { modelId: string; path: string }) => string;
9
- headers?: () => Record<string, string | undefined>;
16
+ headers?: () => OpenAIHeaders;
10
17
  fetch?: FetchFunction;
11
18
  webSocket?: WebSocketConstructor;
12
19
  generateId?: () => string;
@@ -20,3 +27,36 @@ export type OpenAIConfig = {
20
27
  */
21
28
  fileIdPrefixes?: readonly string[];
22
29
  };
30
+
31
+ export function prepareOpenAIConfigForWorkflowDeserialize(
32
+ config: SerializedOpenAIConfig,
33
+ ): OpenAIConfig {
34
+ if (config.provider == null) {
35
+ throw new Error(
36
+ 'OpenAI model is missing provider after workflow deserialization.',
37
+ );
38
+ }
39
+
40
+ return {
41
+ ...config,
42
+ provider: config.provider,
43
+ url:
44
+ typeof config.url === 'function'
45
+ ? config.url
46
+ : ({ path }) => {
47
+ if (config.baseURL == null) {
48
+ throw new Error(
49
+ 'OpenAI model is missing baseURL after workflow deserialization.',
50
+ );
51
+ }
52
+
53
+ return `${config.baseURL}${path}`;
54
+ },
55
+ headers:
56
+ typeof config.headers === 'function'
57
+ ? config.headers
58
+ : config.headers == null
59
+ ? undefined
60
+ : () => config.headers as OpenAIHeaders,
61
+ };
62
+ }
@@ -308,6 +308,7 @@ export function createOpenAI(
308
308
  const createResponsesModel = (modelId: OpenAIResponsesModelId) => {
309
309
  return new OpenAIResponsesBatchLanguageModel(modelId, {
310
310
  provider: `${providerName}.responses`,
311
+ baseURL,
311
312
  url: ({ path }) => `${baseURL}${path}`,
312
313
  headers: getHeaders,
313
314
  fetch: options.fetch,
@@ -33,7 +33,10 @@ import {
33
33
  openaiErrorDataSchema,
34
34
  openaiFailedResponseHandler,
35
35
  } from './openai-error';
36
- import type { OpenAIConfig } from './openai-config';
36
+ import {
37
+ prepareOpenAIConfigForWorkflowDeserialize,
38
+ type OpenAIConfig,
39
+ } from './openai-config';
37
40
  import { openaiFilesResponseSchema } from './files/openai-files-api';
38
41
  import { convertOpenAIResponsesUsage } from './responses/convert-openai-responses-usage';
39
42
  import { mapOpenAIResponseFinishReason } from './responses/map-openai-responses-finish-reason';
@@ -41,7 +44,10 @@ import {
41
44
  openaiResponsesResponseSchema,
42
45
  type OpenAIResponsesLogprobs,
43
46
  } from './responses/openai-responses-api';
44
- import { OpenAIResponsesLanguageModel } from './responses/openai-responses-language-model';
47
+ import {
48
+ mapWebSearchOutput,
49
+ OpenAIResponsesLanguageModel,
50
+ } from './responses/openai-responses-language-model';
45
51
  import type { OpenAIResponsesModelId } from './responses/openai-responses-language-model-options';
46
52
  import type { ResponsesReasoningProviderMetadata } from './responses/openai-responses-provider-metadata';
47
53
 
@@ -163,6 +169,23 @@ class OpenAIResponsesBatch {
163
169
  for (const warning of preparedRequest.warnings) {
164
170
  warnings.push({ requestId: request.id, warning });
165
171
  }
172
+
173
+ for (const tool of request.options.tools ?? []) {
174
+ if (
175
+ tool.type === 'provider' &&
176
+ !openAIBatchConvertibleProviderToolIds.has(tool.id)
177
+ ) {
178
+ warnings.push({
179
+ requestId: request.id,
180
+ warning: {
181
+ type: 'unsupported',
182
+ feature: `batch result conversion for tool "${tool.name}"`,
183
+ details:
184
+ 'OpenAI may return output for this tool that AI SDK text batches cannot currently convert.',
185
+ },
186
+ });
187
+ }
188
+ }
166
189
  }
167
190
 
168
191
  const filename = 'batch.jsonl';
@@ -382,6 +405,14 @@ class OpenAIResponsesBatch {
382
405
  }
383
406
  }
384
407
 
408
+ const openAIBatchConvertibleProviderToolIds = new Set([
409
+ 'openai.code_interpreter',
410
+ 'openai.custom',
411
+ 'openai.file_search',
412
+ 'openai.web_search',
413
+ 'openai.web_search_preview',
414
+ ]);
415
+
385
416
  export class OpenAIResponsesBatchLanguageModel
386
417
  extends OpenAIResponsesLanguageModel
387
418
  implements BatchLanguageModelV4
@@ -393,12 +424,12 @@ export class OpenAIResponsesBatchLanguageModel
393
424
  }
394
425
 
395
426
  static [WORKFLOW_DESERIALIZE](options: {
396
- modelId: OpenAIResponsesModelId;
397
- config: OpenAIConfig;
427
+ modelId: string;
428
+ config: Parameters<typeof prepareOpenAIConfigForWorkflowDeserialize>[0];
398
429
  }) {
399
430
  return new OpenAIResponsesBatchLanguageModel(
400
- options.modelId,
401
- options.config,
431
+ options.modelId as OpenAIResponsesModelId,
432
+ prepareOpenAIConfigForWorkflowDeserialize(options.config),
402
433
  );
403
434
  }
404
435
 
@@ -577,6 +608,7 @@ async function convertOpenAIResponsesBatchResponse(
577
608
 
578
609
  const content: LanguageModelV4GenerateResult['content'] = [];
579
610
  const logprobs: Array<NonNullable<OpenAIResponsesLogprobs>> = [];
611
+ let hasFunctionCall = false;
580
612
 
581
613
  for (const part of response.output) {
582
614
  switch (part.type) {
@@ -612,15 +644,104 @@ async function convertOpenAIResponsesBatchResponse(
612
644
  }
613
645
 
614
646
  case 'function_call':
647
+ hasFunctionCall = true;
648
+ content.push({
649
+ type: 'tool-call',
650
+ toolCallId: part.call_id,
651
+ toolName: part.name,
652
+ input: part.arguments,
653
+ providerMetadata: {
654
+ openai: {
655
+ itemId: part.id,
656
+ ...(part.namespace != null && { namespace: part.namespace }),
657
+ ...(part.caller != null && {
658
+ caller:
659
+ part.caller.type === 'program'
660
+ ? { type: 'program', callerId: part.caller.caller_id }
661
+ : part.caller,
662
+ }),
663
+ },
664
+ },
665
+ });
666
+ break;
667
+
615
668
  case 'custom_tool_call':
616
- return {
617
- success: false,
618
- error: {
619
- message:
620
- 'OpenAI returned a tool call, but tool calls are not supported in AI SDK text batches.',
621
- code: 'unsupported_content',
669
+ hasFunctionCall = true;
670
+ content.push({
671
+ type: 'tool-call',
672
+ toolCallId: part.call_id,
673
+ toolName: part.name,
674
+ input: JSON.stringify(part.input),
675
+ providerMetadata: { openai: { itemId: part.id } },
676
+ });
677
+ break;
678
+
679
+ case 'web_search_call':
680
+ content.push({
681
+ type: 'tool-call',
682
+ toolCallId: part.id,
683
+ toolName: 'web_search',
684
+ input: '{}',
685
+ providerExecuted: true,
686
+ dynamic: true,
687
+ });
688
+ content.push({
689
+ type: 'tool-result',
690
+ toolCallId: part.id,
691
+ toolName: 'web_search',
692
+ result: mapWebSearchOutput(part.action),
693
+ dynamic: true,
694
+ });
695
+ break;
696
+
697
+ case 'file_search_call':
698
+ content.push({
699
+ type: 'tool-call',
700
+ toolCallId: part.id,
701
+ toolName: 'file_search',
702
+ input: '{}',
703
+ providerExecuted: true,
704
+ dynamic: true,
705
+ });
706
+ content.push({
707
+ type: 'tool-result',
708
+ toolCallId: part.id,
709
+ toolName: 'file_search',
710
+ result: {
711
+ queries: part.queries,
712
+ results:
713
+ part.results?.map(result => ({
714
+ attributes: result.attributes,
715
+ fileId: result.file_id,
716
+ filename: result.filename,
717
+ score: result.score,
718
+ text: result.text,
719
+ })) ?? null,
622
720
  },
623
- };
721
+ dynamic: true,
722
+ });
723
+ break;
724
+
725
+ case 'code_interpreter_call':
726
+ content.push({
727
+ type: 'tool-call',
728
+ toolCallId: part.id,
729
+ toolName: 'code_interpreter',
730
+ input: JSON.stringify({
731
+ code: part.code,
732
+ containerId: part.container_id,
733
+ }),
734
+ providerExecuted: true,
735
+ dynamic: true,
736
+ });
737
+ content.push({
738
+ type: 'tool-result',
739
+ toolCallId: part.id,
740
+ toolName: 'code_interpreter',
741
+ result: { outputs: part.outputs },
742
+ dynamic: true,
743
+ });
744
+ break;
624
745
 
625
746
  default:
626
747
  return {
@@ -655,7 +776,7 @@ async function convertOpenAIResponsesBatchResponse(
655
776
  finishReason: {
656
777
  unified: mapOpenAIResponseFinishReason({
657
778
  finishReason: response.incomplete_details?.reason,
658
- hasFunctionCall: false,
779
+ hasFunctionCall,
659
780
  }),
660
781
  raw: response.incomplete_details?.reason ?? undefined,
661
782
  },
@@ -831,24 +831,26 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
831
831
  type: z.enum(['response.completed', 'response.incomplete']),
832
832
  response: z.object({
833
833
  incomplete_details: z.object({ reason: z.string() }).nullish(),
834
- usage: z.object({
835
- input_tokens: z.number(),
836
- input_tokens_details: z
837
- .object({
838
- cached_tokens: z.number().nullish(),
839
- cache_write_tokens: z.number().nullish(),
840
- orchestration_input_tokens: z.number().nullish(),
841
- orchestration_input_cached_tokens: z.number().nullish(),
842
- })
843
- .nullish(),
844
- output_tokens: z.number(),
845
- output_tokens_details: z
846
- .object({
847
- reasoning_tokens: z.number().nullish(),
848
- orchestration_output_tokens: z.number().nullish(),
849
- })
850
- .nullish(),
851
- }),
834
+ usage: z
835
+ .object({
836
+ input_tokens: z.number(),
837
+ input_tokens_details: z
838
+ .object({
839
+ cached_tokens: z.number().nullish(),
840
+ cache_write_tokens: z.number().nullish(),
841
+ orchestration_input_tokens: z.number().nullish(),
842
+ orchestration_input_cached_tokens: z.number().nullish(),
843
+ })
844
+ .nullish(),
845
+ output_tokens: z.number(),
846
+ output_tokens_details: z
847
+ .object({
848
+ reasoning_tokens: z.number().nullish(),
849
+ orchestration_output_tokens: z.number().nullish(),
850
+ })
851
+ .nullish(),
852
+ })
853
+ .nullish(),
852
854
  reasoning: z
853
855
  .object({
854
856
  context: z.string().nullish(),
@@ -1762,7 +1764,7 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1762
1764
  })
1763
1765
  .nullish(),
1764
1766
  })
1765
- .optional(),
1767
+ .nullish(),
1766
1768
  }),
1767
1769
  ),
1768
1770
  );
@@ -30,7 +30,10 @@ import {
30
30
  type InferSchema,
31
31
  type ParseResult,
32
32
  } from '@ai-sdk/provider-utils';
33
- import type { OpenAIConfig } from '../openai-config';
33
+ import {
34
+ prepareOpenAIConfigForWorkflowDeserialize,
35
+ type OpenAIConfig,
36
+ } from '../openai-config';
34
37
  import { openaiFailedResponseHandler } from '../openai-error';
35
38
  import { getOpenAILanguageModelCapabilities } from '../openai-language-model-capabilities';
36
39
  import {
@@ -214,10 +217,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
214
217
  }
215
218
 
216
219
  static [WORKFLOW_DESERIALIZE](options: {
217
- modelId: OpenAIResponsesModelId;
218
- config: OpenAIConfig;
220
+ modelId: string;
221
+ config: Parameters<typeof prepareOpenAIConfigForWorkflowDeserialize>[0];
219
222
  }) {
220
- return new OpenAIResponsesLanguageModel(options.modelId, options.config);
223
+ return new OpenAIResponsesLanguageModel(
224
+ options.modelId as OpenAIResponsesModelId,
225
+ prepareOpenAIConfigForWorkflowDeserialize(options.config),
226
+ );
221
227
  }
222
228
 
223
229
  constructor(modelId: OpenAIResponsesModelId, config: OpenAIConfig) {
@@ -2578,7 +2584,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2578
2584
  raw: value.response.incomplete_details?.reason ?? undefined,
2579
2585
  };
2580
2586
  }
2581
- usage = value.response.usage;
2587
+ usage = value.response.usage ?? undefined;
2582
2588
  if (typeof value.response.service_tier === 'string') {
2583
2589
  serviceTier = value.response.service_tier;
2584
2590
  }
@@ -2909,7 +2915,7 @@ function isResponseOutputChunk(chunk: OpenAIResponsesChunk): boolean {
2909
2915
  );
2910
2916
  }
2911
2917
 
2912
- function mapWebSearchOutput(
2918
+ export function mapWebSearchOutput(
2913
2919
  action: OpenAIResponsesWebSearchAction | null | undefined,
2914
2920
  ): InferSchema<typeof webSearchOutputSchema> {
2915
2921
  if (action == null) {