@ai-sdk/open-responses 2.0.54 → 2.0.55

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.
@@ -386,6 +386,57 @@ const result = await generateText({
386
386
  });
387
387
  ```
388
388
 
389
+ Some Responses-compatible implementations document non-namespaced extension
390
+ discriminators, such as `web_search`, `web_search_call`, or
391
+ `response.custom_tool_call_input.delta`. These are not portable Open Responses
392
+ types. Register each exact value through the dedicated bare fields to opt in:
393
+
394
+ ```ts
395
+ import type { Experimental_OpenResponsesBareExtension } from '@ai-sdk/open-responses';
396
+
397
+ const webSearchExtension: Experimental_OpenResponsesBareExtension = {
398
+ id: 'acme.web_search',
399
+ allowBareTypes: true,
400
+ bareToolType: 'web_search',
401
+ bareItemTypes: ['web_search_call'],
402
+ bareEventTypes: ['response.web_search_call.completed'],
403
+ encodeTool: ({ name, args }) => ({
404
+ name,
405
+ search_context_size: args.searchContextSize,
406
+ }),
407
+ decodeItem: ({ item }) => {
408
+ if (
409
+ typeof item.call_id !== 'string' ||
410
+ typeof item.name !== 'string' ||
411
+ typeof item.query !== 'string'
412
+ ) {
413
+ return undefined;
414
+ }
415
+
416
+ return [
417
+ {
418
+ type: 'tool-call',
419
+ toolCallId: item.call_id,
420
+ toolName: item.name,
421
+ input: JSON.stringify({ query: item.query }),
422
+ providerExecuted: true,
423
+ },
424
+ ];
425
+ },
426
+ decodeEvent: () => undefined,
427
+ };
428
+ ```
429
+
430
+ Set `allowBareTypes: true` when using `bareToolType`, `bareItemTypes`, or
431
+ `bareEventTypes`. These fields accept only non-namespaced values. They do not
432
+ enable unknown JSON passthrough: a bare discriminator is handled only when it
433
+ exactly matches a registered value. Core Open Responses discriminators and
434
+ duplicate registrations are rejected.
435
+ Use the normal namespaced fields whenever the implementation supports them.
436
+ `Experimental_OpenResponsesExtension` remains the namespaced interface;
437
+ use `Experimental_OpenResponsesBareExtension` only for this compatibility
438
+ mode.
439
+
389
440
  Set `providerExecuted` on each decoded `tool-call` or `tool-input-start` part.
390
441
  It can vary by item or event.
391
442
 
@@ -395,18 +446,18 @@ An extension codec can define:
395
446
  with an `unsupported` warning. A `toolChoice` that selects the omitted tool is
396
447
  also omitted.
397
448
  - `encodeToolChoice`: Encodes a specific `toolChoice`. The default is
398
- `{ type: toolType }`.
449
+ an object containing the registered `toolType` or `bareToolType`.
399
450
  - `decodeItem`: Maps a completed item to AI SDK content parts. `mode` is either
400
451
  `'generate'` or `'stream'`.
401
452
  - `encodeInputItem`: Encodes tool-call or tool-result history when the original
402
453
  wire item is unavailable. Returned items must include `id`, `status`, and a
403
- registered namespaced `type`.
454
+ `type` registered through `itemTypes` or `bareItemTypes`.
404
455
  - `decodeEvent`: Maps a streaming event to AI SDK stream parts. Its `state` map
405
456
  lasts for one response stream.
406
457
 
407
458
  Tool, item, and event capabilities are independent. An item-only extension, for
408
- example, defines `itemTypes` and `decodeItem` without `toolType` or `encodeTool`.
409
- Capability fields must be provided in pairs.
459
+ example, defines `itemTypes` (or `bareItemTypes`) and `decodeItem` without a
460
+ tool type or `encodeTool`. Capability fields must be provided in pairs.
410
461
 
411
462
  Each decoded item adds an `open-responses.extension-replay` custom part that
412
463
  stores the original JSON in provider metadata. Other decoded parts reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/open-responses",
3
- "version": "2.0.54",
3
+ "version": "2.0.55",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,8 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/provider": "4.0.18",
33
- "@ai-sdk/provider-utils": "5.0.49"
32
+ "@ai-sdk/provider": "4.0.19",
33
+ "@ai-sdk/provider-utils": "5.0.50"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@ai-sdk/test-server": "2.0.2",
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export type {
6
6
  } from './open-responses-provider';
7
7
  export type { OpenResponsesCustomToolOptions } from './open-responses-tools';
8
8
  export type {
9
+ OpenResponsesBareExtension as Experimental_OpenResponsesBareExtension,
9
10
  OpenResponsesExtension as Experimental_OpenResponsesExtension,
10
11
  OpenResponsesExtensionContentPart as Experimental_OpenResponsesExtensionContentPart,
11
12
  OpenResponsesExtensionEvent as Experimental_OpenResponsesExtensionEvent,
@@ -10,16 +10,22 @@ import type { MaybePromiseLike } from '@ai-sdk/provider-utils';
10
10
 
11
11
  export type OpenResponsesNamespacedType = `${string}:${string}`;
12
12
 
13
- export type OpenResponsesExtensionRecord = JSONObject & {
14
- type: OpenResponsesNamespacedType;
13
+ export type OpenResponsesExtensionRecord<
14
+ Type extends string = OpenResponsesNamespacedType,
15
+ > = JSONObject & {
16
+ type: Type;
15
17
  };
16
18
 
17
- export type OpenResponsesExtensionItem = OpenResponsesExtensionRecord & {
19
+ export type OpenResponsesExtensionItem<
20
+ Type extends string = OpenResponsesNamespacedType,
21
+ > = OpenResponsesExtensionRecord<Type> & {
18
22
  id: string;
19
23
  status: string;
20
24
  };
21
25
 
22
- export type OpenResponsesExtensionEvent = OpenResponsesExtensionRecord & {
26
+ export type OpenResponsesExtensionEvent<
27
+ Type extends string = OpenResponsesNamespacedType,
28
+ > = OpenResponsesExtensionRecord<Type> & {
23
29
  sequence_number: number;
24
30
  };
25
31
 
@@ -44,7 +50,7 @@ export type OpenResponsesExtensionStreamPart = Exclude<
44
50
  *
45
51
  * Tool, item, and event codecs can be registered independently.
46
52
  */
47
- export interface OpenResponsesExtension {
53
+ interface OpenResponsesExtensionBase {
48
54
  /**
49
55
  * Extension ID in `<implementor>.<extension>` format. For extensions that
50
56
  * encode a provider tool, this is also the AI SDK provider-tool ID.
@@ -57,22 +63,10 @@ export interface OpenResponsesExtension {
57
63
  */
58
64
  toolType?: OpenResponsesNamespacedType;
59
65
 
60
- /**
61
- * Namespaced item types decoded by this extension. Must be provided together
62
- * with `decodeItem`.
63
- */
64
- itemTypes?: readonly OpenResponsesNamespacedType[];
65
-
66
- /**
67
- * Namespaced streaming event types decoded by this extension. Must be
68
- * provided together with `decodeEvent`.
69
- */
70
- eventTypes?: readonly OpenResponsesNamespacedType[];
71
-
72
66
  /**
73
67
  * Encodes an AI SDK provider tool. Return `undefined` when its arguments
74
- * cannot be encoded. The adapter adds `toolType` and omits a specific tool
75
- * choice that selects the omitted tool.
68
+ * cannot be encoded. The adapter adds the registered tool type and omits a
69
+ * specific tool choice that selects the omitted tool.
76
70
  */
77
71
  encodeTool?(options: {
78
72
  name: string;
@@ -80,74 +74,196 @@ export interface OpenResponsesExtension {
80
74
  }): MaybePromiseLike<JSONObject | undefined>;
81
75
 
82
76
  /**
83
- * Encodes a specific `toolChoice`. The adapter adds `toolType`. The default
84
- * is an object containing only `toolType`.
77
+ * Encodes a specific `toolChoice`. The adapter adds the registered tool type.
78
+ * The default is an object containing only that type.
85
79
  */
86
80
  encodeToolChoice?(options: {
87
81
  name: string;
88
82
  args: Record<string, unknown>;
89
83
  }): MaybePromiseLike<JSONObject | undefined>;
84
+ }
90
85
 
86
+ type OpenResponsesExtensionItemCodec<Type extends string> = {
91
87
  /**
92
- * Decodes a completed namespaced item into AI SDK content parts. The adapter
88
+ * Namespaced item types decoded by this extension. Must be provided together
89
+ * with `decodeItem`.
90
+ */
91
+ itemTypes?: readonly OpenResponsesNamespacedType[];
92
+
93
+ /**
94
+ * Decodes a completed registered item into AI SDK content parts. The adapter
93
95
  * adds a custom replay part containing the original item and reference
94
96
  * metadata to the returned parts.
95
97
  */
96
98
  decodeItem?(options: {
97
- item: OpenResponsesExtensionItem;
99
+ item: OpenResponsesExtensionItem<Type>;
98
100
  mode: 'generate' | 'stream';
99
101
  }): MaybePromiseLike<OpenResponsesExtensionContentPart[] | undefined>;
100
102
 
101
103
  /**
102
104
  * Encodes an AI SDK history part when no original wire item is available.
103
- * Every returned item must use one of `itemTypes`.
105
+ * Every returned item must use one of the registered item types.
104
106
  */
105
107
  encodeInputItem?(options: {
106
108
  part: OpenResponsesExtensionInputPart;
107
109
  tool: LanguageModelV4ProviderTool;
108
110
  }): MaybePromiseLike<
109
- OpenResponsesExtensionItem | OpenResponsesExtensionItem[] | undefined
111
+ | OpenResponsesExtensionItem<Type>
112
+ | OpenResponsesExtensionItem<Type>[]
113
+ | undefined
110
114
  >;
115
+ };
111
116
 
117
+ type OpenResponsesExtensionEventCodec<Type extends string> = {
112
118
  /**
113
- * Decodes a namespaced streaming event.
119
+ * Namespaced streaming event types decoded by this extension. Must be
120
+ * provided together with `decodeEvent`.
121
+ */
122
+ eventTypes?: readonly OpenResponsesNamespacedType[];
123
+
124
+ /**
125
+ * Decodes a registered streaming event.
114
126
  *
115
127
  * `state` persists for the lifetime of one response stream.
116
128
  */
117
129
  decodeEvent?(options: {
118
- event: OpenResponsesExtensionEvent;
130
+ event: OpenResponsesExtensionEvent<Type>;
119
131
  state: Map<string, unknown>;
120
132
  }): MaybePromiseLike<OpenResponsesExtensionStreamPart[] | undefined>;
133
+ };
134
+
135
+ /**
136
+ * Defines a portable Open Responses extension using namespaced wire types.
137
+ *
138
+ * This remains an interface so existing consumers can extend it from their own
139
+ * interfaces and implement it in classes.
140
+ */
141
+ export interface OpenResponsesExtension
142
+ extends
143
+ OpenResponsesExtensionBase,
144
+ OpenResponsesExtensionItemCodec<OpenResponsesNamespacedType>,
145
+ OpenResponsesExtensionEventCodec<OpenResponsesNamespacedType> {
146
+ allowBareTypes?: false;
147
+ bareToolType?: undefined;
148
+ bareItemTypes?: undefined;
149
+ bareEventTypes?: undefined;
121
150
  }
122
151
 
152
+ /**
153
+ * Defines a non-portable Open Responses extension that explicitly registers
154
+ * exact bare wire types.
155
+ */
156
+ export type OpenResponsesBareExtension = OpenResponsesExtensionBase &
157
+ OpenResponsesExtensionItemCodec<string> &
158
+ OpenResponsesExtensionEventCodec<string> & {
159
+ /**
160
+ * Explicitly enables non-portable bare discriminator registration for this
161
+ * extension.
162
+ */
163
+ allowBareTypes: true;
164
+
165
+ /**
166
+ * Exact non-namespaced tool type used by a documented implementation
167
+ * extension. Must be provided together with `encodeTool` and cannot be used
168
+ * together with `toolType`.
169
+ */
170
+ bareToolType?: string;
171
+
172
+ /**
173
+ * Exact non-namespaced item types decoded by a documented implementation
174
+ * extension. Must be provided together with `decodeItem`.
175
+ */
176
+ bareItemTypes?: readonly string[];
177
+
178
+ /**
179
+ * Exact non-namespaced streaming event types decoded by this extension.
180
+ * Must be provided together with `decodeEvent`.
181
+ */
182
+ bareEventTypes?: readonly string[];
183
+ } & (
184
+ | { bareToolType: string }
185
+ | { bareItemTypes: readonly string[] }
186
+ | { bareEventTypes: readonly string[] }
187
+ );
188
+
189
+ export type OpenResponsesExtensionRegistration =
190
+ | OpenResponsesExtension
191
+ | OpenResponsesBareExtension;
192
+
123
193
  export type OpenResponsesExtensionRegistry = {
124
- byEventType: Map<OpenResponsesNamespacedType, OpenResponsesEventExtension>;
125
- byExtensionId: Map<LanguageModelV4ProviderTool['id'], OpenResponsesExtension>;
126
- byItemType: Map<OpenResponsesNamespacedType, OpenResponsesItemExtension>;
194
+ byEventType: Map<string, OpenResponsesEventExtension>;
195
+ byExtensionId: Map<
196
+ LanguageModelV4ProviderTool['id'],
197
+ OpenResponsesExtensionRegistration
198
+ >;
199
+ byItemType: Map<string, OpenResponsesItemExtension>;
127
200
  byProviderToolId: Map<
128
201
  LanguageModelV4ProviderTool['id'],
129
202
  OpenResponsesToolExtension
130
203
  >;
131
- byToolType: Map<OpenResponsesNamespacedType, OpenResponsesToolExtension>;
204
+ byToolType: Map<string, OpenResponsesToolExtension>;
132
205
  };
133
206
 
134
- type OpenResponsesToolExtension = OpenResponsesExtension & {
135
- toolType: OpenResponsesNamespacedType;
136
- encodeTool: NonNullable<OpenResponsesExtension['encodeTool']>;
207
+ type OpenResponsesToolExtension = OpenResponsesExtensionRegistration & {
208
+ encodeTool: NonNullable<OpenResponsesExtensionRegistration['encodeTool']>;
209
+ encodeInputItem?: (options: {
210
+ part: OpenResponsesExtensionInputPart;
211
+ tool: LanguageModelV4ProviderTool;
212
+ }) => MaybePromiseLike<
213
+ | OpenResponsesExtensionItem<string>
214
+ | OpenResponsesExtensionItem<string>[]
215
+ | undefined
216
+ >;
137
217
  };
138
218
 
139
- type OpenResponsesItemExtension = OpenResponsesExtension & {
140
- itemTypes: readonly OpenResponsesNamespacedType[];
141
- decodeItem: NonNullable<OpenResponsesExtension['decodeItem']>;
219
+ type OpenResponsesItemExtension = OpenResponsesExtensionRegistration & {
220
+ decodeItem(options: {
221
+ item: OpenResponsesExtensionItem<string>;
222
+ mode: 'generate' | 'stream';
223
+ }): MaybePromiseLike<OpenResponsesExtensionContentPart[] | undefined>;
142
224
  };
143
225
 
144
- type OpenResponsesEventExtension = OpenResponsesExtension & {
145
- eventTypes: readonly OpenResponsesNamespacedType[];
146
- decodeEvent: NonNullable<OpenResponsesExtension['decodeEvent']>;
226
+ type OpenResponsesEventExtension = OpenResponsesExtensionRegistration & {
227
+ decodeEvent(options: {
228
+ event: OpenResponsesExtensionEvent<string>;
229
+ state: Map<string, unknown>;
230
+ }): MaybePromiseLike<OpenResponsesExtensionStreamPart[] | undefined>;
147
231
  };
148
232
 
233
+ const coreToolTypes = new Set(['allowed_tools', 'function']);
234
+ const coreItemTypes = new Set([
235
+ 'function_call',
236
+ 'function_call_output',
237
+ 'item_reference',
238
+ 'message',
239
+ 'reasoning',
240
+ ]);
241
+ const coreEventTypes = new Set([
242
+ 'error',
243
+ 'response.completed',
244
+ 'response.content_part.added',
245
+ 'response.content_part.done',
246
+ 'response.created',
247
+ 'response.failed',
248
+ 'response.function_call_arguments.delta',
249
+ 'response.function_call_arguments.done',
250
+ 'response.in_progress',
251
+ 'response.incomplete',
252
+ 'response.output_item.added',
253
+ 'response.output_item.done',
254
+ 'response.output_text.delta',
255
+ 'response.output_text.done',
256
+ 'response.reasoning_summary_part.added',
257
+ 'response.reasoning_summary_part.done',
258
+ 'response.reasoning_summary_text.delta',
259
+ 'response.reasoning_summary_text.done',
260
+ 'response.reasoning_text.delta',
261
+ 'response.refusal.delta',
262
+ 'response.refusal.done',
263
+ ]);
264
+
149
265
  export function createOpenResponsesExtensionRegistry(
150
- extensions?: readonly OpenResponsesExtension[],
266
+ extensions?: readonly OpenResponsesExtensionRegistration[],
151
267
  ): OpenResponsesExtensionRegistry {
152
268
  const registry: OpenResponsesExtensionRegistry = {
153
269
  byEventType: new Map(),
@@ -173,28 +289,56 @@ export function createOpenResponsesExtensionRegistry(
173
289
  field: 'id',
174
290
  });
175
291
 
176
- const hasToolType = extension.toolType != null;
292
+ const hasBareTypes =
293
+ extension.bareToolType != null ||
294
+ extension.bareItemTypes != null ||
295
+ extension.bareEventTypes != null;
296
+ if (hasBareTypes !== (extension.allowBareTypes === true)) {
297
+ throw new Error(
298
+ `Open Responses extension ${extension.id} must set allowBareTypes to true exactly when registering bare types.`,
299
+ );
300
+ }
301
+
302
+ if (extension.toolType != null && extension.bareToolType != null) {
303
+ throw new Error(
304
+ `Open Responses extension ${extension.id} cannot provide toolType and bareToolType together.`,
305
+ );
306
+ }
307
+
308
+ const toolType = extension.toolType ?? extension.bareToolType;
309
+ const hasToolType = toolType != null;
177
310
  const hasToolEncoder = extension.encodeTool != null;
178
311
  if (hasToolType !== hasToolEncoder) {
312
+ const typeField =
313
+ extension.bareToolType != null ? 'bareToolType' : 'toolType';
179
314
  throw new Error(
180
- `Open Responses extension ${extension.id} must provide toolType and encodeTool together.`,
315
+ `Open Responses extension ${extension.id} must provide ${typeField} and encodeTool together.`,
181
316
  );
182
317
  }
183
318
 
184
319
  if (extension.encodeToolChoice != null && !hasToolEncoder) {
185
320
  throw new Error(
186
- `Open Responses extension ${extension.id} cannot provide encodeToolChoice without toolType and encodeTool.`,
321
+ `Open Responses extension ${extension.id} cannot provide encodeToolChoice without a tool type and encodeTool.`,
187
322
  );
188
323
  }
189
324
 
190
325
  if (hasToolType && hasToolEncoder) {
191
326
  const toolExtension = extension as OpenResponsesToolExtension;
192
- assertNamespacedType({
193
- extensionId: extension.id,
194
- namespace,
195
- type: toolExtension.toolType,
196
- field: 'toolType',
197
- });
327
+ if (extension.toolType != null) {
328
+ assertNamespacedType({
329
+ extensionId: extension.id,
330
+ namespace,
331
+ type: extension.toolType,
332
+ field: 'toolType',
333
+ });
334
+ } else {
335
+ assertBareType({
336
+ coreTypes: coreToolTypes,
337
+ extensionId: extension.id,
338
+ type: extension.bareToolType!,
339
+ field: 'bareToolType',
340
+ });
341
+ }
198
342
  registerUnique({
199
343
  map: registry.byProviderToolId,
200
344
  key: extension.id,
@@ -203,34 +347,39 @@ export function createOpenResponsesExtensionRegistry(
203
347
  });
204
348
  registerUnique({
205
349
  map: registry.byToolType,
206
- key: toolExtension.toolType,
350
+ key: toolType,
207
351
  extension: toolExtension,
208
- field: 'toolType',
352
+ field: 'tool type',
209
353
  });
210
354
  }
211
355
 
212
- const hasItemTypes = extension.itemTypes != null;
356
+ const itemTypes = [
357
+ ...(extension.itemTypes ?? []),
358
+ ...(extension.bareItemTypes ?? []),
359
+ ];
360
+ const hasItemTypes =
361
+ extension.itemTypes != null || extension.bareItemTypes != null;
213
362
  const hasItemDecoder = extension.decodeItem != null;
214
363
  if (hasItemTypes !== hasItemDecoder) {
215
364
  throw new Error(
216
- `Open Responses extension ${extension.id} must provide itemTypes and decodeItem together.`,
365
+ `Open Responses extension ${extension.id} must provide itemTypes or bareItemTypes together with decodeItem.`,
217
366
  );
218
367
  }
219
368
 
220
369
  if (extension.encodeInputItem != null && !hasItemDecoder) {
221
370
  throw new Error(
222
- `Open Responses extension ${extension.id} cannot provide encodeInputItem without itemTypes and decodeItem.`,
371
+ `Open Responses extension ${extension.id} cannot provide encodeInputItem without itemTypes or bareItemTypes and decodeItem.`,
223
372
  );
224
373
  }
225
374
 
226
375
  if (hasItemTypes && hasItemDecoder) {
227
376
  const itemExtension = extension as OpenResponsesItemExtension;
228
- if (itemExtension.itemTypes.length === 0) {
377
+ if (itemTypes.length === 0) {
229
378
  throw new Error(
230
379
  `Open Responses extension ${extension.id} must register at least one item type.`,
231
380
  );
232
381
  }
233
- for (const itemType of itemExtension.itemTypes) {
382
+ for (const itemType of extension.itemTypes ?? []) {
234
383
  assertNamespacedType({
235
384
  extensionId: extension.id,
236
385
  namespace,
@@ -244,24 +393,43 @@ export function createOpenResponsesExtensionRegistry(
244
393
  field: 'item type',
245
394
  });
246
395
  }
396
+ for (const itemType of extension.bareItemTypes ?? []) {
397
+ assertBareType({
398
+ coreTypes: coreItemTypes,
399
+ extensionId: extension.id,
400
+ type: itemType,
401
+ field: 'bareItemTypes',
402
+ });
403
+ registerUnique({
404
+ map: registry.byItemType,
405
+ key: itemType,
406
+ extension: itemExtension,
407
+ field: 'item type',
408
+ });
409
+ }
247
410
  }
248
411
 
249
- const hasEventTypes = extension.eventTypes != null;
412
+ const eventTypes = [
413
+ ...(extension.eventTypes ?? []),
414
+ ...(extension.bareEventTypes ?? []),
415
+ ];
416
+ const hasEventTypes =
417
+ extension.eventTypes != null || extension.bareEventTypes != null;
250
418
  const hasEventDecoder = extension.decodeEvent != null;
251
419
  if (hasEventTypes !== hasEventDecoder) {
252
420
  throw new Error(
253
- `Open Responses extension ${extension.id} must provide eventTypes and decodeEvent together.`,
421
+ `Open Responses extension ${extension.id} must provide eventTypes or bareEventTypes together with decodeEvent.`,
254
422
  );
255
423
  }
256
424
 
257
425
  if (hasEventTypes && hasEventDecoder) {
258
426
  const eventExtension = extension as OpenResponsesEventExtension;
259
- if (eventExtension.eventTypes.length === 0) {
427
+ if (eventTypes.length === 0) {
260
428
  throw new Error(
261
429
  `Open Responses extension ${extension.id} must register at least one event type.`,
262
430
  );
263
431
  }
264
- for (const eventType of eventExtension.eventTypes) {
432
+ for (const eventType of extension.eventTypes ?? []) {
265
433
  assertNamespacedType({
266
434
  extensionId: extension.id,
267
435
  namespace,
@@ -275,6 +443,20 @@ export function createOpenResponsesExtensionRegistry(
275
443
  field: 'event type',
276
444
  });
277
445
  }
446
+ for (const eventType of extension.bareEventTypes ?? []) {
447
+ assertBareType({
448
+ coreTypes: coreEventTypes,
449
+ extensionId: extension.id,
450
+ type: eventType,
451
+ field: 'bareEventTypes',
452
+ });
453
+ registerUnique({
454
+ map: registry.byEventType,
455
+ key: eventType,
456
+ extension: eventExtension,
457
+ field: 'event type',
458
+ });
459
+ }
278
460
  }
279
461
 
280
462
  if (!hasToolEncoder && !hasItemDecoder && !hasEventDecoder) {
@@ -298,14 +480,64 @@ function assertNamespacedType({
298
480
  type: string;
299
481
  field: string;
300
482
  }) {
301
- if (!type.includes(':') || type.slice(0, type.indexOf(':')) !== namespace) {
483
+ const separatorIndex = type.indexOf(':');
484
+ if (separatorIndex <= 0 || type.slice(0, separatorIndex) !== namespace) {
302
485
  throw new Error(
303
486
  `Open Responses extension ${extensionId} has invalid ${field} value ${type}. Extension wire types must use the ${namespace}: namespace.`,
304
487
  );
305
488
  }
306
489
  }
307
490
 
308
- function registerUnique<K, Extension extends OpenResponsesExtension>({
491
+ function assertBareType({
492
+ coreTypes,
493
+ extensionId,
494
+ type,
495
+ field,
496
+ }: {
497
+ coreTypes: Set<string>;
498
+ extensionId: string;
499
+ type: string;
500
+ field: string;
501
+ }) {
502
+ if (type.length === 0 || type.includes(':')) {
503
+ throw new Error(
504
+ `Open Responses extension ${extensionId} has invalid ${field} value ${type}. Bare extension wire types must be non-empty and must not contain a colon.`,
505
+ );
506
+ }
507
+
508
+ if (coreTypes.has(type)) {
509
+ throw new Error(
510
+ `Open Responses extension ${extensionId} cannot register core ${field} value ${type}.`,
511
+ );
512
+ }
513
+ }
514
+
515
+ export function getOpenResponsesExtensionItemTypes(
516
+ extension: OpenResponsesExtensionRegistration,
517
+ ): readonly string[] {
518
+ return [...(extension.itemTypes ?? []), ...(extension.bareItemTypes ?? [])];
519
+ }
520
+
521
+ export function getOpenResponsesExtensionToolType(
522
+ extension: OpenResponsesExtensionRegistration,
523
+ ): string | undefined {
524
+ return extension.toolType ?? extension.bareToolType;
525
+ }
526
+
527
+ /**
528
+ * Keeps the internal API unions narrow enough to discriminate core wire types.
529
+ * Bare records only reach this boundary after registry validation.
530
+ */
531
+ export function asOpenResponsesExtensionRecord(
532
+ value: OpenResponsesExtensionRecord<string>,
533
+ ): OpenResponsesExtensionRecord {
534
+ return value as OpenResponsesExtensionRecord;
535
+ }
536
+
537
+ function registerUnique<
538
+ K,
539
+ Extension extends OpenResponsesExtensionRegistration,
540
+ >({
309
541
  map,
310
542
  key,
311
543
  extension,
@@ -333,10 +565,10 @@ export function isOpenResponsesNamespacedType(
333
565
 
334
566
  export function isOpenResponsesExtensionRecord(
335
567
  value: unknown,
336
- ): value is OpenResponsesExtensionRecord {
568
+ ): value is OpenResponsesExtensionRecord<string> {
337
569
  return (
338
570
  isOpenResponsesJSONObject(value) &&
339
- isOpenResponsesNamespacedType((value as { type?: unknown }).type)
571
+ typeof (value as { type?: unknown }).type === 'string'
340
572
  );
341
573
  }
342
574
 
@@ -352,7 +584,7 @@ export function isOpenResponsesJSONObject(value: unknown): value is JSONObject {
352
584
 
353
585
  export function isOpenResponsesExtensionItem(
354
586
  value: unknown,
355
- ): value is OpenResponsesExtensionItem {
587
+ ): value is OpenResponsesExtensionItem<string> {
356
588
  return (
357
589
  isOpenResponsesExtensionRecord(value) &&
358
590
  typeof value.id === 'string' &&
@@ -362,7 +594,7 @@ export function isOpenResponsesExtensionItem(
362
594
 
363
595
  export function isOpenResponsesExtensionEvent(
364
596
  value: unknown,
365
- ): value is OpenResponsesExtensionEvent {
597
+ ): value is OpenResponsesExtensionEvent<string> {
366
598
  return (
367
599
  isOpenResponsesExtensionRecord(value) &&
368
600
  typeof value.sequence_number === 'number'
@@ -12,7 +12,7 @@ import {
12
12
  } from '@ai-sdk/provider-utils';
13
13
  import {
14
14
  createOpenResponsesExtensionRegistry,
15
- type OpenResponsesExtension,
15
+ type OpenResponsesExtensionRegistration,
16
16
  } from './open-responses-extension';
17
17
  import { createOpenResponsesTools } from './open-responses-tools';
18
18
  import { OpenResponsesLanguageModel } from './responses/open-responses-language-model';
@@ -99,7 +99,7 @@ export interface OpenResponsesProviderSettings {
99
99
  *
100
100
  * @experimental This API may change in a future release.
101
101
  */
102
- experimental_extensions?: readonly OpenResponsesExtension[];
102
+ experimental_extensions?: readonly OpenResponsesExtensionRegistration[];
103
103
  }
104
104
 
105
105
  export function createOpenResponses(