@ai-sdk/openai 4.0.32 → 4.0.34

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.
@@ -226,7 +226,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
226
226
  return this.config.provider;
227
227
  }
228
228
 
229
- private async getArgs({
229
+ protected async getArgs({
230
230
  maxOutputTokens,
231
231
  temperature,
232
232
  stopSequences,
@@ -1380,6 +1380,20 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1380
1380
  }
1381
1381
  > = {};
1382
1382
 
1383
+ // OpenAI-compatible providers can rotate opaque item ids between events.
1384
+ // output_index remains stable for the lifetime of an output item.
1385
+ const activeOutputItemIds: Record<number, string | undefined> = {};
1386
+ const resolveOutputItemId = ({
1387
+ itemId,
1388
+ outputIndex,
1389
+ }: {
1390
+ itemId: string;
1391
+ outputIndex?: number | null;
1392
+ }) =>
1393
+ outputIndex == null
1394
+ ? itemId
1395
+ : (activeOutputItemIds[outputIndex] ?? itemId);
1396
+
1383
1397
  let serviceTier: string | undefined;
1384
1398
  let reasoningContext: ResponsesProviderMetadata['reasoningContext'];
1385
1399
  const hostedToolSearchCallIds: string[] = [];
@@ -1608,6 +1622,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1608
1622
  } else if (value.item.type === 'shell_call_output') {
1609
1623
  // shell_call_output is handled in output_item.done
1610
1624
  } else if (value.item.type === 'message') {
1625
+ activeOutputItemIds[value.output_index] = value.item.id;
1611
1626
  ongoingAnnotations.splice(0, ongoingAnnotations.length);
1612
1627
  activeMessagePhase = value.item.phase ?? undefined;
1613
1628
  controller.enqueue({
@@ -1626,6 +1641,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1626
1641
  isResponseOutputItemAddedChunk(value) &&
1627
1642
  value.item.type === 'reasoning'
1628
1643
  ) {
1644
+ activeOutputItemIds[value.output_index] = value.item.id;
1629
1645
  activeReasoning[value.item.id] = {
1630
1646
  encryptedContent: value.item.encrypted_content,
1631
1647
  summaryParts: { 0: 'active' },
@@ -1645,14 +1661,18 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1645
1661
  }
1646
1662
  } else if (isResponseOutputItemDoneChunk(value)) {
1647
1663
  if (value.item.type === 'message') {
1664
+ const itemId = resolveOutputItemId({
1665
+ itemId: value.item.id,
1666
+ outputIndex: value.output_index,
1667
+ });
1648
1668
  const phase = value.item.phase ?? activeMessagePhase;
1649
1669
  activeMessagePhase = undefined;
1650
1670
  controller.enqueue({
1651
1671
  type: 'text-end',
1652
- id: value.item.id,
1672
+ id: itemId,
1653
1673
  providerMetadata: {
1654
1674
  [providerOptionsName]: {
1655
- itemId: value.item.id,
1675
+ itemId,
1656
1676
  ...(phase != null && { phase }),
1657
1677
  ...(ongoingAnnotations.length > 0 && {
1658
1678
  annotations: ongoingAnnotations,
@@ -1660,6 +1680,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1660
1680
  } satisfies ResponsesTextProviderMetadata,
1661
1681
  },
1662
1682
  });
1683
+ activeOutputItemIds[value.output_index] = undefined;
1663
1684
  } else if (value.item.type === 'function_call') {
1664
1685
  ongoingToolCalls[value.output_index] = undefined;
1665
1686
  hasFunctionCall = true;
@@ -2128,34 +2149,41 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2128
2149
  } satisfies InferSchema<typeof shellOutputSchema>,
2129
2150
  });
2130
2151
  } else if (value.item.type === 'reasoning') {
2131
- const activeReasoningPart = activeReasoning[value.item.id];
2132
-
2133
- // get all active or can-conclude summary parts' ids
2134
- // to conclude ongoing reasoning parts:
2135
- const summaryPartIndices = Object.entries(
2136
- activeReasoningPart.summaryParts,
2137
- )
2138
- .filter(
2139
- ([_, status]) =>
2140
- status === 'active' || status === 'can-conclude',
2152
+ const itemId = resolveOutputItemId({
2153
+ itemId: value.item.id,
2154
+ outputIndex: value.output_index,
2155
+ });
2156
+ const activeReasoningPart = activeReasoning[itemId];
2157
+
2158
+ if (activeReasoningPart != null) {
2159
+ // get all active or can-conclude summary parts' ids
2160
+ // to conclude ongoing reasoning parts:
2161
+ const summaryPartIndices = Object.entries(
2162
+ activeReasoningPart.summaryParts,
2141
2163
  )
2142
- .map(([summaryIndex]) => summaryIndex);
2164
+ .filter(
2165
+ ([_, status]) =>
2166
+ status === 'active' || status === 'can-conclude',
2167
+ )
2168
+ .map(([summaryIndex]) => summaryIndex);
2143
2169
 
2144
- for (const summaryIndex of summaryPartIndices) {
2145
- controller.enqueue({
2146
- type: 'reasoning-end',
2147
- id: `${value.item.id}:${summaryIndex}`,
2148
- providerMetadata: {
2149
- [providerOptionsName]: {
2150
- itemId: value.item.id,
2151
- reasoningEncryptedContent:
2152
- value.item.encrypted_content ?? null,
2153
- } satisfies ResponsesReasoningProviderMetadata,
2154
- },
2155
- });
2156
- }
2170
+ for (const summaryIndex of summaryPartIndices) {
2171
+ controller.enqueue({
2172
+ type: 'reasoning-end',
2173
+ id: `${itemId}:${summaryIndex}`,
2174
+ providerMetadata: {
2175
+ [providerOptionsName]: {
2176
+ itemId,
2177
+ reasoningEncryptedContent:
2178
+ value.item.encrypted_content ?? null,
2179
+ } satisfies ResponsesReasoningProviderMetadata,
2180
+ },
2181
+ });
2182
+ }
2157
2183
 
2158
- delete activeReasoning[value.item.id];
2184
+ delete activeReasoning[itemId];
2185
+ }
2186
+ activeOutputItemIds[value.output_index] = undefined;
2159
2187
  } else if (value.item.type === 'compaction') {
2160
2188
  controller.enqueue({
2161
2189
  type: 'custom',
@@ -2285,9 +2313,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2285
2313
  modelId: value.response.model,
2286
2314
  });
2287
2315
  } else if (isTextDeltaChunk(value)) {
2316
+ const itemId = resolveOutputItemId({
2317
+ itemId: value.item_id,
2318
+ outputIndex: value.output_index,
2319
+ });
2288
2320
  controller.enqueue({
2289
2321
  type: 'text-delta',
2290
- id: value.item_id,
2322
+ id: itemId,
2291
2323
  delta: value.delta,
2292
2324
  });
2293
2325
 
@@ -2298,83 +2330,98 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2298
2330
  logprobs.push(value.logprobs);
2299
2331
  }
2300
2332
  } else if (value.type === 'response.reasoning_summary_part.added') {
2333
+ const itemId = resolveOutputItemId({
2334
+ itemId: value.item_id,
2335
+ outputIndex: value.output_index,
2336
+ });
2301
2337
  // the first reasoning start is pushed in isResponseOutputItemAddedReasoningChunk
2302
2338
  if (value.summary_index > 0) {
2303
- const activeReasoningPart = activeReasoning[value.item_id]!;
2304
-
2305
- activeReasoningPart.summaryParts[value.summary_index] =
2306
- 'active';
2307
-
2308
- // since there is a new active summary part, we can conclude all can-conclude summary parts
2309
- for (const summaryIndex of Object.keys(
2310
- activeReasoningPart.summaryParts,
2311
- )) {
2312
- if (
2313
- activeReasoningPart.summaryParts[summaryIndex] ===
2314
- 'can-conclude'
2315
- ) {
2316
- controller.enqueue({
2317
- type: 'reasoning-end',
2318
- id: `${value.item_id}:${summaryIndex}`,
2319
- providerMetadata: {
2320
- [providerOptionsName]: {
2321
- itemId: value.item_id,
2322
- } satisfies ResponsesReasoningProviderMetadata,
2323
- },
2324
- });
2325
- activeReasoningPart.summaryParts[summaryIndex] =
2326
- 'concluded';
2339
+ const activeReasoningPart = activeReasoning[itemId];
2340
+
2341
+ if (activeReasoningPart != null) {
2342
+ activeReasoningPart.summaryParts[value.summary_index] =
2343
+ 'active';
2344
+
2345
+ // since there is a new active summary part, we can conclude all can-conclude summary parts
2346
+ for (const summaryIndex of Object.keys(
2347
+ activeReasoningPart.summaryParts,
2348
+ )) {
2349
+ if (
2350
+ activeReasoningPart.summaryParts[summaryIndex] ===
2351
+ 'can-conclude'
2352
+ ) {
2353
+ controller.enqueue({
2354
+ type: 'reasoning-end',
2355
+ id: `${itemId}:${summaryIndex}`,
2356
+ providerMetadata: {
2357
+ [providerOptionsName]: {
2358
+ itemId,
2359
+ } satisfies ResponsesReasoningProviderMetadata,
2360
+ },
2361
+ });
2362
+ activeReasoningPart.summaryParts[summaryIndex] =
2363
+ 'concluded';
2364
+ }
2327
2365
  }
2328
- }
2329
2366
 
2330
- controller.enqueue({
2331
- type: 'reasoning-start',
2332
- id: `${value.item_id}:${value.summary_index}`,
2333
- providerMetadata: {
2334
- [providerOptionsName]: {
2335
- itemId: value.item_id,
2336
- reasoningEncryptedContent:
2337
- activeReasoning[value.item_id]?.encryptedContent ??
2338
- null,
2339
- } satisfies ResponsesReasoningProviderMetadata,
2340
- },
2341
- });
2367
+ controller.enqueue({
2368
+ type: 'reasoning-start',
2369
+ id: `${itemId}:${value.summary_index}`,
2370
+ providerMetadata: {
2371
+ [providerOptionsName]: {
2372
+ itemId,
2373
+ reasoningEncryptedContent:
2374
+ activeReasoningPart.encryptedContent ?? null,
2375
+ } satisfies ResponsesReasoningProviderMetadata,
2376
+ },
2377
+ });
2378
+ }
2342
2379
  }
2343
2380
  } else if (value.type === 'response.reasoning_summary_text.delta') {
2381
+ const itemId = resolveOutputItemId({
2382
+ itemId: value.item_id,
2383
+ outputIndex: value.output_index,
2384
+ });
2344
2385
  controller.enqueue({
2345
2386
  type: 'reasoning-delta',
2346
- id: `${value.item_id}:${value.summary_index}`,
2387
+ id: `${itemId}:${value.summary_index}`,
2347
2388
  delta: value.delta,
2348
2389
  providerMetadata: {
2349
2390
  [providerOptionsName]: {
2350
- itemId: value.item_id,
2391
+ itemId,
2351
2392
  } satisfies ResponsesReasoningProviderMetadata,
2352
2393
  },
2353
2394
  });
2354
2395
  } else if (value.type === 'response.reasoning_summary_part.done') {
2355
- // when OpenAI stores the message data, we can immediately conclude the reasoning part
2356
- // since we do not need to send the encrypted content.
2357
- if (store) {
2358
- controller.enqueue({
2359
- type: 'reasoning-end',
2360
- id: `${value.item_id}:${value.summary_index}`,
2361
- providerMetadata: {
2362
- [providerOptionsName]: {
2363
- itemId: value.item_id,
2364
- } satisfies ResponsesReasoningProviderMetadata,
2365
- },
2366
- });
2396
+ const itemId = resolveOutputItemId({
2397
+ itemId: value.item_id,
2398
+ outputIndex: value.output_index,
2399
+ });
2400
+ const activeReasoningPart = activeReasoning[itemId];
2367
2401
 
2368
- // mark the summary part as concluded
2369
- activeReasoning[value.item_id]!.summaryParts[
2370
- value.summary_index
2371
- ] = 'concluded';
2372
- } else {
2373
- // mark the summary part as can-conclude only
2374
- // because we need to have a final summary part with the encrypted content
2375
- activeReasoning[value.item_id]!.summaryParts[
2376
- value.summary_index
2377
- ] = 'can-conclude';
2402
+ if (activeReasoningPart != null) {
2403
+ // when OpenAI stores the message data, we can immediately conclude the reasoning part
2404
+ // since we do not need to send the encrypted content.
2405
+ if (store) {
2406
+ controller.enqueue({
2407
+ type: 'reasoning-end',
2408
+ id: `${itemId}:${value.summary_index}`,
2409
+ providerMetadata: {
2410
+ [providerOptionsName]: {
2411
+ itemId,
2412
+ } satisfies ResponsesReasoningProviderMetadata,
2413
+ },
2414
+ });
2415
+
2416
+ // mark the summary part as concluded
2417
+ activeReasoningPart.summaryParts[value.summary_index] =
2418
+ 'concluded';
2419
+ } else {
2420
+ // mark the summary part as can-conclude only
2421
+ // because we need to have a final summary part with the encrypted content
2422
+ activeReasoningPart.summaryParts[value.summary_index] =
2423
+ 'can-conclude';
2424
+ }
2378
2425
  }
2379
2426
  } else if (isResponseFinishedChunk(value)) {
2380
2427
  finishReason = {
@@ -0,0 +1,18 @@
1
+ import {
2
+ lazySchema,
3
+ zodSchema,
4
+ type InferSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+
8
+ export type OpenAISpeechTranslationModelId =
9
+ | 'gpt-realtime-translate'
10
+ | (string & {});
11
+
12
+ export const openAISpeechTranslationModelOptions = lazySchema(() =>
13
+ zodSchema(z.object({})),
14
+ );
15
+
16
+ export type OpenAISpeechTranslationModelOptions = InferSchema<
17
+ typeof openAISpeechTranslationModelOptions
18
+ >;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  InvalidArgumentError,
3
- type Experimental_SpeechTranslationModelV4 as TranslationModelV4,
3
+ type Experimental_SpeechTranslationModelV4 as SpeechTranslationModelV4,
4
4
  type Experimental_SpeechTranslationModelV4StreamOptions as SpeechTranslationModelV4StreamOptions,
5
5
  type Experimental_SpeechTranslationModelV4StreamPart as SpeechTranslationModelV4StreamPart,
6
6
  type SharedV4Warning,
@@ -21,26 +21,26 @@ import {
21
21
  } from '@ai-sdk/provider-utils';
22
22
  import type { OpenAIConfig } from '../openai-config';
23
23
  import {
24
- openAITranslationModelOptions,
25
- type OpenAITranslationModelId,
26
- } from './openai-translation-model-options';
24
+ openAISpeechTranslationModelOptions,
25
+ type OpenAISpeechTranslationModelId,
26
+ } from './openai-speech-translation-model-options';
27
27
 
28
- type OpenAIRealtimeTranslationEvent = {
28
+ type OpenAIRealtimeSpeechTranslationEvent = {
29
29
  type?: string;
30
30
  delta?: string;
31
31
  error?: { message?: string };
32
32
  };
33
33
 
34
- interface OpenAITranslationModelConfig extends OpenAIConfig {
34
+ interface OpenAISpeechTranslationModelConfig extends OpenAIConfig {
35
35
  _internal?: {
36
36
  currentDate?: () => Date;
37
37
  };
38
38
  }
39
39
 
40
- export class OpenAITranslationModel implements TranslationModelV4 {
40
+ export class OpenAISpeechTranslationModel implements SpeechTranslationModelV4 {
41
41
  readonly specificationVersion = 'v4';
42
42
 
43
- static [WORKFLOW_SERIALIZE](model: OpenAITranslationModel) {
43
+ static [WORKFLOW_SERIALIZE](model: OpenAISpeechTranslationModel) {
44
44
  return serializeModelOptions({
45
45
  modelId: model.modelId,
46
46
  config: model.config,
@@ -48,10 +48,10 @@ export class OpenAITranslationModel implements TranslationModelV4 {
48
48
  }
49
49
 
50
50
  static [WORKFLOW_DESERIALIZE](options: {
51
- modelId: OpenAITranslationModelId;
52
- config: OpenAITranslationModelConfig;
51
+ modelId: OpenAISpeechTranslationModelId;
52
+ config: OpenAISpeechTranslationModelConfig;
53
53
  }) {
54
- return new OpenAITranslationModel(options.modelId, options.config);
54
+ return new OpenAISpeechTranslationModel(options.modelId, options.config);
55
55
  }
56
56
 
57
57
  get provider(): string {
@@ -59,13 +59,13 @@ export class OpenAITranslationModel implements TranslationModelV4 {
59
59
  }
60
60
 
61
61
  constructor(
62
- readonly modelId: OpenAITranslationModelId,
63
- private readonly config: OpenAITranslationModelConfig,
62
+ readonly modelId: OpenAISpeechTranslationModelId,
63
+ private readonly config: OpenAISpeechTranslationModelConfig,
64
64
  ) {}
65
65
 
66
66
  async doStream(
67
67
  options: SpeechTranslationModelV4StreamOptions,
68
- ): Promise<Awaited<ReturnType<TranslationModelV4['doStream']>>> {
68
+ ): Promise<Awaited<ReturnType<SpeechTranslationModelV4['doStream']>>> {
69
69
  if (options.targetLanguage == null) {
70
70
  throw new InvalidArgumentError({
71
71
  argument: 'targetLanguage',
@@ -77,11 +77,11 @@ export class OpenAITranslationModel implements TranslationModelV4 {
77
77
  await parseProviderOptions({
78
78
  provider: 'openai',
79
79
  providerOptions: options.providerOptions,
80
- schema: openAITranslationModelOptions,
80
+ schema: openAISpeechTranslationModelOptions,
81
81
  });
82
82
  const warnings: SharedV4Warning[] = [];
83
83
 
84
- validateOpenAITranslationInputAudioFormat(options.inputAudioFormat);
84
+ validateOpenAISpeechTranslationInputAudioFormat(options.inputAudioFormat);
85
85
 
86
86
  if (options.sourceLanguage != null) {
87
87
  warnings.push({
@@ -102,7 +102,7 @@ export class OpenAITranslationModel implements TranslationModelV4 {
102
102
  }
103
103
 
104
104
  const headers = combineHeaders(this.config.headers?.(), options.headers);
105
- const sessionUpdate = buildOpenAIRealtimeTranslationSession({
105
+ const sessionUpdate = buildOpenAIRealtimeSpeechTranslationSession({
106
106
  targetLanguage: options.targetLanguage,
107
107
  });
108
108
 
@@ -112,7 +112,7 @@ export class OpenAITranslationModel implements TranslationModelV4 {
112
112
  timestamp: currentDate,
113
113
  modelId: this.modelId,
114
114
  },
115
- stream: createOpenAIRealtimeTranslationStream({
115
+ stream: createOpenAIRealtimeSpeechTranslationStream({
116
116
  webSocket: this.config.webSocket,
117
117
  url: toWebSocketUrl(
118
118
  this.config.url({
@@ -131,7 +131,7 @@ export class OpenAITranslationModel implements TranslationModelV4 {
131
131
  }
132
132
  }
133
133
 
134
- function createOpenAIRealtimeTranslationStream({
134
+ function createOpenAIRealtimeSpeechTranslationStream({
135
135
  webSocket,
136
136
  url,
137
137
  headers,
@@ -249,7 +249,7 @@ function createOpenAIRealtimeTranslationStream({
249
249
  if (finished) return;
250
250
  const parsed = await safeParseJSON({ text });
251
251
  if (!parsed.success) return;
252
- const raw = parsed.value as OpenAIRealtimeTranslationEvent;
252
+ const raw = parsed.value as OpenAIRealtimeSpeechTranslationEvent;
253
253
 
254
254
  if (includeRawChunks) {
255
255
  controller.enqueue({ type: 'raw', rawValue: raw });
@@ -324,7 +324,7 @@ function createOpenAIRealtimeTranslationStream({
324
324
  });
325
325
  }
326
326
 
327
- function buildOpenAIRealtimeTranslationSession({
327
+ function buildOpenAIRealtimeSpeechTranslationSession({
328
328
  targetLanguage,
329
329
  }: {
330
330
  targetLanguage: string;
@@ -347,7 +347,7 @@ function buildOpenAIRealtimeTranslationSession({
347
347
  };
348
348
  }
349
349
 
350
- function validateOpenAITranslationInputAudioFormat(
350
+ function validateOpenAISpeechTranslationInputAudioFormat(
351
351
  inputAudioFormat: SpeechTranslationModelV4StreamOptions['inputAudioFormat'],
352
352
  ) {
353
353
  if (
@@ -1,16 +0,0 @@
1
- import {
2
- lazySchema,
3
- zodSchema,
4
- type InferSchema,
5
- } from '@ai-sdk/provider-utils';
6
- import { z } from 'zod/v4';
7
-
8
- export type OpenAITranslationModelId = 'gpt-realtime-translate' | (string & {});
9
-
10
- export const openAITranslationModelOptions = lazySchema(() =>
11
- zodSchema(z.object({})),
12
- );
13
-
14
- export type OpenAITranslationModelOptions = InferSchema<
15
- typeof openAITranslationModelOptions
16
- >;