@ai-sdk/openai 4.0.91 → 4.0.93

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.
@@ -71,6 +71,10 @@ import {
71
71
  expandParallelToolCall,
72
72
  isUndeclaredParallelToolCall,
73
73
  } from './expand-parallel-tool-call';
74
+ import {
75
+ mapOpenAIResponsesAnnotationSource,
76
+ mapOpenAIResponsesCitations,
77
+ } from './map-openai-responses-annotation';
74
78
  import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-reason';
75
79
  import {
76
80
  openaiResponsesChunkSchema,
@@ -95,7 +99,6 @@ import type {
95
99
  ResponsesCompactionProviderMetadata,
96
100
  ResponsesProviderMetadata,
97
101
  ResponsesReasoningProviderMetadata,
98
- ResponsesSourceDocumentProviderMetadata,
99
102
  ResponsesTextProviderMetadata,
100
103
  ResponsesToolCallProviderMetadata,
101
104
  } from './openai-responses-provider-metadata';
@@ -434,6 +437,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
434
437
  passThroughUnsupportedFiles:
435
438
  openaiOptions?.passThroughUnsupportedFiles ?? false,
436
439
  store: openaiOptions?.store ?? true,
440
+ isReasoningModel,
437
441
  hasConversation: openaiOptions?.conversation != null,
438
442
  hasPreviousResponseId: openaiOptions?.previousResponseId != null,
439
443
  hasLocalShellTool: hasOpenAITool('openai.local_shell'),
@@ -905,6 +909,18 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
905
909
  options.tools?.filter(
906
910
  (tool): tool is LanguageModelV4FunctionTool => tool.type === 'function',
907
911
  ) ?? [];
912
+ const hasWebSearchActionSources = response.output.some(
913
+ part =>
914
+ part.type === 'web_search_call' &&
915
+ part.action?.type === 'search' &&
916
+ part.action.sources?.some(source => source.type === 'url'),
917
+ );
918
+
919
+ const hasFileSearchResults = response.output.some(
920
+ part =>
921
+ part.type === 'file_search_call' && (part.results?.length ?? 0) > 0,
922
+ );
923
+ const retrievedFileIds = new Set<string>();
908
924
 
909
925
  // flag that checks if there have been client-side tool calls (not executed by openai)
910
926
  let hasFunctionCall = false;
@@ -1084,78 +1100,35 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1084
1100
  content.push({
1085
1101
  type: 'text',
1086
1102
  text: contentPart.text,
1103
+ ...(contentPart.annotations.some(
1104
+ annotation => annotation.type !== 'file_path',
1105
+ ) && {
1106
+ citations: mapOpenAIResponsesCitations(
1107
+ contentPart.annotations,
1108
+ providerOptionsName,
1109
+ ),
1110
+ }),
1087
1111
  providerMetadata: {
1088
1112
  [providerOptionsName]: providerMetadata,
1089
1113
  },
1090
1114
  });
1091
1115
 
1092
1116
  for (const annotation of contentPart.annotations) {
1093
- if (annotation.type === 'url_citation') {
1094
- content.push({
1095
- type: 'source',
1096
- sourceType: 'url',
1097
- id: this.config.generateId?.() ?? generateId(),
1098
- url: annotation.url,
1099
- title: annotation.title,
1100
- });
1101
- } else if (annotation.type === 'file_citation') {
1102
- content.push({
1103
- type: 'source',
1104
- sourceType: 'document',
1105
- id: this.config.generateId?.() ?? generateId(),
1106
- mediaType: 'text/plain',
1107
- title: annotation.filename,
1108
- filename: annotation.filename,
1109
- providerMetadata: {
1110
- [providerOptionsName]: {
1111
- type: annotation.type,
1112
- fileId: annotation.file_id,
1113
- index: annotation.index,
1114
- } satisfies Extract<
1115
- ResponsesSourceDocumentProviderMetadata,
1116
- { type: 'file_citation' }
1117
- >,
1118
- },
1119
- });
1120
- } else if (annotation.type === 'container_file_citation') {
1121
- content.push({
1122
- type: 'source',
1123
- sourceType: 'document',
1124
- id: this.config.generateId?.() ?? generateId(),
1125
- mediaType: 'text/plain',
1126
- title: annotation.filename,
1127
- filename: annotation.filename,
1128
- providerMetadata: {
1129
- [providerOptionsName]: {
1130
- type: annotation.type,
1131
- fileId: annotation.file_id,
1132
- containerId: annotation.container_id,
1133
- } satisfies Extract<
1134
- ResponsesSourceDocumentProviderMetadata,
1135
- { type: 'container_file_citation' }
1136
- >,
1137
- },
1138
- });
1139
- } else if (annotation.type === 'file_path') {
1140
- content.push({
1141
- type: 'source',
1142
- sourceType: 'document',
1143
- id: this.config.generateId?.() ?? generateId(),
1144
- mediaType: 'application/octet-stream',
1145
- title: annotation.file_id,
1146
- filename: annotation.file_id,
1147
- providerMetadata: {
1148
- [providerOptionsName]: {
1149
- type: annotation.type,
1150
- fileId: annotation.file_id,
1151
- index: annotation.index,
1152
- } satisfies Extract<
1153
- ResponsesSourceDocumentProviderMetadata,
1154
- { type: 'file_path' }
1155
- >,
1156
- },
1157
- });
1117
+ // Use citation-derived sources only when retrieval data is unavailable.
1118
+ if (
1119
+ (annotation.type === 'url_citation' &&
1120
+ hasWebSearchActionSources) ||
1121
+ (annotation.type === 'file_citation' && hasFileSearchResults)
1122
+ ) {
1123
+ continue;
1158
1124
  }
1125
+ content.push(
1126
+ mapOpenAIResponsesAnnotationSource(
1127
+ annotation,
1128
+ providerOptionsName,
1129
+ this.config.generateId?.() ?? generateId(),
1130
+ ),
1131
+ );
1159
1132
  }
1160
1133
  }
1161
1134
 
@@ -1288,6 +1261,19 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1288
1261
  : { result: mapWebSearchOutput(part.action) }),
