@ai-sdk/openai 4.0.104 → 4.0.106

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 (33) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/index.d.ts +50 -9
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +651 -185
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +4 -4
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +573 -142
  9. package/dist/internal/index.js.map +1 -1
  10. package/docs/03-openai.mdx +101 -9
  11. package/package.json +3 -3
  12. package/src/chat/openai-chat-language-model.ts +50 -28
  13. package/src/completion/openai-completion-language-model.ts +11 -3
  14. package/src/embedding/openai-embedding-model.ts +2 -1
  15. package/src/files/openai-files.ts +10 -8
  16. package/src/image/openai-image-model-options.ts +10 -20
  17. package/src/image/openai-image-model.ts +10 -21
  18. package/src/live/openai-realtime-model-live.ts +15 -16
  19. package/src/openai-batch.ts +22 -6
  20. package/src/openai-config.ts +2 -1
  21. package/src/openai-decision-model-options.ts +1 -0
  22. package/src/openai-decision-model.ts +48 -6
  23. package/src/openai-model-catalog.ts +935 -0
  24. package/src/openai-provider.ts +18 -13
  25. package/src/realtime/openai-realtime-factory.ts +6 -6
  26. package/src/realtime/openai-realtime-model.ts +7 -3
  27. package/src/resolve-openai-reasoning-effort.ts +45 -0
  28. package/src/responses/openai-responses-language-model.ts +35 -36
  29. package/src/skills/openai-skills.ts +4 -2
  30. package/src/speech/openai-speech-model.ts +5 -1
  31. package/src/speech-translation/openai-speech-translation-model.ts +5 -1
  32. package/src/transcription/openai-transcription-model.ts +25 -56
  33. package/src/openai-language-model-capabilities.ts +0 -101
@@ -4,6 +4,7 @@ import type {
4
4
  SharedV4Warning,
5
5
  } from '@ai-sdk/provider';
