@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.
@@ -52,15 +52,31 @@ You can use the following optional settings to customize the Open Responses prov
52
52
 
53
53
  API key that is sent as a bearer token in the `Authorization` header.
54
54
 
55
- - **headers** _Record<string,string>_
55
+ - **headers** _Record<string,string> | () => Record<string,string>_
56
56
 
57
- Custom headers to include in the requests.
57
+ Custom headers to include in requests. Pass a function when headers must be
58
+ resolved lazily for each request.
58
59
 
59
60
  - **fetch** _(input: RequestInfo, init?: RequestInit) => Promise<Response>_
60
61
 
61
62
  Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
62
63
  Defaults to the global `fetch` function.
63
64
 
65
+ - **failedResponseHandler** _ResponseHandler<APICallError>_
66
+
67
+ Custom handler for non-successful HTTP responses. Use this when an endpoint
68
+ returns an error envelope that differs from the default OpenAI-compatible
69
+ `{ error: { message, type, param, code } }` shape.
70
+
71
+ - **getResponseErrorMetadata** _function_
72
+
73
+ Extracts optional `statusCode` and `isRetryable` metadata from a response error
74
+ object, including endpoint-specific fields. Applies to generation failures and
75
+ streaming `response.failed` and `error` events. The standard Open Responses
76
+ error fields are `code` and `message`; endpoint-specific status fields require
77
+ this callback. When only a status code is supplied, the SDK derives retryability
78
+ from it. HTTP failures are handled separately by `failedResponseHandler`.
79
+
64
80
  - **strictResponseInput** _boolean_
65
81
 
66
82
  Serializes assistant history using strict OpenAI Responses input schemas.
@@ -68,6 +84,21 @@ You can use the following optional settings to customize the Open Responses prov
68
84
  messages with a genuine provider item ID are sent as complete output items.
69
85
  Defaults to `false`.
70
86
 
87
+ - **customToolId** _`${string}.${string}`_
88
+
89
+ Provider-tool ID used by `provider.tools.customTool`. Defaults to
90
+ `open-responses.custom`.
91
+
92
+ - **structuredOutputs** _boolean_
93
+
94
+ Whether JSON response formats are sent to the endpoint. When `false`,
95
+ structured-output requests are omitted with an unsupported warning.
96
+ Defaults to `true`.
97
+
98
+ - **userAgentSuffix** _string_
99
+
100
+ Overrides the default `ai-sdk/open-responses/<version>` user-agent suffix.
101
+
71
102
  ### Endpoint Examples
72
103
 
73
104
  For a local LM Studio server that does not require authentication:
@@ -199,6 +230,39 @@ combined with either reasoning effort setting. Reasoning support and accepted
199
230
  effort values depend on the endpoint and model; unsupported values may be
200
231
  rejected or ignored by the endpoint.
201
232
 
233
+ ## Custom Tools
234
+
235
+ Endpoints with OpenAI-compatible custom-tool support can call tools with raw
236
+ string inputs, optionally constrained by a regex or Lark grammar. These tools
237
+ execute in your application:
238
+
239
+ ```ts
240
+ import { generateText, isStepCount } from 'ai';
241
+
242
+ const result = await generateText({
243
+ model: openResponses('your-model-id'),
244
+ prompt: 'Write a SQL query that lists active users.',
245
+ tools: {
246
+ write_sql: openResponses.tools.customTool({
247
+ description: 'Return a SQL SELECT query.',
248
+ format: {
249
+ type: 'grammar',
250
+ syntax: 'regex',
251
+ definition: 'SELECT .+',
252
+ },
253
+ execute: async sql => runReadOnlyQuery(sql),
254
+ }),
255
+ },
256
+ stopWhen: isStepCount(3),
257
+ });
258
+ ```
259
+
260
+ The endpoint must support the OpenAI-compatible `custom` tool,
261
+ `custom_tool_call`, and `custom_tool_call_output` wire types, as OpenAI and
262
+ QuiverAI do. These are compatibility extensions, not part of the Open Responses
263
+ 2026-04-24 specification. Custom-tool results can include text, images, and files
264
+ when supported by the endpoint.
265
+
202
266
  ## File Inputs
203
267
 
204
268
  The provider supports image and non-image file inputs in user messages. Images
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/open-responses",
3
- "version": "2.0.51",
3
+ "version": "2.0.54",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@ai-sdk/provider": "4.0.18",
33
- "@ai-sdk/provider-utils": "5.0.47"
33
+ "@ai-sdk/provider-utils": "5.0.49"
34
34
  },
