@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
@@ -0,0 +1,935 @@
1
+ import type {
2
+ ModelCapabilities,
3
+ ModelCapability,
4
+ ModelEntry,
5
+ ProviderOptionDefinition,
6
+ ProviderOptionDefinitions,
7
+ } from '@ai-sdk/provider';
8
+ import type { OpenAILanguageModelResponsesOptions } from './responses/openai-responses-language-model-options';
9
+
10
+ type OpenAILanguageProviderOption = keyof OpenAILanguageModelResponsesOptions;
11
+
12
+ /**
13
+ * Options accepted under `providerOptions.openai`. Language options are the
14
+ * Responses API options. The `satisfies` check keeps this list in sync with
15
+ * the option schema.
16
+ */
17
+ export const openaiProviderOptions = {
18
+ language: {
19
+ conversation: { description: 'OpenAI Conversation ID to continue.' },
20
+ include: { description: 'Extra fields to include in the response.' },
21
+ includeWebSearchSources: {
22
+ description: 'Include web search action sources.',
23
+ },
24
+ instructions: { description: 'Instructions for the model.' },
25
+ logprobs: { description: 'Return token log probabilities.' },
26
+ maxToolCalls: { description: 'Maximum number of built-in tool calls.' },
27
+ metadata: { description: 'Metadata to store with the generation.' },
28
+ parallelToolCalls: { description: 'Whether to use parallel tool calls.' },
29
+ previousResponseId: { description: 'Previous response ID to continue.' },
30
+ promptCacheKey: { description: 'Cache key for prompt caching.' },
31
+ promptCacheOptions: { description: 'Prompt cache mode, TTL, and prewarm.' },
32
+ promptCacheRetention: { description: 'Prompt cache retention policy.' },
33
+ reasoningEffort: { description: 'Reasoning effort.' },
34
+ reasoningEffortUpdate: {
35
+ description: 'Reasoning effort update from this response onward.',
36
+ },
37
+ reasoningMode: { description: 'Standard or pro reasoning mode.' },
38
+ reasoningContext: { description: 'Which reasoning items can be used.' },
39
+ reasoningSummary: { description: 'Reasoning summary output.' },
40
+ safetyIdentifier: { description: 'Identifier for safety monitoring.' },
41
+ serviceTier: { description: 'Service tier for the request.' },
42
+ store: { description: 'Whether to store the generation.' },
43
+ passThroughUnsupportedFiles: {
44
+ description: 'Send non-image file types as generic input files.',
45
+ },
46
+ strictJsonSchema: { description: 'Use strict JSON schema validation.' },
47
+ textVerbosity: { description: 'Verbosity of text responses.' },
48
+ truncation: { description: 'Output truncation behavior.' },
49
+ user: { description: 'End-user identifier for abuse monitoring.' },
50
+ systemMessageMode: { description: 'System message role override.' },
51
+ forceReasoning: { description: 'Treat the model as a reasoning model.' },
52
+ contextManagement: { description: 'Server-side context compaction.' },
53
+ compactionTrigger: { description: 'Request explicit compaction.' },
54
+ allowedTools: { description: 'Restrict callable tools to a subset.' },
55
+ } satisfies Record<OpenAILanguageProviderOption, ProviderOptionDefinition>,
56
+ } as const satisfies ProviderOptionDefinitions;
57
+
58
+ export type OpenAILanguageModelEntry = {
59
+ capabilities: {
60
+ language: NonNullable<ModelCapabilities['language']> & {
61
+ serviceTiers: {
62
+ flex: boolean;
63
+ priority: boolean;
64
+ };
65
+ configurationUpdates: boolean;
66
+ tools: NonNullable<ModelCapabilities['language']>['tools'] & {
67
+ async: boolean;
68
+ };
69
+ reasoning: NonNullable<ModelCapabilities['language']>['reasoning'] & {
70
+ sampling: 'unsupported' | 'reasoning-effort-none';
71
+ };
72
+ promptCache: {
73
+ retention: boolean;
74
+ options: ModelCapability;
75
+ };
76
+ endpoints: {
77
+ chat: {
78
+ temperatureWithoutReasoning: boolean;
79
+ functionToolsWithReasoning: boolean;
80
+ };
81
+ responses: {
82
+ logprobsRequireNonReasoning: boolean;
83
+ };
84
+ };
85
+ };
86
+ };
87
+ providerOptions: {
88
+ language: Record<OpenAILanguageProviderOption, ModelCapability>;
89
+ };
90
+ requestConfig: {
91
+ language: {
92
+ systemMessageMode: 'remove' | 'system' | 'developer';
93
+ };
94
+ };
95
+ };
96
+
97
+ type OpenAILanguageModelDefinition = {
98
+ capabilities?: {
99
+ language?: Partial<
100
+ Pick<
101
+ OpenAILanguageModelEntry['capabilities']['language'],
102
+ 'inputs' | 'objectGeneration' | 'tools' | 'reasoning'
103
+ >
104
+ >;
105
+ };
106
+ requestConfig?: {
107
+ language?: Partial<OpenAILanguageModelEntry['requestConfig']['language']>;
108
+ };
109
+ };
110
+
111
+ const configurableNonReasoning: OpenAILanguageModelDefinition = {
112
+ capabilities: {
113
+ language: {
114
+ reasoning: {
115
+ support: true,
116
+ efforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
117
+ sampling: 'reasoning-effort-none',
118
+ },
119
+ },
120
+ },
121
+ };
122
+
123
+ // Model-specific exceptions belong in the table; family rules provide defaults.
124
+ const openaiLanguageModelDefinitions: Record<
125
+ string,
126
+ OpenAILanguageModelDefinition
127
+ > = {
128
+ 'gpt-6-astra': {},
129
+ 'gpt-6.1-sol': {},
130
+ 'gpt-6.1-terra': configurableNonReasoning,
131
+ 'gpt-6-luna': configurableNonReasoning,
132
+ 'gpt-6-sol': configurableNonReasoning,
133
+ 'gpt-5.6': {},
134
+ 'gpt-5.6-luna': {},
135
+ 'gpt-5.6-sol': {},
136
+ 'gpt-5.6-terra': {},
137
+ 'gpt-5.5': {},
138
+ 'gpt-5.4-pro': {},
139
+ 'gpt-5.4': {},
140
+ 'gpt-5.4-mini': {},
141
+ 'gpt-5.4-nano': {},
142
+ 'gpt-5.3-chat-latest': {},
143
+ 'gpt-5.2-pro': {},
144
+ 'gpt-5.2-chat-latest': {},
145
+ 'gpt-5.2': {},
146
+ 'gpt-5.1-codex-mini': {},
147
+ 'gpt-5.1-codex': {},
148
+ 'gpt-5.1-chat-latest': {},
149
+ 'gpt-5.1': {},
150
+ 'gpt-5-pro': {},
151
+ 'gpt-5': {},
152
+ 'gpt-5-mini': {},
153
+ 'gpt-5-nano': {},
154
+ 'gpt-5-codex': {},
155
+ 'gpt-5-chat-latest': {
156
+ capabilities: {
157
+ language: {
158
+ objectGeneration: false,
159
+ tools: { usage: false, streaming: false, async: false },
160
+ },
161
+ },
162
+ },
163
+ 'gpt-4.1': {},
164
+ 'gpt-4.1-mini': {},
165
+ 'gpt-4.1-nano': {},
166
+ 'gpt-4o': {},
167
+ 'gpt-4o-mini': {},
168
+ };
169
+
170
+ const openaiLanguageModelEntries: Readonly<
171
+ Record<string, OpenAILanguageModelEntry>
172
+ > = Object.fromEntries(
173
+ Object.entries(openaiLanguageModelDefinitions).map(
174
+ ([modelId, definition]) => [
175
+ modelId,
176
+ createOpenAILanguageModelEntry(modelId, definition),
177
+ ],
178
+ ),
179
+ );
180
+
181
+ function createOpenAILanguageModelEntry(
182
+ modelId: string,
183
+ definition?: OpenAILanguageModelDefinition,
184
+ ): OpenAILanguageModelEntry {
185
+ const oSeriesVersion = getOSeriesVersion(modelId);
186
+ const gptVersion = getGptVersion(modelId);
187
+ const isRecognizedModelFamily =
188
+ oSeriesVersion != null ||
189
+ gptVersion != null ||
190
+ /^gpt-4o(?:-|$)/.test(modelId);
191
+ const isGptChatModel =
192
+ gptVersion?.minor == null &&
193
+ (gptVersion?.variant?.startsWith('chat') ?? false);
194
+ const isGptNanoModel = gptVersion?.variant?.startsWith('nano') ?? false;
195
+ const isGpt6OrLaterModel = gptVersion != null && gptVersion.major >= 6;
196
+ const isGpt56OrLaterModel =
197
+ isGpt6OrLaterModel ||
198
+ (gptVersion?.major === 5 && (gptVersion.minor ?? 0) >= 6);
199
+ // Pro, codex, and chat variants are excluded because their supported
200
+ // efforts are not known.
201
+ const isStandardGpt5Model =
202
+ gptVersion?.major === 5 &&
203
+ (gptVersion.variant == null ||
204
+ /^(?:(?:mini|nano|sol|luna|terra)(?:-\d{4}-\d{2}-\d{2})?|\d{4}-\d{2}-\d{2})$/.test(
205
+ gptVersion.variant,
206
+ ));
207
+ const supportsFlexProcessing =
208
+ (oSeriesVersion != null && oSeriesVersion >= 3) ||
209
+ (gptVersion != null && gptVersion.major >= 5 && !isGptChatModel);
210
+ const supportsPriorityProcessing =
211
+ modelId.startsWith('gpt-4') ||
212
+ (gptVersion != null &&
213
+ gptVersion.major >= 5 &&
214
+ !isGptNanoModel &&
215
+ !isGptChatModel) ||
216
+ (oSeriesVersion != null && oSeriesVersion >= 3);
217
+ const isReasoningModel =
218
+ oSeriesVersion != null ||
219
+ (gptVersion != null && gptVersion.major >= 5 && !isGptChatModel);
220
+ const reasoningSampling: OpenAILanguageModelEntry['capabilities']['language']['reasoning']['sampling'] =
221
+ definition?.capabilities?.language?.reasoning?.sampling ??
222
+ (!isGpt6OrLaterModel &&
223
+ gptVersion != null &&
224
+ (gptVersion.major > 5 ||
225
+ (gptVersion.major === 5 && (gptVersion.minor ?? 0) >= 1))
226
+ ? 'reasoning-effort-none'
227
+ : 'unsupported');
228
+ const sampling: OpenAILanguageModelEntry['capabilities']['language']['sampling']['temperature'] =
229
+ !isRecognizedModelFamily
230
+ ? 'unknown'
231
+ : !isReasoningModel
232
+ ? true
233
+ : reasoningSampling === 'reasoning-effort-none'
234
+ ? { condition: 'Only when reasoning effort is none.' }
235
+ : false;
236
+ const chatTemperatureSupportedWithoutReasoning =
237
+ !modelId.startsWith('gpt-4o-search-preview') &&
238
+ !modelId.startsWith('gpt-4o-mini-search-preview');
239
+
240
+ const entry: Omit<OpenAILanguageModelEntry, 'providerOptions'> = {
241
+ capabilities: {
242
+ language: {
243
+ inputs: definition?.capabilities?.language?.inputs ?? {
244
+ image: definition != null ? true : 'unknown',
245
+ audio: definition != null ? false : 'unknown',
246
+ },
247
+ objectGeneration:
248
+ definition?.capabilities?.language?.objectGeneration ??
249
+ (definition != null ? true : 'unknown'),
250
+ tools: definition?.capabilities?.language?.tools ?? {
251
+ usage: definition != null ? true : 'unknown',
252
+ streaming: definition != null ? true : 'unknown',
253
+ async: isGpt6OrLaterModel,
254
+ },
255
+ reasoning: {
256
+ support: isRecognizedModelFamily ? isReasoningModel : 'unknown',
257
+ efforts:
258
+ definition?.capabilities?.language?.reasoning?.efforts ??
259
+ (isGpt6OrLaterModel
260
+ ? ['low', 'medium', 'high', 'xhigh', 'max']
261
+ : isStandardGpt5Model
262
+ ? getGpt5ReasoningEfforts(gptVersion.minor ?? 0)
263
+ : undefined),
264
+ sampling: reasoningSampling,
265
+ },
266
+ sampling: {
267
+ temperature: chatTemperatureSupportedWithoutReasoning
268
+ ? sampling
269
+ : {
270
+ condition:
271
+ 'Not supported by Chat Completions search preview models.',
272
+ },
273
+ topP: sampling,
274
+ logprobs:
275
+ isReasoningModel && !isGpt6OrLaterModel
276
+ ? {
277
+ condition:
278
+ reasoningSampling === 'reasoning-effort-none'
279
+ ? 'Chat Completions requires reasoning effort none; Responses support is model-dependent.'
280
+ : 'Not supported by Chat Completions while reasoning; Responses support is model-dependent.',
281
+ }
282
+ : sampling,
283
+ },
284
+ serviceTiers: {
285
+ flex: supportsFlexProcessing,
286
+ priority: supportsPriorityProcessing,
287
+ },
288
+ configurationUpdates: isGpt6OrLaterModel,
289
+ promptCache: {
290
+ retention: !isGpt6OrLaterModel,
291
+ // Prompt cache options are documented for GPT-5.6 and later.
292
+ options: isRecognizedModelFamily ? isGpt56OrLaterModel : 'unknown',
293
+ },
294
+ endpoints: {
295
+ chat: {
296
+ temperatureWithoutReasoning:
297
+ chatTemperatureSupportedWithoutReasoning,
298
+ // GPT-5.6 rejects function tools with reasoning in Chat Completions.
299
+ functionToolsWithReasoning: !(
300
+ gptVersion?.major === 5 && (gptVersion.minor ?? 0) >= 6
301
+ ),
302
+ },
303
+ responses: {
304
+ logprobsRequireNonReasoning: isGpt6OrLaterModel,
305
+ },
306
+ },
307
+ },
308
+ },
309
+ requestConfig: {
310
+ language: {
311
+ systemMessageMode:
312
+ definition?.requestConfig?.language?.systemMessageMode ??
313
+ (isReasoningModel ? 'developer' : 'system'),
314
+ },
315
+ },
316
+ };
317
+
318
+ // Options default to supported; exceptions reuse capabilities declared above.
319
+ const { language } = entry.capabilities;
320
+ return {
321
+ ...entry,
322
+ providerOptions: {
323
+ language: declareProviderOptions(
324
+ openaiProviderOptions.language,
325
+ definition != null ? true : 'unknown',
326
+ {
327
+ logprobs: language.sampling.logprobs,
328
+ maxToolCalls: language.tools.usage,
329
+ parallelToolCalls: language.tools.usage,
330
+ allowedTools: language.tools.usage,
331
+ reasoningEffort: language.reasoning.support,
332
+ reasoningSummary: language.reasoning.support,
333
+ reasoningMode: language.reasoning.support,
334
+ reasoningContext: language.reasoning.support,
335
+ reasoningEffortUpdate: language.configurationUpdates,
336
+ promptCacheOptions: language.promptCache.options,
337
+ promptCacheRetention: language.promptCache.retention,
338
+ },
339
+ ),
340
+ },
341
+ };
342
+ }
343
+
344
+ /** Applies one default support value to every option, except the listed ones. */
345
+ function declareProviderOptions<Option extends string>(
346
+ definitions: Readonly<Record<Option, ProviderOptionDefinition>>,
347
+ support: ModelCapability,
348
+ exceptions: Partial<Record<Option, ModelCapability>>,
349
+ ): Record<Option, ModelCapability> {
350
+ return Object.fromEntries(
351
+ (Object.keys(definitions) as Option[]).map(option => [
352
+ option,
353
+ exceptions[option] ?? support,
354
+ ]),
355
+ ) as Record<Option, ModelCapability>;
356
+ }
357
+
358
+ function getGpt5ReasoningEfforts(minor: number): readonly string[] {
359
+ if (minor === 0) {
360
+ return ['minimal', 'low', 'medium', 'high'];
361
+ }
362
+
363
+ if (minor === 1) {
364
+ return ['none', 'low', 'medium', 'high'];
365
+ }
366
+
367
+ if (minor < 6) {
368
+ return ['none', 'low', 'medium', 'high', 'xhigh'];
369
+ }
370
+
371
+ return ['none', 'low', 'medium', 'high', 'xhigh', 'max'];
372
+ }
373
+
374
+ function getOSeriesVersion(modelId: string): number | undefined {
375
+ const match = /^o(\d+)(?:-|$)/.exec(modelId);
376
+ return match == null ? undefined : Number(match[1]);
377
+ }
378
+
379
+ function getGptVersion(modelId: string):
380
+ | {
381
+ major: number;
382
+ minor: number | undefined;
383
+ variant: string | undefined;
384
+ }
385
+ | undefined {
386
+ const match = /^gpt-(\d+)(?:\.(\d+))?(?:-(.+))?$/.exec(modelId);
387
+ return match == null
388
+ ? undefined
389
+ : {
390
+ major: Number(match[1]),
391
+ minor: match[2] == null ? undefined : Number(match[2]),
392
+ variant: match[3],
393
+ };
394
+ }
395
+
396
+ type OperationModelEntry<Section extends keyof ModelCapabilities> = {
397
+ readonly capabilities: {
398
+ readonly [Key in Section]-?: NonNullable<ModelCapabilities[Key]>;
399
+ };
400
+ };
401
+
402
+ export type OpenAIImageModelEntry = OperationModelEntry<'image'> & {
403
+ readonly requestConfig: {
404
+ readonly image: {
405
+ readonly responseFormat: 'provider-default' | 'b64_json';
406
+ };
407
+ };
408
+ };
409
+
410
+ export type OpenAITranscriptionModelEntry =
411
+ OperationModelEntry<'transcription'> & {
412
+ readonly requestConfig: {
413
+ readonly transcription: {
414
+ readonly responseFormat?: 'verbose_json' | 'diarized_json';
415
+ readonly chunkingStrategy?: 'auto';
416
+ readonly languageField: 'language' | 'languages';
417
+ };
418
+ };
419
+ };
420
+
421
+ const openaiEmbeddingModelEntries: Readonly<
422
+ Record<string, OperationModelEntry<'embedding'>>
423
+ > = {
424
+ 'text-embedding-3-large': {
425
+ capabilities: {
426
+ embedding: { defaultDimensions: 3072, customDimensions: true },
427
+ },
428
+ },
429
+ 'text-embedding-3-small': {
430
+ capabilities: {
431
+ embedding: { defaultDimensions: 1536, customDimensions: true },
432
+ },
433
+ },
434
+ 'text-embedding-ada-002': {
435
+ capabilities: {
436
+ embedding: { defaultDimensions: 1536, customDimensions: false },
437
+ },
438
+ },
439
+ };
440
+
441
+ const gptImageSizes = ['1024x1024', '1536x1024', '1024x1536'];
442
+ const customImageSizes = {
443
+ description: 'Standard presets and custom dimensions',
444
+ };
445
+
446
+ function createGptImageEntry(
447
+ sizes: NonNullable<ModelCapabilities['image']>['sizes'],
448
+ ): OpenAIImageModelEntry {
449
+ return {
450
+ capabilities: {
451
+ image: {
452
+ sizes,
453
+ fileInput: true,
454
+ maskInput: true,
455
+ maxImagesPerCall: 10,
456
+ },
457
+ },
458
+ requestConfig: { image: { responseFormat: 'provider-default' } },
459
+ };
460
+ }
461
+
462
+ const openaiImageModelEntries: Readonly<Record<string, OpenAIImageModelEntry>> =
463
+ {
464
+ 'gpt-image-2.5-flare': createGptImageEntry(customImageSizes),
465
+ 'gpt-image-2.5-sunburst': createGptImageEntry(customImageSizes),
466
+ 'gpt-image-2': createGptImageEntry(gptImageSizes),
467
+ 'gpt-image-1.5': createGptImageEntry(gptImageSizes),
468
+ 'gpt-image-1-mini': createGptImageEntry(gptImageSizes),
469
+ 'gpt-image-1': createGptImageEntry(gptImageSizes),
470
+ 'dall-e-3': {
471
+ capabilities: {
472
+ image: {
473
+ sizes: ['1024x1024', '1792x1024', '1024x1792'],
474
+ fileInput: false,
475
+ maskInput: false,
476
+ maxImagesPerCall: 1,
477
+ },
478
+ },
479
+ requestConfig: { image: { responseFormat: 'b64_json' } },
480
+ },
481
+ 'dall-e-2': {
482
+ capabilities: {
483
+ image: {
484
+ sizes: ['256x256', '512x512', '1024x1024'],
485
+ fileInput: true,
486
+ maskInput: true,
487
+ maxImagesPerCall: 10,
488
+ },
489
+ },
490
+ requestConfig: { image: { responseFormat: 'b64_json' } },
491
+ },
492
+ };
493
+
494
+ const imageModelAliases: Readonly<Record<string, OpenAIImageModelEntry>> = {
495
+ 'gpt-image-2.5-flare-2026-09-08':
496
+ openaiImageModelEntries['gpt-image-2.5-flare'],
497
+ 'gpt-image-2.5-sunburst-2026-09-08':
498
+ openaiImageModelEntries['gpt-image-2.5-sunburst'],
499
+ 'chatgpt-image-latest': createGptImageEntry({ description: 'Unknown' }),
500
+ };
501
+
502
+ function resolveOpenAIImageModelEntry(modelId: string): OpenAIImageModelEntry {
503
+ if (Object.prototype.hasOwnProperty.call(openaiImageModelEntries, modelId)) {
504
+ return openaiImageModelEntries[modelId];
505
+ }
506
+ if (Object.prototype.hasOwnProperty.call(imageModelAliases, modelId)) {
507
+ return imageModelAliases[modelId];
508
+ }
509
+ return {
510
+ capabilities: {
511
+ image: {
512
+ sizes: { description: 'Unknown' },
513
+ fileInput: 'unknown',
514
+ maskInput: 'unknown',
515
+ maxImagesPerCall: modelId.startsWith('gpt-image-') ? 10 : 1,
516
+ },
517
+ },
518
+ requestConfig: {
519
+ image: {
520
+ responseFormat:
521
+ modelId.startsWith('chatgpt-image-') ||
522
+ modelId.startsWith('gpt-image-')
523
+ ? 'provider-default'
524
+ : 'b64_json',
525
+ },
526
+ },
527
+ };
528
+ }
529
+
530
+ export const openaiImageModelMaxImagesPerCall: Record<string, number> =
531
+ Object.fromEntries(
532
+ Object.entries({ ...openaiImageModelEntries, ...imageModelAliases }).map(
533
+ ([modelId, entry]) => [
534
+ modelId,
535
+ entry.capabilities.image.maxImagesPerCall ?? 1,
536
+ ],
537
+ ),
538
+ );
539
+
540
+ const defaultTranscriptionRequestConfig: OpenAITranscriptionModelEntry['requestConfig']['transcription'] =
541
+ {
542
+ languageField: 'language',
543
+ };
544
+
545
+ const openaiTranscriptionModelEntries: Readonly<
546
+ Record<string, OpenAITranscriptionModelEntry>
547
+ > = {
548
+ 'whisper-1': {
549
+ capabilities: {
550
+ transcription: {
551
+ transcription: true,
552
+ streaming: false,
553
+ streamingDelay: false,
554
+ duration: true,
555
+ segments: true,
556
+ language: true,
557
+ },
558
+ },
559
+ requestConfig: {
560
+ transcription: {
561
+ ...defaultTranscriptionRequestConfig,
562
+ responseFormat: 'verbose_json',
563
+ },
564
+ },
565
+ },
566
+ 'gpt-4o-mini-transcribe': {
567
+ capabilities: {
568
+ transcription: {
569
+ transcription: true,
570
+ streaming: false,
571
+ streamingDelay: false,
572
+ duration: false,
573
+ segments: false,
574
+ language: false,
575
+ },
576
+ },
577
+ requestConfig: { transcription: defaultTranscriptionRequestConfig },
578
+ },
579
+ 'gpt-4o-transcribe': {
580
+ capabilities: {
581
+ transcription: {
582
+ transcription: true,
583
+ streaming: false,
584
+ streamingDelay: false,
585
+ duration: false,
586
+ segments: false,
587
+ language: false,
588
+ },
589
+ },
590
+ requestConfig: { transcription: defaultTranscriptionRequestConfig },
591
+ },
592
+ 'gpt-4o-transcribe-diarize': {
593
+ capabilities: {
594
+ transcription: {
595
+ transcription: true,
596
+ streaming: false,
597
+ streamingDelay: false,
598
+ duration: true,
599
+ segments: true,
600
+ language: false,
601
+ },
602
+ },
603
+ requestConfig: {
604
+ transcription: {
605
+ ...defaultTranscriptionRequestConfig,
606
+ responseFormat: 'diarized_json',
607
+ chunkingStrategy: 'auto',
608
+ },
609
+ },
610
+ },
611
+ 'gpt-transcribe': {
612
+ capabilities: {
613
+ transcription: {
614
+ transcription: true,
615
+ streaming: true,
616
+ streamingDelay: false,
617
+ duration: false,
618
+ segments: false,
619
+ language: true,
620
+ },
621
+ },
622
+ requestConfig: { transcription: { languageField: 'languages' } },
623
+ },
624
+ 'gpt-live-transcribe': {
625
+ capabilities: {
626
+ transcription: {
627
+ transcription: false,
628
+ streaming: true,
629
+ streamingDelay: true,
630
+ duration: false,
631
+ segments: false,
632
+ language: false,
633
+ },
634
+ },
635
+ requestConfig: { transcription: { languageField: 'languages' } },
636
+ },
637
+ 'gpt-realtime-whisper': {
638
+ capabilities: {
639
+ transcription: {
640
+ transcription: false,
641
+ streaming: true,
642
+ streamingDelay: true,
643
+ duration: false,
644
+ segments: false,
645
+ language: false,
646
+ },
647
+ },
648
+ requestConfig: { transcription: defaultTranscriptionRequestConfig },
649
+ },
650
+ };
651
+
652
+ const transcriptionSnapshotModelIds = [
653
+ 'gpt-transcribe',
654
+ 'gpt-live-transcribe',
655
+ 'gpt-realtime-whisper',
656
+ ] as const;
657
+
658
+ function resolveOpenAITranscriptionModelEntry(
659
+ modelId: string,
660
+ ): OpenAITranscriptionModelEntry {
661
+ if (
662
+ Object.prototype.hasOwnProperty.call(
663
+ openaiTranscriptionModelEntries,
664
+ modelId,
665
+ )
666
+ ) {
667
+ return openaiTranscriptionModelEntries[modelId];
668
+ }
669
+ for (const baseModelId of transcriptionSnapshotModelIds) {
670
+ if (modelId.startsWith(`${baseModelId}-`)) {
671
+ return openaiTranscriptionModelEntries[baseModelId];
672
+ }
673
+ }
674
+ return {
675
+ capabilities: {
676
+ transcription: {
677
+ transcription: 'unknown',
678
+ streaming: 'unknown',
679
+ streamingDelay: 'unknown',
680
+ duration: 'unknown',
681
+ segments: 'unknown',
682
+ language: 'unknown',
683
+ },
684
+ },
685
+ requestConfig: { transcription: defaultTranscriptionRequestConfig },
686
+ };
687
+ }
688
+
689
+ const openaiSpeechTranslationModelEntries: Readonly<
690
+ Record<string, OperationModelEntry<'speechTranslation'>>
691
+ > = {
692
+ 'gpt-realtime-translate': {
693
+ capabilities: {
694
+ speechTranslation: {
695
+ translatedAudio: true,
696
+ translatedText: true,
697
+ sourceTranscript: true,
698
+ },
699
+ },
700
+ },
701
+ };
702
+
703
+ const openaiSpeechModelEntries: Readonly<
704
+ Record<string, OperationModelEntry<'speech'>>
705
+ > = {
706
+ 'tts-1': { capabilities: { speech: { instructions: false } } },
707
+ 'tts-1-hd': { capabilities: { speech: { instructions: false } } },
708
+ 'gpt-4o-mini-tts': { capabilities: { speech: { instructions: true } } },
709
+ };
710
+
711
+ type RealtimeRequestConfig = {
712
+ api: 'live' | 'realtime';
713
+ startup: 'session-start' | 'session-update';
714
+ finalization: 'session-close' | 'transport-close';
715
+ };
716
+
717
+ type OpenAIRealtimeModelEntry = {
718
+ capabilities: { realtime: NonNullable<ModelCapabilities['realtime']> };
719
+ requestConfig: { realtime: RealtimeRequestConfig };
720
+ };
721
+
722
+ type OpenAIRequestConfig = {
723
+ language?: OpenAILanguageModelEntry['requestConfig']['language'];
724
+ image?: OpenAIImageModelEntry['requestConfig']['image'];
725
+ transcription?: OpenAITranscriptionModelEntry['requestConfig']['transcription'];
726
+ realtime?: RealtimeRequestConfig;
727
+ };
728
+
729
+ type OpenAIModelEntry = ModelEntry<OpenAIRequestConfig> & {
730
+ capabilities: ModelCapabilities & {
731
+ language?: OpenAILanguageModelEntry['capabilities']['language'];
732
+ };
733
+ providerOptions?: {
734
+ language?: OpenAILanguageModelEntry['providerOptions']['language'];
735
+ };
736
+ };
737
+
738
+ const liveModelEntry = {
739
+ capabilities: {
740
+ realtime: {
741
+ conversation: 'continuous',
742
+ transports: ['websocket', 'webrtc'],
743
+ connections: ['server-websocket', 'webrtc'],
744
+ },
745
+ },
746
+ requestConfig: {
747
+ realtime: {
748
+ api: 'live',
749
+ startup: 'session-start',
750
+ finalization: 'session-close',
751
+ },
752
+ },
753
+ } as const satisfies OpenAIRealtimeModelEntry;
754
+
755
+ const realtimeModelEntry = {
756
+ capabilities: {
757
+ realtime: {
758
+ conversation: 'turn-based',
759
+ transports: ['websocket'],
760
+ connections: ['client-secret-websocket'],
761
+ },
762
+ },
763
+ requestConfig: {
764
+ realtime: {
765
+ api: 'realtime',
766
+ startup: 'session-update',
767
+ finalization: 'transport-close',
768
+ },
769
+ },
770
+ } as const satisfies OpenAIRealtimeModelEntry;
771
+
772
+ const realtimeModelEntries = {
773
+ 'gpt-live-1': liveModelEntry,
774
+ 'gpt-realtime': realtimeModelEntry,
775
+ } as const;
776
+
777
+ export type OpenAIKnownLiveModelId = {
778
+ [ModelId in keyof typeof realtimeModelEntries]: (typeof realtimeModelEntries)[ModelId]['requestConfig']['realtime']['api'] extends 'live'
779
+ ? ModelId
780
+ : never;
781
+ }[keyof typeof realtimeModelEntries];
782
+
783
+ /** Keep the existing realtime interface's shape while deriving it from the catalog. */
784
+ export const openaiLiveRealtimeCapabilities = {
785
+ ...liveModelEntry.capabilities.realtime,
786
+ startup: liveModelEntry.requestConfig.realtime.startup,
787
+ finalization: liveModelEntry.requestConfig.realtime.finalization,
788
+ } as const;
789
+
790
+ /**
791
+ * The catalog consumed by request builders and documentation. Source declarations
792
+ * are grouped by operation; entries for the same model combine their sections.
793
+ * An absent section is undeclared, not an instruction to reject a request.
794
+ */
795
+ export const openaiModelCatalog: Readonly<Record<string, OpenAIModelEntry>> =
796
+ combineModelEntries([
797
+ openaiLanguageModelEntries,
798
+ openaiEmbeddingModelEntries,
799
+ openaiImageModelEntries,
800
+ openaiTranscriptionModelEntries,
801
+ openaiSpeechTranslationModelEntries,
802
+ openaiSpeechModelEntries,
803
+ realtimeModelEntries,
804
+ ]);
805
+
806
+ function combineModelEntries(
807
+ catalogs: ReadonlyArray<Readonly<Record<string, OpenAIModelEntry>>>,
808
+ ): Record<string, OpenAIModelEntry> {
809
+ const entries = new Map<string, OpenAIModelEntry>();
810
+ for (const catalog of catalogs) {
811
+ for (const [modelId, entry] of Object.entries(catalog)) {
812
+ const previous = entries.get(modelId);
813
+ entries.set(modelId, {
814
+ capabilities: { ...previous?.capabilities, ...entry.capabilities },
815
+ ...(previous?.providerOptions != null || entry.providerOptions != null
816
+ ? {
817
+ providerOptions: {
818
+ ...previous?.providerOptions,
819
+ ...entry.providerOptions,
820
+ },
821
+ }
822
+ : {}),
823
+ ...(previous?.requestConfig != null || entry.requestConfig != null
824
+ ? {
825
+ requestConfig: {
826
+ ...previous?.requestConfig,
827
+ ...entry.requestConfig,
828
+ },
829
+ }
830
+ : {}),
831
+ });
832
+ }
833
+ }
834
+ return Object.fromEntries(entries);
835
+ }
836
+
837
+ function findModelEntry(modelId: string): OpenAIModelEntry | undefined {
838
+ return Object.prototype.hasOwnProperty.call(openaiModelCatalog, modelId)
839
+ ? openaiModelCatalog[modelId]
840
+ : undefined;
841
+ }
842
+
843
+ export function getOpenAILanguageModelEntry(
844
+ modelId: string,
845
+ ): OpenAILanguageModelEntry {
846
+ const entry = findModelEntry(modelId);
847
+ return entry?.capabilities.language != null &&
848
+ entry.providerOptions?.language != null &&
849
+ entry.requestConfig?.language != null
850
+ ? {
851
+ capabilities: { language: entry.capabilities.language },
852
+ providerOptions: { language: entry.providerOptions.language },
853
+ requestConfig: { language: entry.requestConfig.language },
854
+ }
855
+ : createOpenAILanguageModelEntry(modelId);
856
+ }
857
+
858
+ export function getOpenAIImageModelEntry(
859
+ modelId: string,
860
+ ): OpenAIImageModelEntry {
861
+ const entry = findModelEntry(modelId);
862
+ return entry?.capabilities.image != null && entry.requestConfig?.image != null
863
+ ? {
864
+ capabilities: { image: entry.capabilities.image },
865
+ requestConfig: { image: entry.requestConfig.image },
866
+ }
867
+ : resolveOpenAIImageModelEntry(modelId);
868
+ }
869
+
870
+ export function getOpenAITranscriptionModelEntry(
871
+ modelId: string,
872
+ ): OpenAITranscriptionModelEntry {
873
+ const entry = findModelEntry(modelId);
874
+ return entry?.capabilities.transcription != null &&
875
+ entry.requestConfig?.transcription != null
876
+ ? {
877
+ capabilities: { transcription: entry.capabilities.transcription },
878
+ requestConfig: { transcription: entry.requestConfig.transcription },
879
+ }
880
+ : resolveOpenAITranscriptionModelEntry(modelId);
881
+ }
882
+
883
+ export function getOpenAIRealtimeModelEntry(
884
+ modelId: string,
885
+ api?: 'live' | 'realtime',
886
+ ): OpenAIRealtimeModelEntry {
887
+ const entry = findModelEntry(modelId);
888
+ if (
889
+ entry?.capabilities.realtime != null &&
890
+ entry.requestConfig?.realtime != null &&
891
+ (api == null || entry.requestConfig.realtime.api === api)
892
+ ) {
893
+ return {
894
+ capabilities: { realtime: entry.capabilities.realtime },
895
+ requestConfig: { realtime: entry.requestConfig.realtime },
896
+ };
897
+ }
898
+ return api === 'live' ? liveModelEntry : realtimeModelEntry;
899
+ }
900
+
901
+ export type OpenAILanguageModelCapabilities = {
902
+ isReasoningModel: boolean;
903
+ systemMessageMode: 'remove' | 'system' | 'developer';
904
+ supportsFlexProcessing: boolean;
905
+ supportsPriorityProcessing: boolean;
906
+ supportsConfigurationUpdate: boolean;
907
+ supportsAsyncToolCalling: boolean;
908
+ supportedReasoningEfforts: readonly string[] | undefined;
909
+ supportsChatFunctionToolsWithReasoning: boolean;
910
+ supportsNonReasoningParameters: boolean;
911
+ };
912
+
913
+ /** Compatibility projection for callers that still consume the legacy shape. */
914
+ export function getOpenAILanguageModelCapabilities(
915
+ modelId: string,
916
+ ): OpenAILanguageModelCapabilities {
917
+ const {
918
+ capabilities: { language: capabilities },
919
+ requestConfig: { language: requestConfig },
920
+ } = getOpenAILanguageModelEntry(modelId);
921
+
922
+ return {
923
+ isReasoningModel: capabilities.reasoning.support === true,
924
+ systemMessageMode: requestConfig.systemMessageMode,
925
+ supportsFlexProcessing: capabilities.serviceTiers.flex,
926
+ supportsPriorityProcessing: capabilities.serviceTiers.priority,
927
+ supportsConfigurationUpdate: capabilities.configurationUpdates,
928
+ supportsAsyncToolCalling: capabilities.tools.async,
929
+ supportedReasoningEfforts: capabilities.reasoning.efforts,
930
+ supportsChatFunctionToolsWithReasoning:
931
+ capabilities.endpoints.chat.functionToolsWithReasoning,
932
+ supportsNonReasoningParameters:
933
+ capabilities.reasoning.sampling === 'reasoning-effort-none',
934
+ };
935
+ }