6
6
  import {
7
+ resolve,
7
8
  combineHeaders,
8
9
  convertBase64ToUint8Array,
9
10
  convertToFormData,
@@ -18,6 +19,7 @@ import {
18
19
  } from '@ai-sdk/provider-utils';
19
20
  import type { OpenAIConfig } from '../openai-config';
20
21
  import { openaiFailedResponseHandler } from '../openai-error';
22
+ import { getOpenAIImageModelEntry } from '../openai-model-catalog';
21
23
  import { openaiImageResponseSchema } from './openai-image-api';
22
24
  import {
23
25
  getMaxImagesPerCall,
@@ -63,24 +65,9 @@ export class OpenAIImageModel implements ImageModelV4 {
63
65
  return this.config.imageInputCapabilities.supportsFileInputs;
64
66
  }
65
67
 
66
- if (
67
- [
68
- 'dall-e-2',
69
- 'gpt-image-1',
70
- 'gpt-image-1-mini',
71
- 'gpt-image-1.5',
72
- 'gpt-image-2',
73
- 'gpt-image-2.5-flare',
74
- 'gpt-image-2.5-flare-2026-09-08',
75
- 'gpt-image-2.5-sunburst',
76
- 'gpt-image-2.5-sunburst-2026-09-08',
77
- 'chatgpt-image-latest',
78
- ].includes(this.modelId)
79
- ) {
80
- return true;
81
- }
82
-
83
- return this.modelId === 'dall-e-3' ? false : undefined;
68
+ const support = getOpenAIImageModelEntry(this.modelId).capabilities.image
69
+ .fileInput;
70
+ return typeof support === 'boolean' ? support : undefined;
84
71
  }
85
72
 
86
73
  get supportsMaskInputs(): boolean | undefined {
@@ -88,7 +75,9 @@ export class OpenAIImageModel implements ImageModelV4 {
88
75
  return this.config.imageInputCapabilities.supportsMaskInputs;
89
76
  }
90
77
 
91
- return this.supportsFileInputs;
78
+ const support = getOpenAIImageModelEntry(this.modelId).capabilities.image
79
+ .maskInput;
80
+ return typeof support === 'boolean' ? support : undefined;
92
81
  }
93
82
 
94
83
  get provider(): string {
@@ -144,7 +133,7 @@ export class OpenAIImageModel implements ImageModelV4 {
144
133
  path: '/images/edits',
145
134
  modelId: this.modelId,
146
135
  }),
147
- headers: combineHeaders(this.config.headers?.(), headers),
136
+ headers: combineHeaders(await resolve(this.config.headers), headers),
148
137
  formData: convertToFormData<OpenAIImageEditInput>({
149
138
  model: this.modelId,
150
139
  prompt,
@@ -234,7 +223,7 @@ export class OpenAIImageModel implements ImageModelV4 {
234
223
  path: '/images/generations',
235
224
  modelId: this.modelId,
236
225
  }),
237
- headers: combineHeaders(this.config.headers?.(), headers),
226
+ headers: combineHeaders(await resolve(this.config.headers), headers),
238
227
  body: {
239
228
  model: this.modelId,
240
229
  prompt,
@@ -4,6 +4,8 @@ import {
4
4
  type Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
5
5
  } from '@ai-sdk/provider';
6
6
  import {
7
+ resolveWith,
8
+ resolve,
7
9
  lazySchema,
8
10
  zodSchema,
9
11
  createJsonResponseHandler,
@@ -11,6 +13,7 @@ import {
11
13
  } from '@ai-sdk/provider-utils';
12
14
  import { z } from 'zod/v4';
13
15
  import { openaiFailedResponseHandler } from '../openai-error';
16
+ import { openaiLiveRealtimeCapabilities } from '../openai-model-catalog';
14
17
  import {
15
18
  createOpenAILiveServerEventParser,
16
19
  parseOpenAILiveServerEvent,
@@ -36,13 +39,7 @@ const webRTCSessionSchema = lazySchema(() =>
36
39
 
37
40
  export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
38
41
  readonly specificationVersion = 'v4' as const;
39
- readonly capabilities = {
40
- conversation: 'continuous',
41
- transports: ['websocket', 'webrtc'],
42
- connections: ['server-websocket', 'webrtc'],
43
- startup: 'session-start',
44
- finalization: 'session-close',
45
- } as const;
42
+ readonly capabilities = openaiLiveRealtimeCapabilities;
46
43
 
47
44
  constructor(
48
45
  readonly modelId: OpenAIRealtimeModelLiveId,
@@ -57,14 +54,16 @@ export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
57
54
  return { dataChannelLabel: 'oai-events' };
58
55
  }
59
56
 
60
- getServerWebSocketConfig(): { url: string; headers: Record<string, string> } {
61
- const url = new URL(`${this.config.baseURL}/live/sessions`);
62
- url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
63
- const headers: Record<string, string> = {};
64
- for (const [key, value] of Object.entries(this.config.headers())) {
65
- if (value !== undefined) headers[key] = value;
66
- }
67
- return { url: url.toString(), headers };
57
+ getServerWebSocketConfig() {
58
+ return resolveWith(this.config.headers, configHeaders => {
59
+ const url = new URL(`${this.config.baseURL}/live/sessions`);
60
+ url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
61
+ const headers: Record<string, string> = {};
62
+ for (const [key, value] of Object.entries(configHeaders)) {
63
+ if (value !== undefined) headers[key] = value;
64
+ }
65
+ return { url: url.toString(), headers };
66
+ });
68
67
  }
69
68
 
70
69
  async doCreateWebRTCSession({
@@ -83,7 +82,7 @@ export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
83
82
  );
84
83
  const { value } = await postJsonToApi({
85
84
  url: `${this.config.baseURL}/live/sessions`,
86
- headers: this.config.headers(),
85
+ headers: await resolve(this.config.headers),
87
86
  body: {
88
87
  session,
89
88
  transport: { type: 'webrtc', sdp: z.string().min(1).parse(sdp) },
@@ -19,6 +19,7 @@ import {
19
19
  type SharedV4Warning,
20
20
  } from '@ai-sdk/provider';
21
21
  import {
22
+ resolve,
22
23
  combineHeaders,
23
24
  convertAsyncIteratorToReadableStream,
24
25
  createJsonLinesResponseHandler,
@@ -267,7 +268,10 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
267
268
 
268
269
  const { value: uploadedFile } = await postToApi({
269
270
  url: this.getUrl('/files'),
270
- headers: combineHeaders(this.options.config.headers?.(), options.headers),
271
+ headers: combineHeaders(
272
+ await resolve(this.options.config.headers),
273
+ options.headers,
274
+ ),
271
275
  body: {
272
276
  content: formData,
273
277
  values: {
@@ -291,7 +295,10 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
291
295
 
292
296
  const { value: batch } = await postJsonToApi({
293
297
  url: this.getUrl('/batches'),
294
- headers: combineHeaders(this.options.config.headers?.(), options.headers),
298
+ headers: combineHeaders(
299
+ await resolve(this.options.config.headers),
300
+ options.headers,
301
+ ),
295
302
  body: {
296
303
  input_file_id: uploadedFile.id,
297
304
  endpoint: openaiBatchEndpoint,
@@ -357,7 +364,10 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
357
364
  url: this.getUrl(
358
365
  `/batches/${encodeURIComponent(options.batchId)}/cancel`,
359
366
  ),
360
- headers: combineHeaders(this.options.config.headers?.(), options.headers),
367
+ headers: combineHeaders(
368
+ await resolve(this.options.config.headers),
369
+ options.headers,
370
+ ),
361
371
  body: {},
362
372
  failedResponseHandler: openaiFailedResponseHandler,
363
373
  successfulResponseHandler: createJsonResponseHandler(
@@ -381,7 +391,10 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
381
391
 
382
392
  const { value: page } = await getFromApi({
383
393
  url: url.toString(),
384
- headers: combineHeaders(this.options.config.headers?.(), options.headers),
394
+ headers: combineHeaders(
395
+ await resolve(this.options.config.headers),
396
+ options.headers,
397
+ ),
385
398
  failedResponseHandler: openaiFailedResponseHandler,
386
399
  successfulResponseHandler: createJsonResponseHandler(
387
400
  openaiBatchListResponseSchema,
@@ -437,7 +450,10 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
437
450
  ): Promise<OpenAIBatchResponse> {
438
451
  const { value: batch } = await getFromApi({
439
452
  url: this.getUrl(`/batches/${encodeURIComponent(options.batchId)}`),
440
- headers: combineHeaders(this.options.config.headers?.(), options.headers),
453
+ headers: combineHeaders(
454
+ await resolve(this.options.config.headers),
455
+ options.headers,
456
+ ),
441
457
  failedResponseHandler: openaiFailedResponseHandler,
442
458
  successfulResponseHandler: createJsonResponseHandler(
443
459
  openaiBatchResponseSchema,
@@ -461,7 +477,7 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
461
477
  const { value: lines } = await getFromApi({
462
478
  url: this.getUrl(`/files/${encodeURIComponent(fileId)}/content`),
463
479
  headers: combineHeaders(
464
- this.options.config.headers?.(),
480
+ await resolve(this.options.config.headers),
465
481
  options.headers,
466
482
  ),
467
483
  failedResponseHandler: openaiFailedResponseHandler,
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ Resolvable,
2
3
  FetchFunction,
3
4
  WebSocketConstructor,
4
5
  } from '@ai-sdk/provider-utils';
@@ -15,7 +16,7 @@ export type OpenAIConfig = {
15
16
  requestModelId?: string;
16
17
  baseURL?: string;
17
18
  url: (options: { modelId: string; path: string }) => string;
18
- headers?: () => OpenAIHeaders;
19
+ headers?: Resolvable<OpenAIHeaders>;
19
20
  fetch?: FetchFunction;
20
21
  webSocket?: WebSocketConstructor;
21
22
  generateId?: () => string;
@@ -10,6 +10,7 @@ export const openaiDecisionModelOptions = lazySchema(() =>
10
10
  z.object({
11
11
  /** An opaque end-user identifier for safety monitoring, up to 128 characters. */
12
12
  safetyIdentifier: z.string().max(128).optional(),
13
+ promptCacheKey: z.string().optional(),
13
14
  }),
14
15
  ),
15
16
  );
@@ -4,8 +4,11 @@ import {
4
4
  type Experimental_DecisionModelV4 as DecisionModelV4,
5
5
  type Experimental_DecisionModelV4Answer as DecisionModelV4Answer,
6
6
  type Experimental_DecisionModelV4Input as DecisionModelV4Input,
7
+ type JSONObject,
8
+ type JSONValue,
7
9
  } from '@ai-sdk/provider';
8
10
  import {
11
+ resolve,
9
12
  lazySchema,
10
13
  zodSchema,
11
14
  combineHeaders,
@@ -144,10 +147,11 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
144
147
  responseHeaders,
145
148
  } = await postJsonToApi({
146
149
  url: this.config.url({ path: '/decisions', modelId: this.modelId }),
147
- headers: combineHeaders(this.config.headers?.(), headers),
150
+ headers: combineHeaders(await resolve(this.config.headers), headers),
148
151
  body: {
149
152
  model: this.modelId,
150
153
  safety_identifier: openaiOptions?.safetyIdentifier,
154
+ prompt_cache_key: openaiOptions?.promptCacheKey,
151
155
  input: [
152
156
  {
153
157
  role: 'user',
@@ -155,10 +159,9 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
155
159
  if (part.type === 'text')
156
160
  return { type: 'input_text', text: part.text };
157
161
  if (part.type === 'json')
158
- return {
159
- type: 'input_text',
160
- text: JSON.stringify(part.value),
161
- };
162
+ return isInputJsonData(part.value)
163
+ ? { type: 'input_json', data: part.value }
164
+ : { type: 'input_text', text: JSON.stringify(part.value) };
162
165
  if (part.data.type !== 'data') {
163
166
  throw new UnsupportedFunctionalityError({
164
167
  functionality: `OpenAI decision file input: ${part.mediaType} (${part.data.type})`,
@@ -323,7 +326,7 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
323
326
  ? [{ type: 'unsupported' as const, feature: 'reasoning' }]
324
327
  : []),
325
328
  ...Object.keys(providerOptions?.openai ?? {})
326
- .filter(option => option !== 'safetyIdentifier')
329
+ .filter(option => !SUPPORTED_PROVIDER_OPTIONS.has(option))
327
330
  .map(option => ({
328
331
  type: 'unsupported' as const,
329
332
  feature: `providerOptions.openai.${option}`,
@@ -351,6 +354,45 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
351
354
  }
352
355
  }
353
356
 
357
+ const SUPPORTED_PROVIDER_OPTIONS = new Set([
358
+ 'safetyIdentifier',
359
+ 'promptCacheKey',
360
+ ]);
361
+
354
362
  function toText(input: DecisionModelV4Input): string {
355
363
  return typeof input === 'string' ? input : JSON.stringify(input);
356
364
  }
365
+
366
+ const INPUT_JSON_MAX_DEPTH = 32;
367
+ const INPUT_JSON_MAX_VALUES = 10_000;
368
+ const INPUT_JSON_MAX_BYTES = 256 * 1024;
369
+
370
+ function isInputJsonData(value: JSONValue): value is JSONObject | JSONObject[] {
371
+ const hasInputJsonShape = Array.isArray(value)
372
+ ? value.length > 0 && value.every(isJSONObject)
373
+ : isJSONObject(value);
374
+ return hasInputJsonShape && fitsInputJsonLimits(value);
375
+ }
376
+
377
+ function fitsInputJsonLimits(value: JSONValue): boolean {
378
+ let count = 0;
379
+ const visit = (node: JSONValue | undefined, depth: number): boolean => {
380
+ count += 1;
381
+ if (count > INPUT_JSON_MAX_VALUES) return false;
382
+ if (node === null || typeof node !== 'object') return true;
383
+ if (depth > INPUT_JSON_MAX_DEPTH) return false;
384
+ const children: readonly (JSONValue | undefined)[] = Array.isArray(node)
385
+ ? node
386
+ : Object.values(node);
387
+ return children.every(child => visit(child, depth + 1));
388
+ };
389
+ return (
390
+ visit(value, 1) &&
391
+ new TextEncoder().encode(JSON.stringify(value)).length <=
392
+ INPUT_JSON_MAX_BYTES
393
+ );
394
+ }
395
+
396
+ function isJSONObject(value: JSONValue): value is JSONObject {
397
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
398
+ }