1289
1262
  });
1290
1263
 
1264
+ if (part.action?.type === 'search') {
1265
+ for (const source of part.action.sources ?? []) {
1266
+ if (source.type === 'url') {
1267
+ content.push({
1268
+ type: 'source',
1269
+ sourceType: 'url',
1270
+ id: this.config.generateId?.() ?? generateId(),
1271
+ url: source.url,
1272
+ });
1273
+ }
1274
+ }
1275
+ }
1276
+
1291
1277
  break;
1292
1278
  }
1293
1279
 
@@ -1424,6 +1410,26 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1424
1410
  })) ?? null,
1425
1411
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
1426
1412
  });
1413
+ for (const result of part.results ?? []) {
1414
+ if (retrievedFileIds.has(result.file_id)) {
1415
+ continue;
1416
+ }
1417
+ retrievedFileIds.add(result.file_id);
1418
+ content.push({
1419
+ type: 'source',
1420
+ sourceType: 'document',
1421
+ id: this.config.generateId?.() ?? generateId(),
1422
+ mediaType: 'text/plain',
1423
+ title: result.filename,
1424
+ filename: result.filename,
1425
+ providerMetadata: {
1426
+ [providerOptionsName]: {
1427
+ type: 'file_search',
1428
+ fileId: result.file_id,
1429
+ },
1430
+ },
1431
+ });
1432
+ }
1427
1433
  break;
1428
1434
  }
1429
1435
 
@@ -1617,12 +1623,16 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1617
1623
  > = {};
1618
1624
 
1619
1625
  // set annotations in 'text-end' part providerMetadata.
1620
- const ongoingAnnotations: Array<
1626
+ let ongoingAnnotations: Array<
1621
1627
  Extract<
1622
1628
  OpenAIResponsesChunk,
1623
1629
  { type: 'response.output_text.annotation.added' }
1624
1630
  >['annotation']
1625
1631
  > = [];
1632
+ let hasWebSearchActionSources = false;
1633
+ let hasFileSearchResults = false;
1634
+ const retrievedFileIds = new Set<string>();
1635
+ const fallbackAnnotations: typeof ongoingAnnotations = [];
1626
1636
 
1627
1637
  // track the phase of the current message being streamed
1628
1638
  let activeMessagePhase: 'commentary' | 'final_answer' | undefined;
@@ -1730,6 +1740,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1730
1740
  id: value.item.call_id,
1731
1741
  toolName,
1732
1742
  });
