@ai-sdk/open-responses 2.0.51 → 2.0.54

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.
@@ -48,12 +48,18 @@ import {
48
48
  type OpenResponsesResponseBody,
49
49
  type OpenResponsesChunk,
50
50
  type ReasoningBody,
51
+ type ResponseError,
51
52
  type ToolChoiceParam,
52
53
  } from './open-responses-api';
53
54
  import { mapOpenResponsesFinishReason } from './map-open-responses-finish-reason';
54
55
  import type { OpenResponsesConfig } from './open-responses-config';
55
56
  import { openResponsesLanguageModelOptions } from './open-responses-language-model-options';
56
57
 
58
+ const defaultFailedResponseHandler = createJsonErrorResponseHandler({
59
+ errorSchema: openResponsesErrorSchema,
60
+ errorToMessage: error => error.error.message,
61
+ });
62
+
57
63
  export class OpenResponsesLanguageModel implements LanguageModelV4 {
58
64
  readonly specificationVersion = 'v4';
59
65
 
@@ -147,6 +153,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
147
153
  extensionRegistry: this.extensionRegistry,
148
154
  providerToolsByName,
149
155
  strictResponseInput: this.config.strictResponseInput,
156
+ customToolId: this.config.customToolId,
150
157
  });
151
158
 
152
159
  warnings.push(...inputWarnings);
@@ -166,6 +173,43 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
166
173
 
