@ai-sdk/openai 4.0.91 → 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
 
@@ -1288,6 +1260,19 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1288
1260
  : { result: mapWebSearchOutput(part.action) }),
1289
1261
  });
1290
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
+
1291
1276
  break;
1292
1277
  }
1293
1278
 
@@ -1424,6 +1409,26 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1424
1409
  })) ?? null,
1425
1410
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
1426
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
+ }
1427
1432
  break;
1428
1433
  }
1429
1434
 
@@ -1617,12 +1622,16 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1617
1622
  > = {};
1618
1623
 
1619
1624
  // set annotations in 'text-end' part providerMetadata.
1620
- const ongoingAnnotations: Array<
1625
+ let ongoingAnnotations: Array<
1621
1626
  Extract<
1622
1627
  OpenAIResponsesChunk,
1623
1628
  { type: 'response.output_text.annotation.added' }
1624
1629
  >['annotation']
1625
1630
  > = [];
1631
+ let hasWebSearchActionSources = false;
1632
+ let hasFileSearchResults = false;
1633
+ const retrievedFileIds = new Set<string>();
1634
+ const fallbackAnnotations: typeof ongoingAnnotations = [];
1626
1635
 
1627
1636
  // track the phase of the current message being streamed
1628
1637
  let activeMessagePhase: 'commentary' | 'final_answer' | undefined;
@@ -1730,6 +1739,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1730
1739
  id: value.item.call_id,
1731
1740
  toolName,
1732
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
+ });
1733
1750
  } else if (value.item.type === 'web_search_call') {
1734
1751
  ongoingToolCalls[value.output_index] = {
1735
1752
  toolName: toolNameMapping.toCustomToolName(
@@ -1894,7 +1911,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1894
1911
  // shell_call_output is handled in output_item.done
1895
1912
  } else if (value.item.type === 'message') {
1896
1913
  activeOutputItemIds[value.output_index] = value.item.id;
1897
- ongoingAnnotations.splice(0);
1914
+ ongoingAnnotations = [];
1898
1915
  activeMessagePhase = value.item.phase ?? undefined;
1899
1916
  controller.enqueue({
1900
1917
  type: 'text-start',
@@ -1941,6 +1958,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1941
1958
  controller.enqueue({
1942
1959
  type: 'text-end',
1943
1960
  id: itemId,
1961
+ ...(ongoingAnnotations.some(
1962
+ annotation => annotation.type !== 'file_path',
1963
+ ) && {
1964
+ citations: mapOpenAIResponsesCitations(
1965
+ ongoingAnnotations,
1966
+ providerOptionsName,
1967
+ ),
1968
+ }),
1944
1969
  providerMetadata: {
1945
1970
  [providerOptionsName]: {
1946
1971
  itemId,
@@ -2120,6 +2145,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2120
2145
  value.item.name,
2121
2146
  );
2122
2147
 
2148
+ controller.enqueue({
2149
+ type: 'tool-input-delta',
2150
+ id: value.item.call_id,
2151
+ delta: '"',
2152
+ });
2153
+
2123
2154
  controller.enqueue({
2124
2155
  type: 'tool-input-end',
2125
2156
  id: value.item.call_id,
@@ -2155,6 +2186,20 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2155
2186
  ? { isError: true, result: { status: value.item.status } }
2156
2187
  : { result: mapWebSearchOutput(value.item.action) }),
2157
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
+ }
2158
2203
  } else if (value.item.type === 'computer_call') {
2159
2204
  ongoingToolCalls[value.output_index] = undefined;
2160
2205
 
@@ -2227,6 +2272,27 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2227
2272
  })) ?? null,
2228
2273
  } satisfies InferSchema<typeof fileSearchOutputSchema>,
2229
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
+ }
2230
2296
  } else if (value.item.type === 'code_interpreter_call') {
2231
2297
  ongoingToolCalls[value.output_index] = undefined;
2232
2298
 
@@ -2582,7 +2648,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2582
2648
  controller.enqueue({
2583
2649
  type: 'tool-input-delta',
2584
2650
  id: toolCall.toolCallId,
2585
- delta: value.delta,
2651
+ delta: escapeJSONDelta(value.delta),
2586
2652
  });
2587
2653
  }
2588
2654
  } else if (isResponseApplyPatchCallOperationDiffDeltaChunk(value)) {
@@ -2843,71 +2909,21 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2843
2909
  }
2844
2910
  } else if (isResponseAnnotationAddedChunk(value)) {
2845
2911
  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
- });
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
+ );
2911
2927
  }
2912
2928
  } else if (isErrorChunk(value)) {
2913
2929
  encounteredStreamError = true;
@@ -2920,6 +2936,23 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2920
2936
  },
2921
2937
 
2922
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
+
2923
2956
  for (const toolCall of Object.values(ongoingToolCalls)) {
2924
2957
  if (!toolCall?.suppressInputStreaming) {
2925
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;