1743
+
1744
+ // Custom tool input is a string, so stream its JSON encoding
1745
+ // consistently with the final tool call input.
1746
+ controller.enqueue({
1747
+ type: 'tool-input-delta',
1748
+ id: value.item.call_id,
1749
+ delta: '"',
1750
+ });
1733
1751
  } else if (value.item.type === 'web_search_call') {
1734
1752
  ongoingToolCalls[value.output_index] = {
1735
1753
  toolName: toolNameMapping.toCustomToolName(
@@ -1894,7 +1912,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1894
1912
  // shell_call_output is handled in output_item.done
1895
1913
  } else if (value.item.type === 'message') {
1896
1914
  activeOutputItemIds[value.output_index] = value.item.id;
1897
- ongoingAnnotations.splice(0);
1915
+ ongoingAnnotations = [];
1898
1916
  activeMessagePhase = value.item.phase ?? undefined;
1899
1917
  controller.enqueue({
1900
1918
  type: 'text-start',
@@ -1941,6 +1959,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1941
1959
  controller.enqueue({
1942
1960
  type: 'text-end',
1943
1961
  id: itemId,
1962
+ ...(ongoingAnnotations.some(
1963
+ annotation => annotation.type !== 'file_path',
1964
+ ) && {
1965
+ citations: mapOpenAIResponsesCitations(
1966
+ ongoingAnnotations,
1967
+ providerOptionsName,
1968
+ ),
1969
+ }),
1944
1970
  providerMetadata: {
1945
1971
  [providerOptionsName]: {
1946
1972
  itemId,
@@ -2120,6 +2146,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2120
2146
  value.item.name,
2121
2147
  );
2122
2148
 
2149
+ controller.enqueue({
2150
+ type: 'tool-input-delta',
2151
+ id: value.item.call_id,
2152
+ delta: '"',
2153
+ });
2154
+
2123
2155
  controller.enqueue({
2124
2156
  type: 'tool-input-end',
2125
2157
  id: value.item.call_id,
@@ -2155,6 +2187,20 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2155
2187
  ? { isError: true, result: { status: value.item.status } }
2156
2188
  : { result: mapWebSearchOutput(value.item.action) }),
2157
2189
  });
2190
+
2191
+ if (value.item.action?.type === 'search') {
2192
+ for (const source of value.item.action.sources ?? []) {
2193
+ if (source.type === 'url') {
2194
+ hasWebSearchActionSources = true;
2195
+ controller.enqueue({
2196
+ type: 'source',
2197
+ sourceType: 'url',
2198
+ id: self.config.generateId?.() ?? generateId(),
2199
+ url: source.url,
2200
+ });
2201
+ }
2202
+ }
2203
+ }
2158
2204
  } else if (value.item.type === 'computer_call') {
2159
2205
  ongoingToolCalls[value.output_index] = undefined;
2160
2206
 
@@ -2227,6 +2273,27 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2227
2273
  })) ?? null,
2228
2274
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
2229
2275
  });
2276
+ for (const result of value.item.results ?? []) {
2277
+ hasFileSearchResults = true;
2278
+ if (retrievedFileIds.has(result.file_id)) {
2279
+ continue;
2280
+ }
2281
+ retrievedFileIds.add(result.file_id);
2282
+ controller.enqueue({
2283
+ type: 'source',
2284
+ sourceType: 'document',
2285
+ id: self.config.generateId?.() ?? generateId(),
2286
+ mediaType: 'text/plain',
2287
+ title: result.filename,
2288
+ filename: result.filename,
2289
+ providerMetadata: {
2290
+ [providerOptionsName]: {
2291
+ type: 'file_search',
2292
+ fileId: result.file_id,
2293
+ },
2294
+ },
2295
+ });
2296
+ }
2230
2297
  } else if (value.item.type === 'code_interpreter_call') {
2231
2298
  ongoingToolCalls[value.output_index] = undefined;
2232
2299
 
@@ -2582,7 +2649,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2582
2649
  controller.enqueue({
2583
2650
  type: 'tool-input-delta',
2584
2651
  id: toolCall.toolCallId,
2585
- delta: value.delta,
2652
+ delta: escapeJSONDelta(value.delta),
2586
2653
  });
2587
2654
  }
2588
2655
  } else if (isResponseApplyPatchCallOperationDiffDeltaChunk(value)) {
@@ -2843,71 +2910,21 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2843
2910
  }
