@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,23 +1,32 @@
1
1
  import {
2
- SharedV3Warning,
3
- LanguageModelV3Prompt,
4
2
  UnsupportedFunctionalityError,
3
+ type SharedV4Warning,
4
+ type LanguageModelV4Prompt,
5
5
  } from '@ai-sdk/provider';
6
- import { OpenAIChatPrompt } from './openai-chat-prompt';
7
- import { convertToBase64 } from '@ai-sdk/provider-utils';
6
+ import type { OpenAIChatPrompt } from './openai-chat-prompt';
7
+ import {
8
+ convertToBase64,
9
+ getTopLevelMediaType,
10
+ resolveFullMediaType,
11
+ resolveProviderReference,
12
+ } from '@ai-sdk/provider-utils';
13
+
14
+ function serializeToolCallArguments(input: unknown): string {
15
+ return JSON.stringify(input === undefined ? {} : input);
16
+ }
8
17
 
9
18
  export function convertToOpenAIChatMessages({
10
19
  prompt,
11
20
  systemMessageMode = 'system',
12
21
  }: {
13
- prompt: LanguageModelV3Prompt;
22
+ prompt: LanguageModelV4Prompt;
14
23
  systemMessageMode?: 'system' | 'developer' | 'remove';
15
24
  }): {
16
25
  messages: OpenAIChatPrompt;
17
- warnings: Array<SharedV3Warning>;
26
+ warnings: Array<SharedV4Warning>;
18
27
  } {
19
28
  const messages: OpenAIChatPrompt = [];
20
- const warnings: Array<SharedV3Warning> = [];
29
+ const warnings: Array<SharedV4Warning> = [];
21
30
 
22
31
  for (const { role, content } of prompt) {
23
32
  switch (role) {
@@ -62,80 +71,99 @@ export function convertToOpenAIChatMessages({
62
71
  return { type: 'text', text: part.text };
63
72
  }
64
73
  case 'file': {
65
- if (part.mediaType.startsWith('image/')) {
66
- const mediaType =
67
- part.mediaType === 'image/*'
68
- ? 'image/jpeg'
69
- : part.mediaType;
70
-
71
- return {
72
- type: 'image_url',
73
- image_url: {
74
- url:
75
- part.data instanceof URL
76
- ? part.data.toString()
77
- : `data:${mediaType};base64,${convertToBase64(part.data)}`,
78
-
79
- // OpenAI specific extension: image detail
80
- detail: part.providerOptions?.openai?.imageDetail,
81
- },
82
- };
83
- } else if (part.mediaType.startsWith('audio/')) {
84
- if (part.data instanceof URL) {
74
+ switch (part.data.type) {
75
+ case 'reference': {
76
+ return {
77
+ type: 'file',
78
+ file: {
79
+ file_id: resolveProviderReference({
80
+ reference: part.data.reference,
81
+ provider: 'openai',
82
+ }),
83
+ },
84
+ };
85
+ }
86
+ case 'text': {
85
87
  throw new UnsupportedFunctionalityError({
86
- functionality: 'audio file parts with URLs',
88
+ functionality: 'text file parts',
87
89
  });
88
90
  }
91
+ case 'url':
92
+ case 'data': {
93
+ const topLevel = getTopLevelMediaType(part.mediaType);
89
94
 
90
- switch (part.mediaType) {
91
- case 'audio/wav': {
95
+ if (topLevel === 'image') {
92
96
  return {
93
- type: 'input_audio',
94
- input_audio: {
95
- data: convertToBase64(part.data),
96
- format: 'wav',
97
+ type: 'image_url',
98
+ image_url: {
99
+ url:
100
+ part.data.type === 'url'
101
+ ? part.data.url.toString()
102
+ : convertToBase64(part.data.data),
103
+
104
+ detail: part.providerOptions?.openai?.imageDetail,
97
105
  },
98
106
  };
107
+ } else if (topLevel === 'audio') {
108
+ if (part.data.type === 'url') {
109
+ throw new UnsupportedFunctionalityError({
110
+ functionality: 'audio file parts with URLs',
111
+ });
112
+ }
113
+
114
+ const fullMediaType = resolveFullMediaType({ part });
115
+
116
+ switch (fullMediaType) {
117
+ case 'audio/wav': {
118
+ return {
119
+ type: 'input_audio',
120
+ input_audio: {
121
+ data: convertToBase64(part.data.data),
122
+ format: 'wav',
123
+ },
124
+ };
125
+ }
126
+ case 'audio/mp3':
127
+ case 'audio/mpeg': {
128
+ return {
129
+ type: 'input_audio',
130
+ input_audio: {
131
+ data: convertToBase64(part.data.data),
132
+ format: 'mp3',
133
+ },
134
+ };
135
+ }
136
+
137
+ default: {
138
+ throw new UnsupportedFunctionalityError({
139
+ functionality: `audio content parts with media type ${fullMediaType}`,
140
+ });
141
+ }
142
+ }
99
143
  }
100
- case 'audio/mp3':
101
- case 'audio/mpeg': {
144
+ {
145
+ const fullMediaType = resolveFullMediaType({ part });
146
+ if (fullMediaType !== 'application/pdf') {
147
+ throw new UnsupportedFunctionalityError({
148
+ functionality: `file part media type ${fullMediaType}`,
149
+ });
150
+ }
151
+
152
+ if (part.data.type === 'url') {
153
+ throw new UnsupportedFunctionalityError({
154
+ functionality: 'PDF file parts with URLs',
155
+ });
156
+ }
157
+
102
158
  return {
103
- type: 'input_audio',
104
- input_audio: {
105
- data: convertToBase64(part.data),
106
- format: 'mp3',
159
+ type: 'file',
160
+ file: {
161
+ filename: part.filename ?? `part-${index}.pdf`,
162
+ file_data: `data:application/pdf;base64,${convertToBase64(part.data.data)}`,
107
163
  },
108
164
  };
109
165
  }
110
-
111
- default: {
112
- throw new UnsupportedFunctionalityError({
113
- functionality: `audio content parts with media type ${part.mediaType}`,
114
- });
115
- }
116
166
  }
117
- } else if (part.mediaType === 'application/pdf') {
118
- if (part.data instanceof URL) {
119
- throw new UnsupportedFunctionalityError({
120
- functionality: 'PDF file parts with URLs',
121
- });
122
- }
123
-
124
- return {
125
- type: 'file',
126
- file:
127
- typeof part.data === 'string' &&
128
- part.data.startsWith('file-')
129
- ? { file_id: part.data }
130
- : {
131
- filename: part.filename ?? `part-${index}.pdf`,
132
- file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
133
- },
134
- };
135
- } else {
136
- throw new UnsupportedFunctionalityError({
137
- functionality: `file part media type ${part.mediaType}`,
138
- });
139
167
  }
140
168
  }
141
169
  }
@@ -165,7 +193,7 @@ export function convertToOpenAIChatMessages({
165
193
  type: 'function',
166
194
  function: {
167
195
  name: part.toolName,
168
- arguments: JSON.stringify(part.input),
196
+ arguments: serializeToolCallArguments(part.input),
169
197
  },
170
198
  });
171
199
  break;
@@ -175,7 +203,7 @@ export function convertToOpenAIChatMessages({
175
203
 
176
204
  messages.push({
177
205
  role: 'assistant',
178
- content: text,
206
+ content: text || null,
179
207
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
180
208
  });
181
209
 
@@ -196,7 +224,7 @@ export function convertToOpenAIChatMessages({
196
224
  contentValue = output.value;
197
225
  break;
198
226
  case 'execution-denied':
199
- contentValue = output.reason ?? 'Tool execution denied.';
227
+ contentValue = output.reason ?? 'Tool call execution denied.';
200
228
  break;
201
229
  case 'content':
202
230
  case 'json':
@@ -1,8 +1,8 @@
1
- import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
1
+ import type { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
3
  export function mapOpenAIFinishReason(
4
4
  finishReason: string | null | undefined,
5
- ): LanguageModelV3FinishReason['unified'] {
5
+ ): LanguageModelV4FinishReason['unified'] {
6
6
  switch (finishReason) {
7
7
  case 'stop':
8
8
  return 'stop';
@@ -1,5 +1,9 @@
1
- import { JSONSchema7 } from '@ai-sdk/provider';
2
- import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
1
+ import type { JSONSchema7 } from '@ai-sdk/provider';
2
+ import {
3
+ lazySchema,
4
+ zodSchema,
5
+ type InferSchema,
6
+ } from '@ai-sdk/provider-utils';
3
7
  import { z } from 'zod/v4';
4
8
  import { openaiErrorDataSchema } from '../openai-error';
5
9
 
@@ -1,55 +1,58 @@
1
- import {
2
- InvalidResponseDataError,
3
- LanguageModelV3,
4
- LanguageModelV3CallOptions,
5
- LanguageModelV3Content,
6
- LanguageModelV3FinishReason,
7
- LanguageModelV3GenerateResult,
8
- LanguageModelV3StreamPart,
9
- LanguageModelV3StreamResult,
10
- SharedV3ProviderMetadata,
11
- SharedV3Warning,
1
+ import type {
2
+ LanguageModelV4,
3
+ LanguageModelV4CallOptions,
4
+ LanguageModelV4Content,
5
+ LanguageModelV4FinishReason,
6
+ LanguageModelV4GenerateResult,
7
+ LanguageModelV4StreamPart,
8
+ LanguageModelV4StreamResult,
9
+ SharedV4ProviderMetadata,
10
+ SharedV4Warning,
12
11
  } from '@ai-sdk/provider';
13
12
  import {
14
- FetchFunction,
15
- ParseResult,
13
+ StreamingToolCallTracker,
16
14
  combineHeaders,
17
15
  createEventSourceResponseHandler,
18
16
  createJsonResponseHandler,
19
17
  generateId,
20
- isParsableJson,
18
+ isCustomReasoning,
21
19
  parseProviderOptions,
22
20
  postJsonToApi,
21
+ serializeModelOptions,
22
+ WORKFLOW_DESERIALIZE,
23
+ WORKFLOW_SERIALIZE,
24
+ type FetchFunction,
25
+ type ParseResult,
23
26
  } from '@ai-sdk/provider-utils';
24
27
  import { openaiFailedResponseHandler } from '../openai-error';
25
28
  import { getOpenAILanguageModelCapabilities } from '../openai-language-model-capabilities';
26
29
  import {
27
- OpenAIChatUsage,
28
30
  convertOpenAIChatUsage,
31
+ type OpenAIChatUsage,
29
32
  } from './convert-openai-chat-usage';
30
33
  import { convertToOpenAIChatMessages } from './convert-to-openai-chat-messages';
31
34
  import { getResponseMetadata } from './get-response-metadata';
32
35
  import { mapOpenAIFinishReason } from './map-openai-finish-reason';
33
36
  import {
34
- OpenAIChatChunk,
35
37
  openaiChatChunkSchema,
36
38
  openaiChatResponseSchema,
39
+ type OpenAIChatChunk,
37
40
  } from './openai-chat-api';
38
41
  import {
39
- OpenAIChatModelId,
40
42
  openaiLanguageModelChatOptions,
43
+ type OpenAIChatModelId,
41
44
  } from './openai-chat-options';
42
45
  import { prepareChatTools } from './openai-chat-prepare-tools';
43
46
 
44
47
  type OpenAIChatConfig = {
45
48
  provider: string;
46
- headers: () => Record<string, string | undefined>;
49
+ headers?: () => Record<string, string | undefined>;
47
50
  url: (options: { modelId: string; path: string }) => string;
48
51
  fetch?: FetchFunction;
49
52
  };
50
53
 
51
- export class OpenAIChatLanguageModel implements LanguageModelV3 {
52
- readonly specificationVersion = 'v3';
54
+ export class OpenAIChatLanguageModel implements LanguageModelV4 {
55
+ readonly specificationVersion = 'v4';
53
56
 
54
57
  readonly modelId: OpenAIChatModelId;
55
58
 
@@ -59,6 +62,20 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
59
62
 
60
63
  private readonly config: OpenAIChatConfig;
61
64
 
65
+ static [WORKFLOW_SERIALIZE](model: OpenAIChatLanguageModel) {
66
+ return serializeModelOptions({
67
+ modelId: model.modelId,
68
+ config: model.config,
69
+ });
70
+ }
71
+
72
+ static [WORKFLOW_DESERIALIZE](options: {
73
+ modelId: OpenAIChatModelId;
74
+ config: OpenAIChatConfig;
75
+ }) {
76
+ return new OpenAIChatLanguageModel(options.modelId, options.config);
77
+ }
78
+
62
79
  constructor(modelId: OpenAIChatModelId, config: OpenAIChatConfig) {
63
80
  this.modelId = modelId;
64
81
  this.config = config;
@@ -81,9 +98,10 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
81
98
  seed,
82
99
  tools,
83
100
  toolChoice,
101
+ reasoning,
84
102
  providerOptions,
85
- }: LanguageModelV3CallOptions) {
86
- const warnings: SharedV3Warning[] = [];
103
+ }: LanguageModelV4CallOptions) {
104
+ const warnings: SharedV4Warning[] = [];
87
105
 
88
106
  // Parse provider options
89
107
  const openaiOptions =
@@ -94,6 +112,12 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
94
112
  })) ?? {};
95
113
 
96
114
  const modelCapabilities = getOpenAILanguageModelCapabilities(this.modelId);
115
+
116
+ // AI SDK reasoning values map directly to the OpenAI reasoning values.
117
+ const resolvedReasoningEffort =
118
+ openaiOptions.reasoningEffort ??
119
+ (isCustomReasoning(reasoning) ? reasoning : undefined);
120
+
97
121
  const isReasoningModel =
98
122
  openaiOptions.forceReasoning ?? modelCapabilities.isReasoningModel;
99
123
 
@@ -168,7 +192,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
168
192
  store: openaiOptions.store,
169
193
  metadata: openaiOptions.metadata,
170
194
  prediction: openaiOptions.prediction,
171
- reasoning_effort: openaiOptions.reasoningEffort,
195
+ reasoning_effort: resolvedReasoningEffort,
172
196
  service_tier: openaiOptions.serviceTier,
173
197
  prompt_cache_key: openaiOptions.promptCacheKey,
174
198
  prompt_cache_retention: openaiOptions.promptCacheRetention,
@@ -184,7 +208,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
184
208
  // when reasoning effort is none, gpt-5.1 models allow temperature, topP, logprobs
185
209
  // https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility
186
210
  if (
187
- openaiOptions.reasoningEffort !== 'none' ||
211
+ resolvedReasoningEffort !== 'none' ||
188
212
  !modelCapabilities.supportsNonReasoningParameters
189
213
  ) {
190
214
  if (baseArgs.temperature != null) {
@@ -314,8 +338,8 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
314
338
  }
315
339
 
316
340
  async doGenerate(
317
- options: LanguageModelV3CallOptions,
318
- ): Promise<LanguageModelV3GenerateResult> {
341
+ options: LanguageModelV4CallOptions,
342
+ ): Promise<LanguageModelV4GenerateResult> {
319
343
  const { args: body, warnings } = await this.getArgs(options);
320
344
 
321
345
  const {
@@ -327,7 +351,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
327
351
  path: '/chat/completions',
328
352
  modelId: this.modelId,
329
353
  }),
330
- headers: combineHeaders(this.config.headers(), options.headers),
354
+ headers: combineHeaders(this.config.headers?.(), options.headers),
331
355
  body,
332
356
  failedResponseHandler: openaiFailedResponseHandler,
333
357
  successfulResponseHandler: createJsonResponseHandler(
@@ -338,7 +362,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
338
362
  });
339
363
 
340
364
  const choice = response.choices[0];
341
- const content: Array<LanguageModelV3Content> = [];
365
+ const content: Array<LanguageModelV4Content> = [];
342
366
 
343
367
  // text content:
344
368
  const text = choice.message.content;
@@ -369,8 +393,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
369
393
 
370
394
  // provider metadata:
371
395
  const completionTokenDetails = response.usage?.completion_tokens_details;
372
- const promptTokenDetails = response.usage?.prompt_tokens_details;
373
- const providerMetadata: SharedV3ProviderMetadata = { openai: {} };
396
+ const providerMetadata: SharedV4ProviderMetadata = { openai: {} };
374
397
  if (completionTokenDetails?.accepted_prediction_tokens != null) {
375
398
  providerMetadata.openai.acceptedPredictionTokens =
376
399
  completionTokenDetails?.accepted_prediction_tokens;
@@ -402,8 +425,8 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
402
425
  }
403
426
 
404
427
  async doStream(
405
- options: LanguageModelV3CallOptions,
406
- ): Promise<LanguageModelV3StreamResult> {
428
+ options: LanguageModelV4CallOptions,
429
+ ): Promise<LanguageModelV4StreamResult> {
407
430
  const { args, warnings } = await this.getArgs(options);
408
431
 
409
432
  const body = {
@@ -419,7 +442,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
419
442
  path: '/chat/completions',
420
443
  modelId: this.modelId,
421
444
  }),
422
- headers: combineHeaders(this.config.headers(), options.headers),
445
+ headers: combineHeaders(this.config.headers?.(), options.headers),
423
446
  body,
424
447
  failedResponseHandler: openaiFailedResponseHandler,
425
448
  successfulResponseHandler: createEventSourceResponseHandler(
@@ -429,17 +452,9 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
429
452
  fetch: this.config.fetch,
430
453
  });
431
454
 
432
- const toolCalls: Array<{
433
- id: string;
434
- type: 'function';
435
- function: {
436
- name: string;
437
- arguments: string;
438
- };
439
- hasFinished: boolean;
440
- }> = [];
441
-
442
- let finishReason: LanguageModelV3FinishReason = {
455
+ let toolCallTracker: StreamingToolCallTracker;
456
+
457
+ let finishReason: LanguageModelV4FinishReason = {
443
458
  unified: 'other',
444
459
  raw: undefined,
445
460
  };
@@ -447,15 +462,19 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
447
462
  let metadataExtracted = false;
448
463
  let isActiveText = false;
449
464
 
450
- const providerMetadata: SharedV3ProviderMetadata = { openai: {} };
465
+ const providerMetadata: SharedV4ProviderMetadata = { openai: {} };
451
466
 
452
467
  return {
453
468
  stream: response.pipeThrough(
454
469
  new TransformStream<
455
470
  ParseResult<OpenAIChatChunk>,
456
- LanguageModelV3StreamPart
471
+ LanguageModelV4StreamPart
457
472
  >({
458
473
  start(controller) {
474
+ toolCallTracker = new StreamingToolCallTracker(controller, {
475
+ generateId,
476
+ typeValidation: 'if-present',
477
+ });
459
478
  controller.enqueue({ type: 'stream-start', warnings });
460
479
  },
461
480
 
@@ -547,124 +566,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
547
566
 
548
567
  if (delta.tool_calls != null) {
549
568
  for (const toolCallDelta of delta.tool_calls) {
550
- const index = toolCallDelta.index;
551
-
552
- // Tool call start. OpenAI returns all information except the arguments in the first chunk.
553
- if (toolCalls[index] == null) {
554
- if (
555
- toolCallDelta.type != null &&
556
- toolCallDelta.type !== 'function'
557
- ) {
558
- throw new InvalidResponseDataError({
559
- data: toolCallDelta,
560
- message: `Expected 'function' type.`,
561
- });
562
- }
563
-
564
- if (toolCallDelta.id == null) {
565
- throw new InvalidResponseDataError({
566
- data: toolCallDelta,
567
- message: `Expected 'id' to be a string.`,
568
- });
569
- }
570
-
571
- if (toolCallDelta.function?.name == null) {
572
- throw new InvalidResponseDataError({
573
- data: toolCallDelta,
574
- message: `Expected 'function.name' to be a string.`,
575
- });
576
- }
577
-
578
- controller.enqueue({
579
- type: 'tool-input-start',
580
- id: toolCallDelta.id,
581
- toolName: toolCallDelta.function.name,
582
- });
583
-
584
- toolCalls[index] = {
585
- id: toolCallDelta.id,
586
- type: 'function',
587
- function: {
588
- name: toolCallDelta.function.name,
589
- arguments: toolCallDelta.function.arguments ?? '',
590
- },
591
- hasFinished: false,
592
- };
593
-
594
- const toolCall = toolCalls[index];
595
-
596
- if (
597
- toolCall.function?.name != null &&
598
- toolCall.function?.arguments != null
599
- ) {
600
- // send delta if the argument text has already started:
601
- if (toolCall.function.arguments.length > 0) {
602
- controller.enqueue({
603
- type: 'tool-input-delta',
604
- id: toolCall.id,
605
- delta: toolCall.function.arguments,
606
- });
607
- }
608
-
609
- // check if tool call is complete
610
- // (some providers send the full tool call in one chunk):
611
- if (isParsableJson(toolCall.function.arguments)) {
612
- controller.enqueue({
613
- type: 'tool-input-end',
614
- id: toolCall.id,
615
- });
616
-
617
- controller.enqueue({
618
- type: 'tool-call',
619
- toolCallId: toolCall.id ?? generateId(),
620
- toolName: toolCall.function.name,
621
- input: toolCall.function.arguments,
622
- });
623
- toolCall.hasFinished = true;
624
- }
625
- }
626
-
627
- continue;
628
- }
629
-
630
- // existing tool call, merge if not finished
631
- const toolCall = toolCalls[index];
632
-
633
- if (toolCall.hasFinished) {
634
- continue;
635
- }
636
-
637
- if (toolCallDelta.function?.arguments != null) {
638
- toolCall.function!.arguments +=
639
- toolCallDelta.function?.arguments ?? '';
640
- }
641
-
642
- // send delta
643
- controller.enqueue({
644
- type: 'tool-input-delta',
645
- id: toolCall.id,
646
- delta: toolCallDelta.function.arguments ?? '',
647
- });
648
-
649
- // check if tool call is complete
650
- if (
651
- toolCall.function?.name != null &&
652
- toolCall.function?.arguments != null &&
653
- isParsableJson(toolCall.function.arguments)
654
- ) {
655
- controller.enqueue({
656
- type: 'tool-input-end',
657
- id: toolCall.id,
658
- });
659
-
660
- controller.enqueue({
661
- type: 'tool-call',
662
- toolCallId: toolCall.id ?? generateId(),
663
- toolName: toolCall.function.name,
664
- input: toolCall.function.arguments,
665
- });
666
- toolCall.hasFinished = true;
667
- }
569
+ toolCallTracker.processDelta(toolCallDelta);
668
570
  }
669
571
  }
670
572
 
@@ -687,6 +589,8 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
687
589
  controller.enqueue({ type: 'text-end', id: '0' });
688
590
  }
689
591
 
592
+ toolCallTracker.flush();
593
+
690
594
  controller.enqueue({
691
595
  type: 'finish',
692
596
  finishReason,
@@ -1,4 +1,8 @@
1
- import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
1
+ import {
2
+ lazySchema,
3
+ zodSchema,
4
+ type InferSchema,
5
+ } from '@ai-sdk/provider-utils';
2
6
  import { z } from 'zod/v4';
3
7
 
4
8
  // https://platform.openai.com/docs/models
@@ -51,8 +55,13 @@ export type OpenAIChatModelId =
51
55
  | 'gpt-5.2-chat-latest'
52
56
  | 'gpt-5.2-pro'
53
57
  | 'gpt-5.2-pro-2025-12-11'
58
+ | 'gpt-5.3-chat-latest'
54
59
  | 'gpt-5.4'
55
60
  | 'gpt-5.4-2026-03-05'
61
+ | 'gpt-5.4-mini'
62
+ | 'gpt-5.4-mini-2026-03-17'
63
+ | 'gpt-5.4-nano'
64
+ | 'gpt-5.4-nano-2026-03-17'
56
65
  | 'gpt-5.4-pro'
57
66
  | 'gpt-5.4-pro-2026-03-05'
58
67
  | (string & {});
@@ -1,9 +1,9 @@
1
1
  import {
2
- LanguageModelV3CallOptions,
3
- SharedV3Warning,
4
2
  UnsupportedFunctionalityError,
3
+ type LanguageModelV4CallOptions,
4
+ type SharedV4Warning,
5
5
  } from '@ai-sdk/provider';
6
- import {
6
+ import type {
7
7
  OpenAIChatToolChoice,
8
8
  OpenAIChatFunctionTool,
9
9
  } from './openai-chat-api';
@@ -12,17 +12,17 @@ export function prepareChatTools({
12
12
  tools,
13
13
  toolChoice,
14
14
  }: {
15
- tools: LanguageModelV3CallOptions['tools'];
16
- toolChoice?: LanguageModelV3CallOptions['toolChoice'];
15
+ tools: LanguageModelV4CallOptions['tools'];
16
+ toolChoice?: LanguageModelV4CallOptions['toolChoice'];
17
17
  }): {
18
18
  tools?: OpenAIChatFunctionTool[];
19
19
  toolChoice?: OpenAIChatToolChoice;
20
- toolWarnings: Array<SharedV3Warning>;
20
+ toolWarnings: Array<SharedV4Warning>;
21
21
  } {
22
22
  // when the tools array is empty, change it to undefined to prevent errors:
23
23
  tools = tools?.length ? tools : undefined;
24
24
 
25
- const toolWarnings: SharedV3Warning[] = [];
25
+ const toolWarnings: SharedV4Warning[] = [];
26
26
 
27
27
  if (tools == null) {
28
28
  return { tools: undefined, toolChoice: undefined, toolWarnings };