@ai-sdk/openai 4.0.90 → 4.0.92

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';
@@ -905,6 +908,18 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
905
908
  options.tools?.filter(
906
909
  (tool): tool is LanguageModelV4FunctionTool => tool.type === 'function',
907
910
  ) ?? [];
911
+ const hasWebSearchActionSources = response.output.some(
912
+ part =>
913
+ part.type === 'web_search_call' &&
914
+ part.action?.type === 'search' &&
915
+ part.action.sources?.some(source => source.type === 'url'),
916
+ );
917
+
918
+ const hasFileSearchResults = response.output.some(
919
+ part =>
920
+ part.type === 'file_search_call' && (part.results?.length ?? 0) > 0,
921
+ );
922
+ const retrievedFileIds = new Set<string>();
908
923
 
909
924
  // flag that checks if there have been client-side tool calls (not executed by openai)
910
925
  let hasFunctionCall = false;
@@ -1084,78 +1099,35 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1084
1099
  content.push({
1085
1100
  type: 'text',
1086
1101
  text: contentPart.text,
1102
+ ...(contentPart.annotations.some(
1103
+ annotation => annotation.type !== 'file_path',
1104
+ ) && {
1105
+ citations: mapOpenAIResponsesCitations(
1106
+ contentPart.annotations,
1107
+ providerOptionsName,
1108
+ ),
1109
+ }),
1087
1110
  providerMetadata: {
1088
1111
  [providerOptionsName]: providerMetadata,
1089
1112
  },
1090
1113
  });
1091
1114
 
1092
1115
  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
- });
1116
+ // Use citation-derived sources only when retrieval data is unavailable.
1117
+ if (
1118
+ (annotation.type === 'url_citation' &&
1119
+ hasWebSearchActionSources) ||
1120
+ (annotation.type === 'file_citation' && hasFileSearchResults)
1121
+ ) {
1122
+ continue;
1158
1123
  }
1124
+ content.push(
1125
+ mapOpenAIResponsesAnnotationSource(
1126
+ annotation,
1127
+ providerOptionsName,
1128
+ this.config.generateId?.() ?? generateId(),
1129
+ ),
1130
+ );
1159
1131
  }
1160
1132
  }
1161
1133
 
@@ -1283,9 +1255,24 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1283
1255
  toolName: toolNameMapping.toCustomToolName(
1284
1256
  webSearchToolName ?? 'web_search',
1285
1257
  ),
1286
- result: mapWebSearchOutput(part.action),
1258
+ ...(part.status === 'failed' || part.status === 'incomplete'
1259
+ ? { isError: true, result: { status: part.status } }
1260
+ : { result: mapWebSearchOutput(part.action) }),
1287
1261
  });
1288
1262
 
1263
+ if (part.action?.type === 'search') {
1264
+ for (const source of part.action.sources ?? []) {
1265
+ if (source.type === 'url') {
1266
+ content.push({
1267
+ type: 'source',
1268
+ sourceType: 'url',
1269
+ id: this.config.generateId?.() ?? generateId(),
1270
+ url: source.url,
1271
+ });
1272
+ }
1273
+ }
1274
+ }
1275
+
1289
1276
  break;
1290
1277
  }
1291
1278
 
@@ -1422,6 +1409,26 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1422
1409
  })) ?? null,
1423
1410
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
1424
1411
  });
1412
+ for (const result of part.results ?? []) {
1413
+ if (retrievedFileIds.has(result.file_id)) {
1414
+ continue;
1415
+ }
1416
+ retrievedFileIds.add(result.file_id);
1417
+ content.push({
1418
+ type: 'source',
1419
+ sourceType: 'document',
1420
+ id: this.config.generateId?.() ?? generateId(),
1421
+ mediaType: 'text/plain',
1422
+ title: result.filename,
1423
+ filename: result.filename,
1424
+ providerMetadata: {
1425
+ [providerOptionsName]: {
1426
+ type: 'file_search',
1427
+ fileId: result.file_id,
1428
+ },
1429
+ },
1430
+ });
1431
+ }
1425
1432
  break;
1426
1433
  }
1427
1434
 
@@ -1615,12 +1622,16 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1615
1622
  > = {};
1616
1623
 
1617
1624
  // set annotations in 'text-end' part providerMetadata.
1618
- const ongoingAnnotations: Array<
1625
+ let ongoingAnnotations: Array<
1619
1626
  Extract<
1620
1627
  OpenAIResponsesChunk,
1621
1628
  { type: 'response.output_text.annotation.added' }
1622
1629
  >['annotation']
1623
1630
  > = [];
1631
+ let hasWebSearchActionSources = false;
1632
+ let hasFileSearchResults = false;
1633
+ const retrievedFileIds = new Set<string>();
1634
+ const fallbackAnnotations: typeof ongoingAnnotations = [];
1624
1635
 