35
35
  "devDependencies": {
36
- "@ai-sdk/test-server": "2.0.1",
36
+ "@ai-sdk/test-server": "2.0.2",
37
37
  "@types/node": "22.19.19",
38
38
  "@vercel/ai-tsconfig": "0.0.0",
39
39
  "tsup": "^8.5.1",
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export type {
4
4
  OpenResponsesProvider,
5
5
  OpenResponsesProviderSettings,
6
6
  } from './open-responses-provider';
7
+ export type { OpenResponsesCustomToolOptions } from './open-responses-tools';
7
8
  export type {
8
9
  OpenResponsesExtension as Experimental_OpenResponsesExtension,
9
10
  OpenResponsesExtensionContentPart as Experimental_OpenResponsesExtensionContentPart,
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type APICallError,
2
3
  NoSuchModelError,
3
4
  type LanguageModelV4,
4
5
  type ProviderV4,
@@ -7,16 +8,21 @@ import {
7
8
  generateId,
8
9
  withUserAgentSuffix,
9
10
  type FetchFunction,
11
+ type ResponseHandler,
10
12
  } from '@ai-sdk/provider-utils';
11
13
  import {
12
14
  createOpenResponsesExtensionRegistry,
13
15
  type OpenResponsesExtension,
14
16
  } from './open-responses-extension';
17
+ import { createOpenResponsesTools } from './open-responses-tools';
15
18
  import { OpenResponsesLanguageModel } from './responses/open-responses-language-model';
16
19
  import { VERSION } from './version';
20
+ import type { OpenResponsesConfig } from './responses/open-responses-config';
17
21
 
18
22
  export interface OpenResponsesProvider extends ProviderV4 {
19
23
  (modelId: string): LanguageModelV4;
24
+
25
+ tools: ReturnType<typeof createOpenResponsesTools>;
20
26
  }
21
27
 
22
28
  export interface OpenResponsesProviderSettings {
@@ -38,7 +44,7 @@ export interface OpenResponsesProviderSettings {
38
44
  /**
39
45
  * Custom headers to include in the requests.
40
46
  */
41
- headers?: Record<string, string>;
47
+ headers?: Record<string, string> | (() => Record<string, string>);
42
48
 
43
49
  /**
44
50
  * Custom fetch implementation. You can use it as a middleware to intercept requests,
@@ -46,6 +52,17 @@ export interface OpenResponsesProviderSettings {
46
52
  */
47
53
  fetch?: FetchFunction;
48
54
 
55
+ /**
56
+ * Custom handler for non-successful HTTP responses.
57
+ */
58
+ failedResponseHandler?: ResponseHandler<APICallError>;
59
+
60
+ /**
61
+ * Extracts HTTP status and retryability metadata from endpoint-specific
62
+ * response errors. Applies to both generation and streaming errors.
63
+ */
64
+ getResponseErrorMetadata?: OpenResponsesConfig['getResponseErrorMetadata'];
65
+
49
66
  /**
50
67
  * Whether to serialize assistant history using the strict OpenAI Responses
51
68
  * input schemas. Assistant messages without an item ID are sent as easy input
@@ -55,6 +72,28 @@ export interface OpenResponsesProviderSettings {
55
72
  */
56
73
  strictResponseInput?: boolean;
57
74
 
75
+ /**
76
+ * Provider-tool ID used for caller-executed Open Responses custom tools.
77
+ *
78
+ * @default 'open-responses.custom'
79
+ */
80
+ customToolId?: `${string}.${string}`;
81
+
82
+ /**
83
+ * Whether JSON response formats are sent to the endpoint. When disabled,
84
+ * structured output requests produce an unsupported warning and are omitted.
85
+ *
86
+ * @default true
87
+ */
88
+ structuredOutputs?: boolean;
89
+
90
+ /**
91
+ * User-agent suffix for requests.
92
+ *
93
+ * @default `ai-sdk/open-responses/<version>`
94
+ */
95
+ userAgentSuffix?: string;
96
+
58
97
  /**
59
98
  * Codecs for Open Responses extension tools, items, and streaming events.
60
99
  *
@@ -67,6 +106,7 @@ export function createOpenResponses(
67
106
  options: OpenResponsesProviderSettings,
68
107
  ): OpenResponsesProvider {
69
108
  const providerName = options.name;
109
+ const customToolId = options.customToolId ?? 'open-responses.custom';
70
110
  const extensionRegistry = createOpenResponsesExtensionRegistry(
71
111
  options.experimental_extensions,
72
112
  );
@@ -79,9 +119,11 @@ export function createOpenResponses(
79
119
  Authorization: `Bearer ${options.apiKey}`,
80
120
  }
81
121
  : {}),
82
- ...options.headers,
122
+ ...(typeof options.headers === 'function'
123
+ ? options.headers()
124
+ : options.headers),
83
125
  },
84
- `ai-sdk/open-responses/${VERSION}`,
126
+ options.userAgentSuffix ?? `ai-sdk/open-responses/${VERSION}`,
85
127
  );
86
128
 
87
129
  const createResponsesModel = (modelId: string) => {
@@ -91,9 +133,13 @@ export function createOpenResponses(
91
133
  headers: getHeaders,
92
134
  url: options.url,
93
135
  fetch: options.fetch,
136
+ failedResponseHandler: options.failedResponseHandler,
137
+ getResponseErrorMetadata: options.getResponseErrorMetadata,
94
138
  generateId: () => generateId(),
95
139
  extensionRegistry,
96
140
  strictResponseInput: options.strictResponseInput,
141
+ customToolId,
142
+ structuredOutputs: options.structuredOutputs,
97
143
  });
98
144
  };
99
145
 
@@ -113,6 +159,7 @@ export function createOpenResponses(
113
159
 
114
160
  provider.specificationVersion = 'v4' as const;
115
161
  provider.languageModel = createLanguageModel;
162
+ provider.tools = createOpenResponsesTools({ customToolId });
116
163
 
117
164
  provider.embeddingModel = (modelId: string) => {
118
165
  throw new NoSuchModelError({ modelId, modelType: 'embeddingModel' });
@@ -0,0 +1,52 @@
1
+ import {
2
+ createProviderDefinedToolFactory,
3
+ lazySchema,
4
+ zodSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+
8
+ export type OpenResponsesCustomToolOptions = {
9
+ /**
10
+ * An optional description of what the custom tool does.
11
+ */
12
+ description?: string;
13
+
14
+ /**
15
+ * The raw text format returned by the model.
16
+ */
17
+ format?:
18
+ | {
19
+ type: 'grammar';
20
+ syntax: 'regex' | 'lark';
21
+ definition: string;
22
+ }
23
+ | {
24
+ type: 'text';
25
+ };
26
+ };
27
+
28
+ const customInputSchema = lazySchema(() => zodSchema(z.string()));
29
+
30
+ export function createOpenResponsesTools({
31
+ customToolId,
32
+ }: {
33
+ customToolId: `${string}.${string}`;
34
+ }) {
35
+ const customToolFactory = createProviderDefinedToolFactory<
36
+ string,
37
+ OpenResponsesCustomToolOptions
38
+ >({
39
+ id: customToolId,
40
+ inputSchema: customInputSchema,
41
+ });
42
+
43
+ return {
44
+ /**
45
+ * Creates a caller-executed OpenAI-compatible custom tool. Custom tools accept
46
+ * a raw string, optionally constrained by a grammar. Requires endpoint support.
47
+ */
48
+ customTool: (
49
+ options: Parameters<typeof customToolFactory>[0],
50
+ ): ReturnType<typeof customToolFactory> => customToolFactory(options),
51
+ };
52
+ }
@@ -32,12 +32,14 @@ export async function convertToOpenResponsesInput({
32
32
  extensionRegistry,
33
33
  providerToolsByName = new Map(),
34
34
  strictResponseInput = false,
35
+ customToolId,
35
36
  }: {
36
37
  prompt: LanguageModelV4Prompt;
37
38
  providerOptionsName?: string;
38
39
  extensionRegistry?: OpenResponsesExtensionRegistry;
39
40
  providerToolsByName?: Map<string, LanguageModelV4ProviderTool>;
40
41
  strictResponseInput?: boolean;
42
+ customToolId?: `${string}.${string}`;
41
43
  }): Promise<{
42
44
  input: OpenResponsesRequestBody['input'];
43
45
  instructions: string | undefined;
@@ -312,13 +314,24 @@ export async function convertToOpenResponsesInput({
312
314
  ? providerData.itemId
313
315
  : undefined;
314
316
 
315
- input.push({
316
- type: 'function_call',
317
- ...(itemId != null && { id: itemId }),
318
- call_id: part.toolCallId,
319
- name: part.toolName,
320
- arguments: argumentsValue,
321
- });
317
+ const providerTool = providerToolsByName.get(part.toolName);
318
+ if (customToolId != null && providerTool?.id === customToolId) {
319
+ input.push({
320
+ type: 'custom_tool_call',
321
+ ...(itemId != null && { id: itemId }),
322
+ call_id: part.toolCallId,
323
+ name: part.toolName,
324
+ input: argumentsValue,
325
+ });
326
+ } else {
327
+ input.push({
328
+ type: 'function_call',
329
+ ...(itemId != null && { id: itemId }),
330
+ call_id: part.toolCallId,
331
+ name: part.toolName,
332
+ arguments: argumentsValue,
333
+ });
334
+ }
322
335
  break;
323
336
  }
324
337
  }
@@ -460,7 +473,10 @@ export async function convertToOpenResponsesInput({
460
473
  }
461
474
 
462
475
  input.push({
463
- type: 'function_call_output',
476
+ type:
477
+ customToolId != null && providerTool?.id === customToolId
478
+ ? 'custom_tool_call_output'
479
+ : 'function_call_output',
464
480
  call_id: part.toolCallId,
465
481
  output: contentValue,
466
482
  });
@@ -12,9 +12,9 @@ export const openResponsesErrorSchema = lazySchema(() =>
12
12
  z.object({
13
13
  error: z.object({
14
14
  message: z.string(),
15
- type: z.string(),
16
- param: z.string(),
17
- code: z.string(),
15
+ type: z.string().nullish(),
16
+ param: z.string().nullish(),
17
+ code: z.string().nullish(),
18
18
  }),
19
19
  }),
20
20
  ),
@@ -231,6 +231,23 @@ export type FunctionCallOutputItemParam = {
231
231
  status?: FunctionCallStatus;
232
232
  };
233
233
 
234
+ export type CustomToolCallItemParam = {
235
+ id?: string;
236
+ call_id: string;
237
+ type: 'custom_tool_call';
238
+ name: string;
239
+ input: string;
240
+ status?: FunctionCallStatus;
241
+ };
242
+
243
+ export type CustomToolCallOutputItemParam = {
244
+ id?: string;
245
+ call_id: string;
246
+ type: 'custom_tool_call_output';
247
+ output: FunctionCallOutputItemParam['output'];
248
+ status?: FunctionCallStatus;
249
+ };
250
+
234
251
  // ============================================================================
235
252
  // Tool Types
236
253
  // ============================================================================
@@ -246,6 +263,19 @@ export type FunctionToolParam = {
246
263
  type: 'function';
247
264
  };
248
265
 
266
+ export type CustomToolParam = {
267
+ name: string;
268
+ description?: string;
269
+ format?:
270
+ | { type: 'text' }
271
+ | {
272
+ type: 'grammar';
273
+ syntax: 'regex' | 'lark';
274
+ definition: string;
275
+ };
276
+ type: 'custom';
277
+ };
278
+
249
279
  /**
250
280
  * A specific function tool choice.
251
281
  */
@@ -254,6 +284,11 @@ export type SpecificFunctionParam = {
254
284
  name: string;
255
285
  };
256
286
 
287
+ export type SpecificCustomToolParam = {
288
+ type: 'custom';
289
+ name: string;
290
+ };
291
+
257
292
  /**
258
293
  * Allowed tools parameter.
259
294
  */
@@ -269,6 +304,7 @@ export type AllowedToolsParam = {
269
304
  export type ToolChoiceParam =
270
305
  | ToolChoiceValueEnum
271
306
  | SpecificFunctionParam
307
+ | SpecificCustomToolParam
272
308
  | AllowedToolsParam
273
309
  | OpenResponsesExtensionRecord;
274
310
 
@@ -482,6 +518,23 @@ export type FunctionCallOutput = {
482
518
  status: FunctionCallStatus;
483
519
  };
484
520
 
521
+ export type CustomToolCall = {
522
+ type: 'custom_tool_call';
523
+ id: string;
524
+ call_id: string;
525
+ name: string;
526
+ input: string;
527
+ status: FunctionCallStatus;
528
+ };
529
+
530
+ export type CustomToolCallOutput = {
531
+ type: 'custom_tool_call_output';
532
+ id: string;
533
+ call_id: string;
534
+ output: string;
535
+ status: FunctionCallStatus;
536
+ };
537
+
485
538
  /**
486
539
  * A reasoning item that was generated by the model.
487
540
  */
@@ -499,6 +552,8 @@ export type ReasoningBody = {
499
552
  export type OutputItem =
500
553
  | FunctionCall
501
554
  | FunctionCallOutput
555
+ | CustomToolCall
556
+ | CustomToolCallOutput
502
557
  | Message
503
558
  | ReasoningBody
504
559
  | OpenResponsesExtensionItem;
@@ -516,6 +571,7 @@ export type IncompleteDetails = {
516
571
  export type ResponseError = {
517
572
  code: string;
518
573
  message: string;
574
+ [key: string]: unknown;
519
575
  };
520
576
 
521
577
  /**
@@ -588,6 +644,7 @@ export type TextField = {
588
644
  */
589
645
  export type InputTokensDetails = {
590
646
  cached_tokens: number;
647
+ cache_write_tokens?: number;
591
648
  };
592
649
 
593
650
  /**
@@ -644,6 +701,8 @@ export type OpenResponsesRequestBody = {
644
701
  | AssistantMessageItemParam
645
702
  | FunctionCallItemParam
646
703
  | FunctionCallOutputItemParam
704
+ | CustomToolCallItemParam
705
+ | CustomToolCallOutputItemParam
647
706
  | OpenResponsesExtensionRecord
648
707
  >;
649
708
 
@@ -662,7 +721,9 @@ export type OpenResponsesRequestBody = {
662
721
  /**
663
722
  * A list of tools that the model may call while generating the response.
664
723
  */
665
- tools?: Array<FunctionToolParam | OpenResponsesExtensionRecord>;
724
+ tools?: Array<
725
+ FunctionToolParam | CustomToolParam | OpenResponsesExtensionRecord
726
+ >;
666
727
 
667
728
  /**
668
729
  * Controls which tool the model should use, if any.
@@ -1150,6 +1211,22 @@ export type ResponseFunctionCallArgumentsDoneEvent = {
1150
1211
  arguments: string;
1151
1212
  };
1152
1213
 
1214
+ export type ResponseCustomToolCallInputDeltaEvent = {
1215
+ type: 'response.custom_tool_call_input.delta';
1216
+ sequence_number: number;
1217
+ item_id: string;
1218
+ output_index: number;
1219
+ delta: string;
1220
+ };
1221
+
1222
+ export type ResponseCustomToolCallInputDoneEvent = {
1223
+ type: 'response.custom_tool_call_input.done';
1224
+ sequence_number: number;
1225
+ item_id: string;
1226
+ output_index: number;
1227
+ input: string;
1228
+ };
1229
+
1153
1230
  // ----------------------------------------------------------------------------
1154
1231
  // Delta Events - Reasoning
1155
1232
  // ----------------------------------------------------------------------------
@@ -1248,6 +1325,8 @@ export type OpenResponsesChunk =
1248
1325
  // Delta Events - Function Call Arguments
1249
1326
  | ResponseFunctionCallArgumentsDeltaEvent
1250
1327
  | ResponseFunctionCallArgumentsDoneEvent
1328
+ | ResponseCustomToolCallInputDeltaEvent
1329
+ | ResponseCustomToolCallInputDoneEvent
1251
1330
  // Delta Events - Reasoning
1252
1331
  | ResponseReasoningSummaryTextDeltaEvent
1253
1332
  | ResponseReasoningSummaryTextDoneEvent
@@ -1,5 +1,7 @@
1
- import type { FetchFunction } from '@ai-sdk/provider-utils';
1
+ import type { APICallError } from '@ai-sdk/provider';
2
+ import type { FetchFunction, ResponseHandler } from '@ai-sdk/provider-utils';
2
3
  import type { OpenResponsesExtensionRegistry } from '../open-responses-extension';
4
+ import type { ResponseError } from './open-responses-api';
3
5
 
4
6
  export type OpenResponsesConfig = {
5
7
  provider: string;
@@ -7,7 +9,14 @@ export type OpenResponsesConfig = {
7
9
  url: string;
8
10
  headers?: () => Record<string, string | undefined>;
9
11
  fetch?: FetchFunction;
12
+ failedResponseHandler?: ResponseHandler<APICallError>;
13
+ getResponseErrorMetadata?: (error: ResponseError) => {
14
+ statusCode?: number;
15
+ isRetryable?: boolean;
16
+ };
10
17
  generateId: () => string;
11
18
  extensionRegistry?: OpenResponsesExtensionRegistry;
12
19
  strictResponseInput?: boolean;
20
+ customToolId?: `${string}.${string}`;
21
+ structuredOutputs?: boolean;
13
22
  };