2844
2911
  } else if (isResponseAnnotationAddedChunk(value)) {
2845
2912
  ongoingAnnotations.push(value.annotation);
2846
- if (value.annotation.type === 'url_citation') {
2847
- controller.enqueue({
2848
- type: 'source',
2849
- sourceType: 'url',
2850
- id: self.config.generateId?.() ?? generateId(),
2851
- url: value.annotation.url,
2852
- title: value.annotation.title,
2853
- });
2854
- } else if (value.annotation.type === 'file_citation') {
2855
- controller.enqueue({
2856
- type: 'source',
2857
- sourceType: 'document',
2858
- id: self.config.generateId?.() ?? generateId(),
2859
- mediaType: 'text/plain',
2860
- title: value.annotation.filename,
2861
- filename: value.annotation.filename,
2862
- providerMetadata: {
2863
- [providerOptionsName]: {
2864
- type: value.annotation.type,
2865
- fileId: value.annotation.file_id,
2866
- index: value.annotation.index,
2867
- } satisfies Extract<
2868
- ResponsesSourceDocumentProviderMetadata,
2869
- { type: 'file_citation' }
2870
- >,
2871
- },
2872
- });
2873
- } else if (value.annotation.type === 'container_file_citation') {
2874
- controller.enqueue({
2875
- type: 'source',
2876
- sourceType: 'document',
2877
- id: self.config.generateId?.() ?? generateId(),
2878
- mediaType: 'text/plain',
2879
- title: value.annotation.filename,
2880
- filename: value.annotation.filename,
2881
- providerMetadata: {
2882
- [providerOptionsName]: {
2883
- type: value.annotation.type,
2884
- fileId: value.annotation.file_id,
2885
- containerId: value.annotation.container_id,
2886
- } satisfies Extract<
2887
- ResponsesSourceDocumentProviderMetadata,
2888
- { type: 'container_file_citation' }
2889
- >,
2890
- },
2891
- });
2892
- } else if (value.annotation.type === 'file_path') {
2893
- controller.enqueue({
2894
- type: 'source',
2895
- sourceType: 'document',
2896
- id: self.config.generateId?.() ?? generateId(),
2897
- mediaType: 'application/octet-stream',
2898
- title: value.annotation.file_id,
2899
- filename: value.annotation.file_id,
2900
- providerMetadata: {
2901
- [providerOptionsName]: {
2902
- type: value.annotation.type,
2903
- fileId: value.annotation.file_id,
2904
- index: value.annotation.index,
2905
- } satisfies Extract<
2906
- ResponsesSourceDocumentProviderMetadata,
2907
- { type: 'file_path' }
2908
- >,
2909
- },
2910
- });
2913
+ if (
2914
+ value.annotation.type === 'url_citation' ||
2915
+ value.annotation.type === 'file_citation'
2916
+ ) {
2917
+ // Retrieval events may arrive after the annotation. Decide the
2918
+ // compatibility fallback once the complete source set is known.
2919
+ fallbackAnnotations.push(value.annotation);
2920
+ } else {
2921
+ controller.enqueue(
2922
+ mapOpenAIResponsesAnnotationSource(
2923
+ value.annotation,
2924
+ providerOptionsName,
2925
+ self.config.generateId?.() ?? generateId(),
2926
+ ),
2927
+ );
2911
2928
  }
2912
2929
  } else if (isErrorChunk(value)) {
2913
2930
  encounteredStreamError = true;
@@ -2920,6 +2937,23 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2920
2937
  },
2921
2938
 
2922
2939
  flush(controller) {
2940
+ for (const annotation of fallbackAnnotations) {
2941
+ if (
2942
+ (annotation.type === 'url_citation' &&
2943
+ hasWebSearchActionSources) ||
2944
+ (annotation.type === 'file_citation' && hasFileSearchResults)
2945
+ ) {
2946
+ continue;
2947
+ }
2948
+ controller.enqueue(
2949
+ mapOpenAIResponsesAnnotationSource(
2950
+ annotation,
2951
+ providerOptionsName,
2952
+ self.config.generateId?.() ?? generateId(),
2953
+ ),
2954
+ );
2955
+ }
2956
+
2923
2957
  for (const toolCall of Object.values(ongoingToolCalls)) {
2924
2958
  if (!toolCall?.suppressInputStreaming) {
2925
2959
  continue;
@@ -68,6 +68,10 @@ export type OpenaiResponsesTextProviderMetadata = {
68
68
  };
69
69
 
70
70
  export type ResponsesSourceDocumentProviderMetadata =
71
+ | {
72
+ type: 'file_search';
73
+ fileId: string;
74
+ }
71
75
  | {
72
76
  type: 'file_citation';
73
77
  fileId: string;