@ai-sdk/open-responses 2.0.34 → 2.0.36

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.
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  UnsupportedFunctionalityError,
3
+ type LanguageModelV4ProviderTool,
3
4
  type LanguageModelV4Prompt,
4
5
  type SharedV4Warning,
5
6
  } from '@ai-sdk/provider';
@@ -8,6 +9,12 @@ import {
8
9
  getTopLevelMediaType,
9
10
  resolveFullMediaType,
10
11
  } from '@ai-sdk/provider-utils';
12
+ import {
13
+ isOpenResponsesExtensionItem,
14
+ type OpenResponsesExtensionInputPart,
15
+ type OpenResponsesExtensionItem,
16
+ type OpenResponsesExtensionRegistry,
17
+ } from '../open-responses-extension';
11
18
  import type {
12
19
  FunctionCallOutputItemParam,
13
20
  InputFileContentParam,
@@ -22,9 +29,13 @@ import type {
22
29
  export async function convertToOpenResponsesInput({
23
30
  prompt,
24
31
  providerOptionsName = 'open-responses',
32
+ extensionRegistry,
33
+ providerToolsByName = new Map(),
25
34
  }: {
26
35
  prompt: LanguageModelV4Prompt;
27
36
  providerOptionsName?: string;
37
+ extensionRegistry?: OpenResponsesExtensionRegistry;
38
+ providerToolsByName?: Map<string, LanguageModelV4ProviderTool>;
28
39
  }): Promise<{
29
40
  input: OpenResponsesRequestBody['input'];
30
41
  instructions: string | undefined;
@@ -33,6 +44,7 @@ export async function convertToOpenResponsesInput({
33
44
  const input: OpenResponsesRequestBody['input'] = [];
34
45
  const warnings: Array<SharedV4Warning> = [];
35
46
  const systemMessages: string[] = [];
47
+ const replayedExtensionItems = new Set<string>();
36
48
 
37
49
  for (const { role, content } of prompt) {
38
50
  switch (role) {
@@ -125,6 +137,52 @@ export async function convertToOpenResponsesInput({
125
137
  };
126
138
 
127
139
  for (const part of content) {
140
+ const extensionReplay = getExtensionReplay({
141
+ part,
142
+ providerOptionsName,
143
+ extensionRegistry,
144
+ });
145
+
146
+ if (extensionReplay != null) {
147
+ flushAssistantContent();
148
+ const replayItem = extensionReplay.item;
149
+ if (replayItem != null) {
150
+ const replayKey = `${replayItem.type}:${replayItem.id}`;
151
+ if (!replayedExtensionItems.has(replayKey)) {
152
+ input.push(replayItem);
153
+ replayedExtensionItems.add(replayKey);
154
+ }
155
+ }
156
+ continue;
157
+ }
158
+
159
+ if (part.type === 'tool-call' || part.type === 'tool-result') {
160
+ const providerTool = providerToolsByName.get(part.toolName);
161
+ const extension =
162
+ providerTool == null
163
+ ? undefined
164
+ : extensionRegistry?.byProviderToolId.get(providerTool.id);
165
+
166
+ if (providerTool != null && extension != null) {
167
+ flushAssistantContent();
168
+ const encoded = await encodeExtensionInputPart({
169
+ extensionRegistry,
170
+ part,
171
+ providerTool,
172
+ });
173
+
174
+ if (encoded == null) {
175
+ warnings.push({
176
+ type: 'unsupported',
177
+ feature: `provider-defined tool ${providerTool.id} ${part.type} history`,
178
+ });
179
+ } else {
180
+ input.push(...encoded);
181
+ }
182
+ continue;
183
+ }
184
+ }
185
+
128
186
  switch (part.type) {
129
187
  case 'reasoning': {
130
188
  flushAssistantContent();
@@ -243,6 +301,48 @@ export async function convertToOpenResponsesInput({
243
301
  case 'tool': {
244
302
  for (const part of content) {
245
303
  if (part.type === 'tool-result') {
304
+ const extensionReplay = getExtensionReplay({
305
+ part,
306
+ providerOptionsName,
307
+ extensionRegistry,
308
+ });
309
+
310
+ if (extensionReplay != null) {
311
+ const replayItem = extensionReplay.item;
312
+ if (replayItem != null) {
313
+ const replayKey = `${replayItem.type}:${replayItem.id}`;
314
+ if (!replayedExtensionItems.has(replayKey)) {
315
+ input.push(replayItem);
316
+ replayedExtensionItems.add(replayKey);
317
+ }
318
+ }
319
+ continue;
320
+ }
321
+
322
+ const providerTool = providerToolsByName.get(part.toolName);
323
+ const extension =
324
+ providerTool == null
325
+ ? undefined
326
+ : extensionRegistry?.byProviderToolId.get(providerTool.id);
327
+
328
+ if (providerTool != null && extension != null) {
329
+ const encoded = await encodeExtensionInputPart({
330
+ extensionRegistry,
331
+ part,
332
+ providerTool,
333
+ });
334
+
335
+ if (encoded == null) {
336
+ warnings.push({
337
+ type: 'unsupported',
338
+ feature: `provider-defined tool ${providerTool.id} tool-result history`,
339
+ });
340
+ } else {
341
+ input.push(...encoded);
342
+ }
343
+ continue;
344
+ }
345
+
246
346
  const output = part.output;
247
347
  let contentValue: FunctionCallOutputItemParam['output'];
248
348
 
@@ -346,6 +446,99 @@ export async function convertToOpenResponsesInput({
346
446
  };
347
447
  }
348
448
 
449
+ async function encodeExtensionInputPart({
450
+ extensionRegistry,
451
+ part,
452
+ providerTool,
453
+ }: {
454
+ extensionRegistry: OpenResponsesExtensionRegistry | undefined;
455
+ part: OpenResponsesExtensionInputPart;
456
+ providerTool: LanguageModelV4ProviderTool;
457
+ }): Promise<OpenResponsesExtensionItem[] | undefined> {
458
+ const extension = extensionRegistry?.byProviderToolId.get(providerTool.id);
459
+ const encodeInputItem = extension?.encodeInputItem;
460
+ const itemTypes = extension?.itemTypes;
461
+ if (encodeInputItem == null || itemTypes == null) {
462
+ return undefined;
463
+ }
464
+
465
+ try {
466
+ const value = await encodeInputItem({
467
+ part,
468
+ tool: providerTool,
469
+ });
470
+ const items =
471
+ value == null ? undefined : Array.isArray(value) ? value : [value];
472
+
473
+ if (
474
+ items == null ||
475
+ items.length === 0 ||
476
+ !items.every(
477
+ item =>
478
+ isOpenResponsesExtensionItem(item) && itemTypes.includes(item.type),
479
+ )
480
+ ) {
481
+ return undefined;
482
+ }
483
+
484
+ return items;
485
+ } catch {
486
+ return undefined;
487
+ }
488
+ }
489
+
490
+ function getExtensionReplay({
491
+ part,
492
+ providerOptionsName,
493
+ extensionRegistry,
494
+ }: {
495
+ part: {
496
+ providerOptions?: Record<string, unknown>;
497
+ };
498
+ providerOptionsName: string;
499
+ extensionRegistry: OpenResponsesExtensionRegistry | undefined;
500
+ }): { item?: OpenResponsesExtensionItem } | undefined {
501
+ const extensionData = getProviderData(
502
+ part,
503
+ providerOptionsName,
504
+ )?.openResponsesExtension;
505
+
506
+ if (
507
+ extensionData == null ||
508
+ typeof extensionData !== 'object' ||
509
+ Array.isArray(extensionData)
510
+ ) {
511
+ return undefined;
512
+ }
513
+
514
+ const { id, item, itemId } = extensionData as {
515
+ id?: unknown;
516
+ item?: unknown;
517
+ itemId?: unknown;
518
+ };
519
+
520
+ if (typeof id !== 'string') {
521
+ return undefined;
522
+ }
523
+
524
+ const extension = extensionRegistry?.byExtensionId.get(
525
+ id as LanguageModelV4ProviderTool['id'],
526
+ );
527
+
528
+ if (extension == null) {
529
+ return undefined;
530
+ }
531
+
532
+ if (
533
+ isOpenResponsesExtensionItem(item) &&
534
+ extension.itemTypes?.includes(item.type)
535
+ ) {
536
+ return { item };
537
+ }
538
+
539
+ return typeof itemId === 'string' ? {} : undefined;
540
+ }
541
+
349
542
  function getProviderData(
350
543
  part: {
351
544
  providerOptions?: Record<string, unknown>;
@@ -1,6 +1,11 @@
1
1
  import type { JSONSchema7 } from '@ai-sdk/provider';
2
2
  import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
3
3
  import { z } from 'zod/v4';
4
+ import type {
5
+ OpenResponsesExtensionEvent,
6
+ OpenResponsesExtensionItem,
7
+ OpenResponsesExtensionRecord,
8
+ } from '../open-responses-extension';
4
9
 
5
10
  export const openResponsesErrorSchema = lazySchema(() =>
6
11
  zodSchema(
@@ -263,7 +268,8 @@ export type AllowedToolsParam = {
263
268
  export type ToolChoiceParam =
264
269
  | ToolChoiceValueEnum
265
270
  | SpecificFunctionParam
266
- | AllowedToolsParam;
271
+ | AllowedToolsParam
272
+ | OpenResponsesExtensionRecord;
267
273
 
268
274
  // ============================================================================
269
275
  // Configuration Types
@@ -483,7 +489,8 @@ export type OutputItem =
483
489
  | FunctionCall
484
490
  | FunctionCallOutput
485
491
  | Message
486
- | ReasoningBody;
492
+ | ReasoningBody
493
+ | OpenResponsesExtensionItem;
487
494
 
488
495
  /**
489
496
  * Details about why the response was incomplete.
@@ -626,6 +633,7 @@ export type OpenResponsesRequestBody = {
626
633
  | AssistantMessageItemParam
627
634
  | FunctionCallItemParam
628
635
  | FunctionCallOutputItemParam
636
+ | OpenResponsesExtensionRecord
629
637
  >;
630
638
 
631
639
  /**
@@ -643,7 +651,7 @@ export type OpenResponsesRequestBody = {
643
651
  /**
644
652
  * A list of tools that the model may call while generating the response.
645
653
  */
646
- tools?: FunctionToolParam[];
654
+ tools?: Array<FunctionToolParam | OpenResponsesExtensionRecord>;
647
655
 
648
656
  /**
649
657
  * Controls which tool the model should use, if any.
@@ -1235,4 +1243,6 @@ export type OpenResponsesChunk =
1235
1243
  | ResponseReasoningSummaryPartAddedEvent
1236
1244
  | ResponseReasoningSummaryPartDoneEvent
1237
1245
  // Error Event
1238
- | ResponseErrorEvent;
1246
+ | ResponseErrorEvent
1247
+ // Registered extension event
1248
+ | OpenResponsesExtensionEvent;
@@ -1,4 +1,5 @@
1
1
  import type { FetchFunction } from '@ai-sdk/provider-utils';
2
+ import type { OpenResponsesExtensionRegistry } from '../open-responses-extension';
2
3
 
3
4
  export type OpenResponsesConfig = {
4
5
  provider: string;
@@ -7,4 +8,5 @@ export type OpenResponsesConfig = {
7
8
  headers?: () => Record<string, string | undefined>;
8
9
  fetch?: FetchFunction;
9
10
  generateId: () => string;
11
+ extensionRegistry?: OpenResponsesExtensionRegistry;
10
12
  };
@@ -22,17 +22,28 @@ import {
22
22
  mapReasoningToProviderEffort,
23
23
  parseProviderOptions,
24
24
  postJsonToApi,
25
+ SerializationError,
25
26
  serializeModelOptions,
26
27
  WORKFLOW_SERIALIZE,
27
28
  WORKFLOW_DESERIALIZE,
28
29
  type ParseResult,
29
30
  } from '@ai-sdk/provider-utils';
30
31
  import { z } from 'zod/v4';
32
+ import {
33
+ createOpenResponsesExtensionRegistry,
34
+ isOpenResponsesExtensionEvent,
35
+ isOpenResponsesExtensionItem,
36
+ isOpenResponsesJSONObject,
37
+ type OpenResponsesExtension,
38
+ type OpenResponsesExtensionContentPart,
39
+ type OpenResponsesExtensionItem,
40
+ type OpenResponsesExtensionRecord,
41
+ type OpenResponsesExtensionRegistry,
42
+ } from '../open-responses-extension';
31
43
  import { convertToOpenResponsesInput } from './convert-to-open-responses-input';
32
44
  import {
33
45
  openResponsesErrorSchema,
34
46
  type Annotation,
35
- type FunctionToolParam,
36
47
  type OpenResponsesRequestBody,
37
48
  type OpenResponsesResponseBody,
38
49
  type OpenResponsesChunk,
@@ -49,8 +60,16 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
49
60
  readonly modelId: string;
50
61
 
51
62
  private readonly config: OpenResponsesConfig;
63
+ private readonly extensionRegistry: OpenResponsesExtensionRegistry;
52
64
 
53
65
  static [WORKFLOW_SERIALIZE](model: OpenResponsesLanguageModel) {
66
+ if (model.extensionRegistry.byExtensionId.size > 0) {
67
+ throw new SerializationError({
68
+ message:
69
+ 'Open Responses models with registered extensions cannot be serialized across workflow boundaries. Recreate the provider with its extension codecs inside the workflow step.',
70
+ });
71
+ }
72
+
54
73
  return serializeModelOptions({
55
74
  modelId: model.modelId,
56
75
  config: model.config,
@@ -67,6 +86,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
67
86
  constructor(modelId: string, config: OpenResponsesConfig) {
68
87
  this.modelId = modelId;
69
88
  this.config = config;
89
+ this.extensionRegistry =
90
+ config.extensionRegistry ?? createOpenResponsesExtensionRegistry();
70
91
  }
71
92
 
72
93
  readonly supportedUrls: Record<string, RegExp[]> = {
@@ -110,6 +131,12 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
110
131
  warnings.push({ type: 'unsupported', feature: 'seed' });
111
132
  }
112
133
 
134
+ const providerToolsByName = new Map(
135
+ (tools ?? [])
136
+ .filter(tool => tool.type === 'provider')
137
+ .map(tool => [tool.name, tool]),
138
+ );
139
+
113
140
  const {
114
141
  input,
115
142
  instructions,
@@ -117,21 +144,63 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
117
144
  } = await convertToOpenResponsesInput({
118
145
  prompt,
119
146
  providerOptionsName: this.config.providerOptionsName,
147
+ extensionRegistry: this.extensionRegistry,
148
+ providerToolsByName,
120
149
  });
121
150
 
122
151
  warnings.push(...inputWarnings);
123
152
 
124
- // Convert function tools to the Open Responses format
125
- const functionTools: FunctionToolParam[] = [];
153
+ const convertedTools: NonNullable<OpenResponsesRequestBody['tools']> = [];
154
+ const encodedProviderToolsByName = new Map<
155
+ string,
156
+ {
157
+ toolType: OpenResponsesExtensionRecord['type'];
158
+ encodeToolChoice: OpenResponsesExtension['encodeToolChoice'];
159
+ tool: Extract<
160
+ NonNullable<LanguageModelV4CallOptions['tools']>[number],
161
+ { type: 'provider' }
162
+ >;
163
+ }
164
+ >();
126
165
 
127
166
  for (const tool of tools ?? []) {
128
167
  if (tool.type === 'provider') {
129
- warnings.push({
130
- type: 'unsupported',
131
- feature: `provider-defined tool ${tool.id}`,
132
- });
168
+ const extension = this.extensionRegistry.byProviderToolId.get(tool.id);
169
+ let encoded: OpenResponsesExtensionRecord | undefined;
170
+
171
+ if (extension != null) {
172
+ try {
173
+ const fields = await extension.encodeTool({
174
+ name: tool.name,
175
+ args: tool.args,
176
+ });
177
+
178
+ if (isOpenResponsesJSONObject(fields)) {
179
+ encoded = {
180
+ ...fields,
181
+ type: extension.toolType,
182
+ };
183
+ }
184
+ } catch {
185
+ // Encoding failures are reported as unsupported below.
186
+ }
187
+ }
188
+
189
+ if (encoded == null) {
190
+ warnings.push({
191
+ type: 'unsupported',
192
+ feature: `provider-defined tool ${tool.id}`,
193
+ });
194
+ } else if (extension != null) {
195
+ convertedTools.push(encoded);
196
+ encodedProviderToolsByName.set(tool.name, {
197
+ toolType: extension.toolType,
198
+ encodeToolChoice: extension.encodeToolChoice,
199
+ tool,
200
+ });
201
+ }
133
202
  } else {
134
- functionTools.push({
203
+ convertedTools.push({
135
204
  type: 'function',
136
205
  name: tool.name,
137
206
  description: tool.description,
@@ -142,12 +211,47 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
142
211
  }
143
212
 
144
213
  // Convert tool choice to the Open Responses format
145
- const convertedToolChoice: ToolChoiceParam | undefined =
146
- toolChoice == null
147
- ? undefined
148
- : toolChoice.type === 'tool'
149
- ? { type: 'function', name: toolChoice.toolName }
150
- : toolChoice.type; // 'auto' | 'none' | 'required'
214
+ let convertedToolChoice: ToolChoiceParam | undefined;
215
+ if (toolChoice?.type === 'tool') {
216
+ const registeredTool = encodedProviderToolsByName.get(
217
+ toolChoice.toolName,
218
+ );
219
+
220
+ if (registeredTool == null) {
221
+ if (!providerToolsByName.has(toolChoice.toolName)) {
222
+ convertedToolChoice = {
223
+ type: 'function',
224
+ name: toolChoice.toolName,
225
+ };
226
+ }
227
+ } else {
228
+ const { encodeToolChoice, tool, toolType } = registeredTool;
229
+ let fields: unknown = {};
230
+
231
+ try {
232
+ fields = await encodeToolChoice?.({
233
+ name: tool.name,
234
+ args: tool.args,
235
+ });
236
+ } catch {
237
+ fields = undefined;
238
+ }
239
+
240
+ if (encodeToolChoice != null && !isOpenResponsesJSONObject(fields)) {
241
+ warnings.push({
242
+ type: 'unsupported',
243
+ feature: `tool choice for provider-defined tool ${tool.id}`,
244
+ });
245
+ } else {
246
+ convertedToolChoice = {
247
+ ...(isOpenResponsesJSONObject(fields) ? fields : {}),
248
+ type: toolType,
249
+ };
250
+ }
251
+ }
252
+ } else {
253
+ convertedToolChoice = toolChoice?.type;
254
+ }
151
255
 
152
256
  const textFormat =
153
257
  responseFormat?.type === 'json'
@@ -210,7 +314,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
210
314
  }),
211
315
  }
212
316
  : undefined,
213
- tools: functionTools.length ? functionTools : undefined,
317
+ tools: convertedTools.length ? convertedTools : undefined,
214
318
  tool_choice: convertedToolChoice,
215
319
  ...(textFormat != null && { text: { format: textFormat } }),
216
320
  },
@@ -336,6 +440,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
336
440
  });
337
441
  break;
338
442
  }
443
+
444
+ default: {
445
+ if (!isOpenResponsesExtensionItem(part)) {
446
+ break;
447
+ }
448
+
449
+ const decoded = await decodeExtensionItem({
450
+ extensionRegistry: this.extensionRegistry,
451
+ item: part,
452
+ mode: 'generate',
453
+ providerOptionsName: this.config.providerOptionsName,
454
+ });
455
+
456
+ if (decoded != null) {
457
+ content.push(...decoded);
458
+ hasToolCalls ||= decoded.some(part => part.type === 'tool-call');
459
+ }
460
+ break;
461
+ }
339
462
  }
340
463
  }
341
464
 
@@ -455,6 +578,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
455
578
  { toolName?: string; toolCallId?: string; arguments?: string }
456
579
  >();
457
580
  const providerOptionsName = this.config.providerOptionsName;
581
+ const extensionRegistry = this.extensionRegistry;
582
+ const extensionStreamState = new Map<string, unknown>();
458
583
 
459
584
  return {
460
585
  stream: response.pipeThrough(
@@ -466,7 +591,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
466
591
  controller.enqueue({ type: 'stream-start', warnings });
467
592
  },
468
593
 
469
- transform(parseResult, controller) {
594
+ async transform(parseResult, controller) {
470
595
  if (options.includeRawChunks) {
471
596
  controller.enqueue({
472
597
  type: 'raw',
@@ -481,6 +606,27 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
481
606
 
482
607
  const chunk = parseResult.value;
483
608
 
609
+ if (isOpenResponsesExtensionEvent(chunk)) {
610
+ const extension = extensionRegistry.byEventType.get(chunk.type);
611
+ if (extension?.decodeEvent != null) {
612
+ try {
613
+ const decoded = await extension.decodeEvent({
614
+ event: chunk,
615
+ state: extensionStreamState,
616
+ });
617
+ for (const part of decoded ?? []) {
618
+ controller.enqueue(part);
619
+ hasToolCalls ||=
620
+ part.type === 'tool-call' ||
621
+ part.type === 'tool-input-start';
622
+ }
623
+ } catch (error) {
624
+ controller.enqueue({ type: 'error', error });
625
+ }
626
+ }
627
+ return;
628
+ }
629
+
484
630
  // Tool call events (single-shot tool-call when complete)
485
631
  if (
486
632
  chunk.type === 'response.output_item.added' &&
@@ -541,6 +687,25 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
541
687
  hasToolCalls = true;
542
688
 
543
689
  toolCallsByItemId.delete(chunk.item.id);
690
+ } else if (
691
+ chunk.type === 'response.output_item.done' &&
692
+ isOpenResponsesExtensionItem(chunk.item)
693
+ ) {
694
+ try {
695
+ const decoded = await decodeExtensionItem({
696
+ extensionRegistry,
697
+ item: chunk.item,
698
+ mode: 'stream',
699
+ providerOptionsName,
700
+ });
701
+
702
+ for (const part of decoded ?? []) {
703
+ controller.enqueue(part);
704
+ hasToolCalls ||= part.type === 'tool-call';
705
+ }
706
+ } catch (error) {
707
+ controller.enqueue({ type: 'error', error });
708
+ }
544
709
  }
545
710
 
546
711
  // Reasoning events (note: response.reasoning_text.delta is an LM Studio extension, not in official spec)
@@ -729,6 +894,95 @@ function createReasoningProviderMetadata({
729
894
  };
730
895
  }
731
896
 
897
+ async function decodeExtensionItem({
898
+ extensionRegistry,
899
+ item,
900
+ mode,
901
+ providerOptionsName,
902
+ }: {
903
+ extensionRegistry: OpenResponsesExtensionRegistry;
904
+ item: OpenResponsesExtensionItem;
905
+ mode: 'generate' | 'stream';
906
+ providerOptionsName: string;
907
+ }): Promise<OpenResponsesExtensionContentPart[] | undefined> {
908
+ const extension = extensionRegistry.byItemType.get(item.type);
909
+ if (extension == null) {
910
+ return undefined;
911
+ }
912
+
913
+ const decoded = await extension.decodeItem({ item, mode });
914
+ if (decoded == null) {
915
+ return undefined;
916
+ }
917
+
918
+ return [
919
+ createExtensionReplayCarrier({
920
+ extension,
921
+ item,
922
+ providerOptionsName,
923
+ }),
924
+ ...decoded.map(part =>
925
+ addExtensionItemReferenceMetadata({
926
+ extension,
927
+ item,
928
+ part,
929
+ providerOptionsName,
930
+ }),
931
+ ),
932
+ ];
933
+ }
934
+
935
+ function createExtensionReplayCarrier({
936
+ extension,
937
+ item,
938
+ providerOptionsName,
939
+ }: {
940
+ extension: OpenResponsesExtension;
941
+ item: OpenResponsesExtensionItem;
942
+ providerOptionsName: string;
943
+ }): OpenResponsesExtensionContentPart {
944
+ return {
945
+ type: 'custom',
946
+ kind: 'open-responses.extension-replay',
947
+ providerMetadata: {
948
+ [providerOptionsName]: {
949
+ openResponsesExtension: {
950
+ id: extension.id,
951
+ item,
952
+ },
953
+ },
954
+ },
955
+ };
956
+ }
957
+
958
+ function addExtensionItemReferenceMetadata({
959
+ extension,
960
+ item,
961
+ part,
962
+ providerOptionsName,
963
+ }: {
964
+ extension: OpenResponsesExtension;
965
+ item: OpenResponsesExtensionItem;
966
+ part: OpenResponsesExtensionContentPart;
967
+ providerOptionsName: string;
968
+ }): OpenResponsesExtensionContentPart {
969
+ const providerMetadata = part.providerMetadata ?? {};
970
+
971
+ return {
972
+ ...part,
973
+ providerMetadata: {
974
+ ...providerMetadata,
975
+ [providerOptionsName]: {
976
+ ...providerMetadata[providerOptionsName],
977
+ openResponsesExtension: {
978
+ id: extension.id,
979
+ itemId: item.id,
980
+ },
981
+ },
982
+ },
983
+ };
984
+ }
985
+
732
986
  function getOutputTextAnnotations(value: unknown): Annotation[] {
733
987
  if (
734
988
  value == null ||