1625
1636
  // track the phase of the current message being streamed
1626
1637
  let activeMessagePhase: 'commentary' | 'final_answer' | undefined;
@@ -1728,6 +1739,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1728
1739
  id: value.item.call_id,
1729
1740
  toolName,
1730
1741
  });
1742
+
1743
+ // Custom tool input is a string, so stream its JSON encoding
1744
+ // consistently with the final tool call input.
1745
+ controller.enqueue({
1746
+ type: 'tool-input-delta',
1747
+ id: value.item.call_id,
1748
+ delta: '"',
1749
+ });
1731
1750
  } else if (value.item.type === 'web_search_call') {
1732
1751
  ongoingToolCalls[value.output_index] = {
1733
1752
  toolName: toolNameMapping.toCustomToolName(
@@ -1892,7 +1911,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1892
1911
  // shell_call_output is handled in output_item.done
1893
1912
  } else if (value.item.type === 'message') {
1894
1913
  activeOutputItemIds[value.output_index] = value.item.id;
1895
- ongoingAnnotations.splice(0);
1914
+ ongoingAnnotations = [];
1896
1915
  activeMessagePhase = value.item.phase ?? undefined;
1897
1916
  controller.enqueue({
1898
1917
  type: 'text-start',
@@ -1939,6 +1958,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1939
1958
  controller.enqueue({
1940
1959
  type: 'text-end',
1941
1960
  id: itemId,
1961
+ ...(ongoingAnnotations.some(
1962
+ annotation => annotation.type !== 'file_path',
1963
+ ) && {
1964
+ citations: mapOpenAIResponsesCitations(
1965
+ ongoingAnnotations,
1966
+ providerOptionsName,
1967
+ ),
1968
+ }),
1942
1969
  providerMetadata: {
1943
1970
  [providerOptionsName]: {
1944
1971
  itemId,
@@ -2118,6 +2145,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2118
2145
  value.item.name,
2119
2146
  );
2120
2147
 
2148
+ controller.enqueue({
2149
+ type: 'tool-input-delta',
2150
+ id: value.item.call_id,
2151
+ delta: '"',
2152
+ });
2153
+
2121
2154
  controller.enqueue({
2122
2155
  type: 'tool-input-end',
2123
2156
  id: value.item.call_id,
@@ -2148,8 +2181,25 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2148
2181
  toolName: toolNameMapping.toCustomToolName(
2149
2182
  webSearchToolName ?? 'web_search',
2150
2183
  ),
2151
- result: mapWebSearchOutput(value.item.action),
2184
+ ...(value.item.status === 'failed' ||
2185
+ value.item.status === 'incomplete'
2186
+ ? { isError: true, result: { status: value.item.status } }
2187
+ : { result: mapWebSearchOutput(value.item.action) }),
2152
2188
  });
2189
+
2190
+ if (value.item.action?.type === 'search') {
2191
+ for (const source of value.item.action.sources ?? []) {
2192
+ if (source.type === 'url') {
2193
+ hasWebSearchActionSources = true;
2194
+ controller.enqueue({
2195
+ type: 'source',
2196
+ sourceType: 'url',
2197
+ id: self.config.generateId?.() ?? generateId(),
2198
+ url: source.url,
2199
+ });
2200
+ }
2201
+ }
2202
+ }
2153
2203
  } else if (value.item.type === 'computer_call') {
2154
2204
  ongoingToolCalls[value.output_index] = undefined;
2155
2205
 
@@ -2222,6 +2272,27 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2222
2272
  })) ?? null,
2223
2273
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
2224
2274
  });
2275
+ for (const result of value.item.results ?? []) {
2276
+ hasFileSearchResults = true;
2277
+ if (retrievedFileIds.has(result.file_id)) {
2278
+ continue;
2279
+ }
2280
+ retrievedFileIds.add(result.file_id);
2281
+ controller.enqueue({
2282
+ type: 'source',
2283
+ sourceType: 'document',
2284
+ id: self.config.generateId?.() ?? generateId(),
2285
+ mediaType: 'text/plain',
2286
+ title: result.filename,
2287
+ filename: result.filename,
2288
+ providerMetadata: {
2289
+ [providerOptionsName]: {
2290
+ type: 'file_search',
2291
+ fileId: result.file_id,
2292
+ },
2293
+ },
2294
+ });
2295
+ }
2225
2296
  } else if (value.item.type === 'code_interpreter_call') {
2226
2297
  ongoingToolCalls[value.output_index] = undefined;
2227
2298
 
@@ -2577,7 +2648,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2577
2648
  controller.enqueue({
2578
2649
  type: 'tool-input-delta',
2579
2650
  id: toolCall.toolCallId,
2580
- delta: value.delta,
2651
+ delta: escapeJSONDelta(value.delta),
2581
2652
  });
2582
2653
  }
2583
2654
  } else if (isResponseApplyPatchCallOperationDiffDeltaChunk(value)) {
@@ -2838,71 +2909,21 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2838
2909
  }
2839
2910
  } else if (isResponseAnnotationAddedChunk(value)) {
2840
2911
  ongoingAnnotations.push(value.annotation);
2841
- if (value.annotation.type === 'url_citation') {
2842
- controller.enqueue({
2843
- type: 'source',
2844
- sourceType: 'url',
2845
- id: self.config.generateId?.() ?? generateId(),
2846
- url: value.annotation.url,
2847
- title: value.annotation.title,
2848
- });
2849
- } else if (value.annotation.type === 'file_citation') {
2850
- controller.enqueue({
2851
- type: 'source',
2852
- sourceType: 'document',
2853
- id: self.config.generateId?.() ?? generateId(),
2854
- mediaType: 'text/plain',
2855
- title: value.annotation.filename,
2856
- filename: value.annotation.filename,
2857
- providerMetadata: {
2858
- [providerOptionsName]: {
2859
- type: value.annotation.type,
2860
- fileId: value.annotation.file_id,
2861
- index: value.annotation.index,
2862
- } satisfies Extract<
2863
- ResponsesSourceDocumentProviderMetadata,
2864
- { type: 'file_citation' }
2865
- >,
2866
- },
2867
- });
2868
- } else if (value.annotation.type === 'container_file_citation') {
2869
- controller.enqueue({
2870
- type: 'source',
2871
- sourceType: 'document',
2872
- id: self.config.generateId?.() ?? generateId(),
2873
- mediaType: 'text/plain',
2874
- title: value.annotation.filename,
2875
- filename: value.annotation.filename,
2876
- providerMetadata: {
2877
- [providerOptionsName]: {
2878
- type: value.annotation.type,
2879
- fileId: value.annotation.file_id,
2880
- containerId: value.annotation.container_id,
2881
- } satisfies Extract<
2882
- ResponsesSourceDocumentProviderMetadata,
2883
- { type: 'container_file_citation' }
2884
- >,
2885
- },
2886
- });
2887
- } else if (value.annotation.type === 'file_path') {
2888
- controller.enqueue({
2889
- type: 'source',
2890
- sourceType: 'document',
2891
- id: self.config.generateId?.() ?? generateId(),
2892
- mediaType: 'application/octet-stream',
2893
- title: value.annotation.file_id,
2894
- filename: value.annotation.file_id,
2895
- providerMetadata: {
2896
- [providerOptionsName]: {
2897
- type: value.annotation.type,
2898
- fileId: value.annotation.file_id,
2899
- index: value.annotation.index,
2900
- } satisfies Extract<
2901
- ResponsesSourceDocumentProviderMetadata,
2902
- { type: 'file_path' }
2903
- >,
2904
- },
2905
- });
2912
+ if (
2913
+ value.annotation.type === 'url_citation' ||
2914
+ value.annotation.type === 'file_citation'
2915
+ ) {
2916
+ // Retrieval events may arrive after the annotation. Decide the
2917
+ // compatibility fallback once the complete source set is known.
2918
+ fallbackAnnotations.push(value.annotation);
2919
+ } else {
2920
+ controller.enqueue(
2921
+ mapOpenAIResponsesAnnotationSource(
2922
+ value.annotation,
2923
+ providerOptionsName,
2924
+ self.config.generateId?.() ?? generateId(),
2925
+ ),
2926
+ );
2906
2927
  }
2907
2928
  } else if (isErrorChunk(value)) {
2908
2929
  encounteredStreamError = true;
@@ -2915,6 +2936,23 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2915
2936
  },
2916
2937
 
2917
2938
  flush(controller) {
2939
+ for (const annotation of fallbackAnnotations) {
2940
+ if (
2941
+ (annotation.type === 'url_citation' &&
2942
+ hasWebSearchActionSources) ||
2943
+ (annotation.type === 'file_citation' && hasFileSearchResults)
2944
+ ) {
2945
+ continue;
2946
+ }
2947
+ controller.enqueue(
2948
+ mapOpenAIResponsesAnnotationSource(
2949
+ annotation,
2950
+ providerOptionsName,
2951
+ self.config.generateId?.() ?? generateId(),
2952
+ ),
2953
+ );
2954
+ }
2955
+
2918
2956
  for (const toolCall of Object.values(ongoingToolCalls)) {
2919
2957
  if (!toolCall?.suppressInputStreaming) {
2920
2958
  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;