167
174
  for (const tool of tools ?? []) {
168
175
  if (tool.type === 'provider') {
176
+ if (
177
+ this.config.customToolId != null &&
178
+ tool.id === this.config.customToolId
179
+ ) {
180
+ const format =
181
+ tool.args.format != null &&
182
+ typeof tool.args.format === 'object' &&
183
+ !Array.isArray(tool.args.format)
184
+ ? tool.args.format
185
+ : undefined;
186
+
187
+ convertedTools.push({
188
+ type: 'custom',
189
+ name: tool.name,
190
+ description:
191
+ typeof tool.args.description === 'string'
192
+ ? tool.args.description
193
+ : undefined,
194
+ format:
195
+ format != null &&
196
+ 'type' in format &&
197
+ format.type === 'grammar' &&
198
+ 'syntax' in format &&
199
+ (format.syntax === 'regex' || format.syntax === 'lark') &&
200
+ 'definition' in format &&
201
+ typeof format.definition === 'string'
202
+ ? {
203
+ type: 'grammar',
204
+ syntax: format.syntax,
205
+ definition: format.definition,
206
+ }
207
+ : format != null && 'type' in format && format.type === 'text'
208
+ ? { type: 'text' }
209
+ : undefined,
210
+ });
211
+ continue;
212
+ }
169
213
  const extension = this.extensionRegistry.byProviderToolId.get(tool.id);
170
214
  let encoded: OpenResponsesExtensionRecord | undefined;
171
215
 
@@ -219,7 +263,16 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
219
263
  );
220
264
 
221
265
  if (registeredTool == null) {
222
- if (!providerToolsByName.has(toolChoice.toolName)) {
266
+ if (
267
+ this.config.customToolId != null &&
268
+ providerToolsByName.get(toolChoice.toolName)?.id ===
269
+ this.config.customToolId
270
+ ) {
271
+ convertedToolChoice = {
272
+ type: 'custom',
273
+ name: toolChoice.toolName,
274
+ };
275
+ } else if (!providerToolsByName.has(toolChoice.toolName)) {
223
276
  convertedToolChoice = {
224
277
  type: 'function',
225
278
  name: toolChoice.toolName,
@@ -255,7 +308,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
255
308
  }
256
309
 
257
310
  const textFormat =
258
- responseFormat?.type === 'json'
311
+ responseFormat?.type === 'json' && this.config.structuredOutputs !== false
259
312
  ? responseFormat.schema != null
260
313
  ? {
261
314
  type: 'json_schema' as const,
@@ -267,6 +320,13 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
267
320
  : { type: 'json_object' as const }
268
321
  : undefined;
269
322
 
323
+ if (
324
+ responseFormat?.type === 'json' &&
325
+ this.config.structuredOutputs === false
326
+ ) {
327
+ warnings.push({ type: 'unsupported', feature: 'responseFormat' });
328
+ }
329
+
270
330
  const openResponsesOptions = await parseProviderOptions({
271
331
  provider: this.config.providerOptionsName,
272
332
  providerOptions,
@@ -334,10 +394,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
334
394
  url: this.config.url,
335
395
  headers: combineHeaders(this.config.headers?.(), options.headers),
336
396
  body,
337
- failedResponseHandler: createJsonErrorResponseHandler({
338
- errorSchema: openResponsesErrorSchema,
339
- errorToMessage: error => error.error.message,
340
- }),
397
+ failedResponseHandler:
398
+ this.config.failedResponseHandler ?? defaultFailedResponseHandler,
341
399
  successfulResponseHandler: createJsonResponseHandler(
342
400
  // do not validate the response body, only apply types to the response body
343
401
  jsonSchema<OpenResponsesResponseBody>(() => {
@@ -349,14 +407,18 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
349
407
  });
350
408
 
351
409
  if (response.error) {
410
+ const errorMetadata = this.config.getResponseErrorMetadata?.(
411
+ response.error,
412
+ );
352
413
  throw new APICallError({
353
414
  message: response.error.message,
354
415
  url: this.config.url,
355
416
  requestBodyValues: body,
356
- statusCode: 400,
417
+ statusCode: errorMetadata?.statusCode ?? 400,
418
+ isRetryable: errorMetadata?.isRetryable,
357
419
  responseHeaders,
358
420
  responseBody: rawResponse as string,
359
- isRetryable: false,
421
+ data: response.error,
360
422
  });
361
423
  }
362
424
 
@@ -440,6 +502,20 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
440
502
  break;
441
503
  }
442
504
 
505
+ case 'custom_tool_call': {
506
+ hasToolCalls = true;
507
+ content.push({
508
+ type: 'tool-call',
509
+ toolCallId: part.call_id,
510
+ toolName: part.name,
511
+ input: JSON.stringify(part.input),
512
+ providerMetadata: {
513
+ [this.config.providerOptionsName]: { itemId: part.id },
514
+ },
515
+ });
516
+ break;
517
+ }
518
+
443
519
  default: {
444
520
  if (!isOpenResponsesExtensionItem(part)) {
445
521
  break;
@@ -464,6 +540,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
464
540
  const usage = response.usage;
465
541
  const inputTokens = usage?.input_tokens;
466
542
  const cachedInputTokens = usage?.input_tokens_details?.cached_tokens;
543
+ const cacheWriteTokens = usage?.input_tokens_details?.cache_write_tokens;
467
544
  const outputTokens = usage?.output_tokens;
468
545
  const reasoningTokens = usage?.output_tokens_details?.reasoning_tokens;
469
546
 
@@ -479,9 +556,12 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
479
556
  usage: {
480
557
  inputTokens: {
481
558
  total: inputTokens,
482
- noCache: (inputTokens ?? 0) - (cachedInputTokens ?? 0),
559
+ noCache:
560
+ (inputTokens ?? 0) -
561
+ (cachedInputTokens ?? 0) -
562
+ (cacheWriteTokens ?? 0),
483
563
  cacheRead: cachedInputTokens,
484
- cacheWrite: undefined,
564
+ cacheWrite: cacheWriteTokens,
485
565
  },
486
566
  outputTokens: {
487
567
  total: outputTokens,
@@ -515,10 +595,8 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
515
595
  ...body,
516
596
  stream: true,
517
597
  } satisfies OpenResponsesRequestBody,
518
- failedResponseHandler: createJsonErrorResponseHandler({
519
- errorSchema: openResponsesErrorSchema,
520
- errorToMessage: error => error.error.message,
521
- }),
598
+ failedResponseHandler:
599
+ this.config.failedResponseHandler ?? defaultFailedResponseHandler,
522
600
  successfulResponseHandler: createEventSourceResponseHandler(z.any()),
523
601
  abortSignal: options.abortSignal,
524
602
  fetch: this.config.fetch,
@@ -548,15 +626,20 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
548
626
  const inputTokens = responseUsage.input_tokens;
549
627
  const cachedInputTokens =
550
628
  responseUsage.input_tokens_details?.cached_tokens;
629
+ const cacheWriteTokens =
630
+ responseUsage.input_tokens_details?.cache_write_tokens;
551
631
  const outputTokens = responseUsage.output_tokens;
552
632
  const reasoningTokens =
553
633
  responseUsage.output_tokens_details?.reasoning_tokens;
554
634
 
555
635
  usage.inputTokens = {
556
636
  total: inputTokens,
557
- noCache: (inputTokens ?? 0) - (cachedInputTokens ?? 0),
637
+ noCache:
638
+ (inputTokens ?? 0) -
639
+ (cachedInputTokens ?? 0) -
640
+ (cacheWriteTokens ?? 0),
558
641
  cacheRead: cachedInputTokens,
559
- cacheWrite: undefined,
642
+ cacheWrite: cacheWriteTokens,
560
643
  };
561
644
  usage.outputTokens = {
562
645
  total: outputTokens,
@@ -576,9 +659,14 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
576
659
  string,
577
660
  { toolName?: string; toolCallId?: string; arguments?: string }
578
661
  >();
662
+ const customToolCallsByItemId = new Map<
663
+ string,
664
+ { toolName?: string; toolCallId?: string; input?: string }
665
+ >();
579
666
  const providerOptionsName = this.config.providerOptionsName;
580
667
  const extensionRegistry = this.extensionRegistry;
581
668
  const extensionStreamState = new Map<string, unknown>();
669
+ const getResponseErrorMetadata = this.config.getResponseErrorMetadata;
582
670
 
583
671
  return {
584
672
  stream: response.pipeThrough(
@@ -670,7 +758,10 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
670
758
  const toolCall = toolCallsByItemId.get(chunk.item.id);
671
759
  const toolName = toolCall?.toolName ?? chunk.item.name;
672
760
  const toolCallId = toolCall?.toolCallId ?? chunk.item.call_id;
673
- const input = toolCall?.arguments ?? chunk.item.arguments ?? '';
761
+ const input =
762
+ chunk.item.arguments !== ''
763
+ ? chunk.item.arguments
764
+ : (toolCall?.arguments ?? '');
674
765
 
675
766
  controller.enqueue({
676
767
  type: 'tool-call',
@@ -686,6 +777,67 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
686
777
  hasToolCalls = true;
687
778
 
688
779
  toolCallsByItemId.delete(chunk.item.id);
780
+ } else if (
781
+ chunk.type === 'response.output_item.added' &&
782
+ chunk.item.type === 'custom_tool_call'
783
+ ) {
784
+ customToolCallsByItemId.set(chunk.item.id, {
785
+ toolName: chunk.item.name,
786
+ toolCallId: chunk.item.call_id,
787
+ input: chunk.item.input,
788
+ });
789
+ controller.enqueue({
790
+ type: 'tool-input-start',
791
+ id: chunk.item.call_id,
792
+ toolName: chunk.item.name,
793
+ });
794
+ } else if (chunk.type === 'response.custom_tool_call_input.delta') {
795
+ const toolCall = customToolCallsByItemId.get(chunk.item_id);
796
+ if (toolCall == null) {
797
+ customToolCallsByItemId.set(chunk.item_id, {
798
+ input: chunk.delta,
799
+ });
800
+ } else {
801
+ toolCall.input = (toolCall.input ?? '') + chunk.delta;
802
+ }
803
+ controller.enqueue({
804
+ type: 'tool-input-delta',
805
+ id:
806
+ customToolCallsByItemId.get(chunk.item_id)?.toolCallId ??
807
+ chunk.item_id,
808
+ delta: chunk.delta,
809
+ });
810
+ } else if (chunk.type === 'response.custom_tool_call_input.done') {
811
+ const toolCall = customToolCallsByItemId.get(chunk.item_id);
812
+ if (toolCall == null) {
813
+ customToolCallsByItemId.set(chunk.item_id, {
814
+ input: chunk.input,
815
+ });
816
+ } else {
817
+ toolCall.input = chunk.input;
818
+ }
819
+ } else if (
820
+ chunk.type === 'response.output_item.done' &&
821
+ chunk.item.type === 'custom_tool_call'
822
+ ) {
823
+ const toolCall = customToolCallsByItemId.get(chunk.item.id);
824
+ const toolCallId = toolCall?.toolCallId ?? chunk.item.call_id;
825
+ const input =
826
+ chunk.item.input !== ''
827
+ ? chunk.item.input
828
+ : (toolCall?.input ?? '');
829
+ controller.enqueue({ type: 'tool-input-end', id: toolCallId });
830
+ controller.enqueue({
831
+ type: 'tool-call',
832
+ toolCallId,
833
+ toolName: toolCall?.toolName ?? chunk.item.name,
834
+ input: JSON.stringify(input),
835
+ providerMetadata: {
836
+ [providerOptionsName]: { itemId: chunk.item.id },
837
+ },
838
+ });
839
+ hasToolCalls = true;
840
+ customToolCallsByItemId.delete(chunk.item.id);
689
841
  } else if (
690
842
  chunk.type === 'response.output_item.done' &&
691
843
  isOpenResponsesExtensionItem(chunk.item)
@@ -804,6 +956,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
804
956
  type: chunk.type,
805
957
  error: chunk.response.error,
806
958
  data: chunk,
959
+ getResponseErrorMetadata,
807
960
  }),
808
961
  });
809
962
  }
@@ -818,6 +971,7 @@ export class OpenResponsesLanguageModel implements LanguageModelV4 {
818
971
  type: chunk.type,
819
972
  error: chunk.error,
820
973
  data: chunk,
974
+ getResponseErrorMetadata,
821
975
  }),
822
976
  });
823
977
  }
@@ -850,15 +1004,18 @@ function createOpenResponsesStreamError({
850
1004
  type,
851
1005
  error,
852
1006
  data,
1007
+ getResponseErrorMetadata,
853
1008
  }: {
854
1009
  type: 'error' | 'response.failed';
855
- error: { message: string; code: string };
1010
+ error: ResponseError;
856
1011
  data: unknown;
1012
+ getResponseErrorMetadata: OpenResponsesConfig['getResponseErrorMetadata'];
857
1013
  }) {
858
1014
  return createProviderStreamError({
859
1015
  message: error.message,
860
1016
  type,
861
1017
  code: error.code,
1018
+ ...getResponseErrorMetadata?.(error),
862
1019
  data,
863
1020
  });
864
1021
  }