@ai-sdk/openai 4.0.95 → 4.0.97

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.
@@ -720,6 +720,12 @@ the operation is idempotent, and set explicit step limits.
720
720
 
721
721
  The OpenAI responses API supports web search through the `openai.tools.webSearch` tool.
722
722
 
723
+ <Note>
724
+ `openai.tools.webSearchPreview()` is deprecated. Use
725
+ `openai.tools.webSearch()` instead. The existing `searchContextSize` and
726
+ `userLocation` options are supported by both tools.
727
+ </Note>
728
+
723
729
  ```ts
724
730
  const result = await generateText({
725
731
  model: openai('gpt-6-astra'),
@@ -1538,6 +1544,111 @@ In client mode, the flow spans two steps:
1538
1544
 
1539
1545
  For more details, see the [OpenAI Tool Search documentation](https://platform.openai.com/docs/guides/tools-tool-search).
1540
1546
 
1547
+ ##### Load tools after an ordinary tool result
1548
+
1549
+ When your own search function discovers tools, attach
1550
+ `providerOptions.openai.additionalTools` to its tool-result part. The Responses
1551
+ provider preserves the ordinary result and emits an `additional_tools` item
1552
+ immediately after it. Replaying the same messages preserves that insertion point.
1553
+
1554
+ ```ts
1555
+ import {
1556
+ openai,
1557
+ type OpenAIResponsesToolResultOptions,
1558
+ type OpenAIToolOptions,
1559
+ } from '@ai-sdk/openai';
1560
+ import { generateText, tool, type ModelMessage } from 'ai';
1561
+ import { z } from 'zod/v4';
1562
+
1563
+ const messages: ModelMessage[] = [
1564
+ { role: 'user', content: 'Find Linear and list my issues.' },
1565
+ {
1566
+ role: 'assistant',
1567
+ content: [
1568
+ {
1569
+ type: 'tool-call',
1570
+ toolCallId: 'search-1',
1571
+ toolName: 'search',
1572
+ input: {},
1573
+ },
1574
+ ],
1575
+ },
1576
+ {
1577
+ role: 'tool',
1578
+ content: [
1579
+ {
1580
+ type: 'tool-result',
1581
+ toolCallId: 'search-1',
1582
+ toolName: 'search',
1583
+ output: {
1584
+ type: 'json',
1585
+ value: { connection: 'linear', connected: true },
1586
+ },
1587
+ providerOptions: {
1588
+ openai: {
1589
+ additionalTools: [
1590
+ {
1591
+ type: 'function',
1592
+ name: 'list_issues',
1593
+ description: 'List Linear issues.',
1594
+ parameters: {
1595
+ type: 'object',
1596
+ properties: {},
1597
+ additionalProperties: false,
1598
+ },
1599
+ },
1600
+ ],
1601
+ } satisfies OpenAIResponsesToolResultOptions,
1602
+ },
1603
+ },
1604
+ ],
1605
+ },
1606
+ ];
1607
+
1608
+ const tools = {
1609
+ search: tool({ inputSchema: z.object({}) }),
1610
+ list_issues: tool({
1611
+ inputSchema: z.object({}),
1612
+ execute: async () => ({ issues: ['Example issue'] }),
1613
+ providerOptions: {
1614
+ openai: { omitFromRequestTools: true } satisfies OpenAIToolOptions,
1615
+ },
1616
+ }),
1617
+ };
1618
+
1619
+ const result = await generateText({
1620
+ model: openai.responses('gpt-5.4'),
1621
+ tools,
1622
+ messages,
1623
+ providerOptions: { openai: { store: false } },
1624
+ });
1625
+
1626
+ // Keep the original messages, including the additionalTools option, on replay.
1627
+ messages.push(...result.responseMessages);
1628
+ messages.push({ role: 'user', content: 'List my issues again.' });
1629
+ await generateText({
1630
+ model: openai.responses('gpt-5.4'),
1631
+ tools,
1632
+ messages,
1633
+ providerOptions: { openai: { store: false } },
1634
+ });
1635
+ ```
1636
+
1637
+ `additionalTools` currently accepts function definitions with `type: 'function'`,
1638
+ `name`, `parameters`, and optional `description` and `strict`. An empty array emits
1639
+ no item. These are OpenAI-format definitions; keep their names and schemas aligned
1640
+ with the SDK tools used for local validation and execution.
1641
+
1642
+ `omitFromRequestTools: true` omits only the function's top-level Responses tool
1643
+ definition. Keep the function in `tools` and, if you use `activeTools`, include it
1644
+ there too: inactive tools cannot be executed. Use automatic tool choice for these
1645
+ functions; OpenAI cannot force or allow-list a tool absent from the top-level list.
1646
+
1647
+ Both `generateText` and `streamText` support this representation. For streaming,
1648
+ append `await result.responseMessages` when replaying the conversation. This
1649
+ support is for direct `@ai-sdk/openai` Responses requests; AI Gateway support is
1650
+ not guaranteed by this provider option.
1651
+
1541
1652
  #### Custom Tool
1542
1653
 
1543
1654
  The OpenAI Responses API supports
@@ -1657,12 +1768,12 @@ canonical name — two MCP servers, for example — that name is ambiguous, so i
1657
1768
  name those tools as you declared them instead.
1658
1769
 
1659
1770
  <Note>
1660
- Three kinds of tools cannot be allow-listed, because OpenAI's `allowed_tools`
1661
- only sees the eagerly-loaded tool list: the [tool search tool](#tool-search),
1662
- tools marked with `deferLoading`, and tools grouped into a namespace. Naming
1663
- one of these in `allowedTools` removes it from the allow-list and emits a
1664
- warning; if that leaves no tools at all, the request fails with an error
1665
- instead of silently sending no restriction.
1771
+ Some tools cannot be allow-listed, because OpenAI's `allowed_tools` only sees
1772
+ the eagerly-loaded tool list: the [tool search tool](#tool-search), tools
1773
+ marked with `deferLoading` or `omitFromRequestTools`, and tools grouped into a
1774
+ namespace. Naming one of these in `allowedTools` removes it from the
1775
+ allow-list and emits a warning; if that leaves no tools at all, the request
1776
+ fails with an error instead of silently sending no restriction.
1666
1777
  </Note>
1667
1778
 
1668
1779
  <Note>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai",
3
- "version": "4.0.95",
3
+ "version": "4.0.97",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -35,8 +35,8 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@ai-sdk/provider": "4.0.28",
39
- "@ai-sdk/provider-utils": "5.0.61"
38
+ "@ai-sdk/provider": "4.0.30",
39
+ "@ai-sdk/provider-utils": "5.0.63"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -52,6 +52,8 @@ import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
52
52
 
53
53
  type OpenAIChatConfig = {
54
54
  provider: string;
55
+ /** Model ID sent to the API. Defaults to the model's reported ID. */
56
+ requestModelId?: string;
55
57
  headers?: () => Record<string, string | undefined>;
56
58
  url: (options: { modelId: string; path: string }) => string;
57
59
  fetch?: FetchFunction;
@@ -182,7 +184,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
182
184
 
183
185
  const baseArgs = {
184
186
  // model id:
185
- model: this.modelId,
187
+ model: this.config.requestModelId ?? this.modelId,
186
188
 
187
189
  // model specific settings:
188
190
  logit_bias: openaiOptions.logitBias,
@@ -404,7 +406,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
404
406
  } = await postJsonToApi({
405
407
  url: this.config.url({
406
408
  path: '/chat/completions',
407
- modelId: this.modelId,
409
+ modelId: this.config.requestModelId ?? this.modelId,
408
410
  }),
409
411
  headers: combineHeaders(this.config.headers?.(), options.headers),
410
412
  body,
@@ -504,7 +506,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
504
506
 
505
507
  const url = this.config.url({
506
508
  path: '/chat/completions',
507
- modelId: this.modelId,
509
+ modelId: this.config.requestModelId ?? this.modelId,
508
510
  });
509
511
 
510
512
  const { responseHeaders, value: response } = await postJsonToApi({
@@ -43,6 +43,8 @@ import {
43
43
  } from './openai-completion-language-model-options';
44
44
  type OpenAICompletionConfig = {
45
45
  provider: string;
46
+ /** Model ID sent to the API. Defaults to the model's reported ID. */
47
+ requestModelId?: string;
46
48
  headers?: () => Record<string, string | undefined>;
47
49
  url: (options: { modelId: string; path: string }) => string;
48
50
  fetch?: FetchFunction;
@@ -148,7 +150,7 @@ export class OpenAICompletionLanguageModel implements LanguageModelV4 {
148
150
  return {
149
151
  args: {
150
152
  // model id:
151
- model: this.modelId,
153
+ model: this.config.requestModelId ?? this.modelId,
152
154
 
153
155
  // model specific settings:
154
156
  echo: openaiOptions.echo,
@@ -192,7 +194,7 @@ export class OpenAICompletionLanguageModel implements LanguageModelV4 {
192
194
  } = await postJsonToApi({
193
195
  url: this.config.url({
194
196
  path: '/completions',
195
- modelId: this.modelId,
197
+ modelId: this.config.requestModelId ?? this.modelId,
196
198
  }),
197
199
  headers: combineHeaders(this.config.headers?.(), options.headers),
198
200
  body: args,
@@ -246,7 +248,7 @@ export class OpenAICompletionLanguageModel implements LanguageModelV4 {
246
248
 
247
249
  const url = this.config.url({
248
250
  path: '/completions',
249
- modelId: this.modelId,
251
+ modelId: this.config.requestModelId ?? this.modelId,
250
252
  });
251
253
 
252
254
  const { responseHeaders, value: response } = await postJsonToApi({
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export type { OpenAIRealtimeModelConfig as Experimental_OpenAIRealtimeModelConfi
15
15
  export type {
16
16
  OpenAILanguageModelResponsesOptions,
17
17
  OpenAIResponsesSystemMessageOptions,
18
+ OpenAIResponsesToolResultOptions,
18
19
  /** @deprecated Use `OpenAILanguageModelResponsesOptions` instead. */
19
20
  OpenAILanguageModelResponsesOptions as OpenAIResponsesProviderOptions,
20
21
  } from './responses/openai-responses-language-model-options';
@@ -11,6 +11,8 @@ type SerializedOpenAIConfig = Omit<Partial<OpenAIConfig>, 'headers'> & {
11
11
 
12
12
  export type OpenAIConfig = {
13
13
  provider: string;
14
+ /** Model ID sent to the API. Defaults to the model's reported ID. */
15
+ requestModelId?: string;
14
16
  baseURL?: string;
15
17
  url: (options: { modelId: string; path: string }) => string;
16
18
  headers?: () => OpenAIHeaders;
@@ -180,7 +180,10 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
180
180
  },
181
181
  ],
182
182
  questions: Object.entries(questions).map(([name, question]) => {
183
- const instructions = toText(question.instructions);
183
+ const instructions =
184
+ question.instructions == null
185
+ ? question.instructions
186
+ : toText(question.instructions);
184
187
  switch (question.type) {
185
188
  case 'boolean':
186
189
  return {
@@ -195,7 +198,7 @@ export class DecisionOpenAIModel implements DecisionModelV4 {
195
198
  ? undefined
196
199
  : `Criteria for false:\n${toText(question.criteria.false)}`,
197
200
  ]
198
- .filter(part => part !== undefined)
201
+ .filter(part => part != null)
199
202
  .join('\n\n'),
200
203
  };
201
204
  case 'choice':
@@ -107,6 +107,8 @@ export const openaiTools = {
107
107
  * Web search allows models to access up-to-date information from the internet
108
108
  * and provide answers with sourced citations.
109
109
  *
110
+ * @deprecated Use `webSearch` instead.
111
+ *
110
112
  * @param searchContextSize - The search context size to use for the web search.
111
113
  * @param userLocation - The user location to use for the web search.
112
114
  */
@@ -20,7 +20,10 @@ import {
20
20
  type ToolNameMapping,
21
21
  } from '@ai-sdk/provider-utils';
22
22
  import { z } from 'zod/v4';
23
- import { openaiResponsesSystemMessageOptionsSchema } from './openai-responses-language-model-options';
23
+ import {
24
+ openaiResponsesSystemMessageOptionsSchema,
25
+ openaiResponsesToolResultOptionsSchema,
26
+ } from './openai-responses-language-model-options';
24
27
  import {
25
28
  applyPatchInputSchema,
26
29
  applyPatchOutputSchema,
@@ -1794,6 +1797,19 @@ export async function convertToOpenAIResponsesInput({
1794
1797
  output: contentValue,
1795
1798
  ...(caller != null && { caller }),
1796
1799
  });
1800
+
1801
+ const options = await parseProviderOptions({
1802
+ provider: providerOptionsName,
1803
+ providerOptions: part.providerOptions,
1804
+ schema: openaiResponsesToolResultOptionsSchema,
1805
+ });
1806
+ if (options?.additionalTools?.length) {
1807
+ input.push({
1808
+ type: 'additional_tools',
1809
+ role: 'developer',
1810
+ tools: options.additionalTools,
1811
+ });
1812
+ }
1797
1813
  }
1798
1814
 
1799
1815
  break;
@@ -6,6 +6,7 @@ import {
6
6
  type InferSchema,
7
7
  } from '@ai-sdk/provider-utils';
8
8
  import { z } from 'zod/v4';
9
+ import type { OpenAIResponsesToolResultOptions } from './openai-responses-language-model-options';
9
10
 
10
11
  const jsonValueSchema: z.ZodType<JSONValue> = z.lazy(() =>
11
12
  z.union([
@@ -162,6 +163,11 @@ export type OpenAIResponsesInputItem =
162
163
  | OpenAIResponsesAssistantMessage
163
164
  | OpenAIResponsesFunctionCall
164
165
  | OpenAIResponsesFunctionCallOutput
166
+ | {
167
+ type: 'additional_tools';
168
+ role: 'developer';
169
+ tools: NonNullable<OpenAIResponsesToolResultOptions['additionalTools']>;
170
+ }
165
171
  | OpenAIResponsesProgram
166
172
  | OpenAIResponsesProgramOutput
167
173
  | OpenAIResponsesCustomToolCall
@@ -457,3 +457,26 @@ export const openaiResponsesSystemMessageOptionsSchema = lazySchema(() =>
457
457
  export type OpenAIResponsesSystemMessageOptions = InferSchema<
458
458
  typeof openaiResponsesSystemMessageOptionsSchema
459
459
  >;
460
+
461
+ export const openaiResponsesToolResultOptionsSchema = lazySchema(() =>
462
+ zodSchema(
463
+ z.object({
464
+ /** Function definitions to load immediately after this ordinary tool result. */
465
+ additionalTools: z
466
+ .array(
467
+ z.strictObject({
468
+ type: z.literal('function'),
469
+ name: z.string().min(1),
470
+ description: z.string().optional(),
471
+ parameters: z.record(z.string(), z.json()),
472
+ strict: z.boolean().optional(),
473
+ }),
474
+ )
475
+ .optional(),
476
+ }),
477
+ ),
478
+ );
479
+
480
+ export type OpenAIResponsesToolResultOptions = InferSchema<
481
+ typeof openaiResponsesToolResultOptionsSchema
482
+ >;
@@ -598,7 +598,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
598
598
  }
599
599
 
600
600
  const baseArgs = {
601
- model: modelId,
601
+ model: config.requestModelId ?? modelId,
602
602
  input,
603
603
  temperature,
604
604
  top_p: topP,
@@ -854,7 +854,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
854
854
  } = await this.getArgs(options);
855
855
  const url = this.config.url({
856
856
  path: '/responses',
857
- modelId: this.modelId,
857
+ modelId: this.config.requestModelId ?? this.modelId,
858
858
  });
859
859
 
860
860
  const approvalRequestIdToDummyToolCallIdFromPrompt =
@@ -1547,7 +1547,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1547
1547
 
1548
1548
  const url = this.config.url({
1549
1549
  path: '/responses',
1550
- modelId: this.modelId,
1550
+ modelId: this.config.requestModelId ?? this.modelId,
1551
1551
  });
1552
1552
 
1553
1553
  const { responseHeaders, value: response } = await postJsonToApi({
@@ -40,6 +40,12 @@ export type OpenAIToolOptions = {
40
40
  */
41
41
  async?: boolean;
42
42
  deferLoading?: boolean;
43
+ /**
44
+ * Omit this function from the Responses request's top-level tools array.
45
+ * Use for tools supplied in history through additionalTools. Keep the tool
46
+ * active in the SDK so its calls can still be validated and executed.
47
+ */
48
+ omitFromRequestTools?: boolean;
43
49
  outputSchema?: JSONObject;
44
50
  namespace?: {
45
51
  name: string;
@@ -145,6 +151,27 @@ export async function prepareResponsesTools({
145
151
  if (openaiOptions?.outputSchema != null) {
146
152
  outputSchemaToolNames?.add(tool.name);
147
153
  }
154
+ if (openaiOptions?.omitFromRequestTools === true) {
155
+ recordAllowedTool(
156
+ tool.name,
157
+ {
158
+ supported: false,
159
+ reason:
160
+ 'tools omitted from the request tools array cannot be allow-listed',
161
+ },
162
+ undefined,
163
+ );
164
+ if (
165
+ allowedTools == null &&
166
+ toolChoice?.type === 'tool' &&
167
+ toolChoice.toolName === tool.name
168
+ ) {
169
+ throw new UnsupportedFunctionalityError({
170
+ functionality: `forcing tool "${tool.name}" omitted from the request tools array; use automatic tool choice for history-loaded tools`,
171
+ });
172
+ }
173
+ break;
174
+ }
148
175
  const openaiFunctionTool = prepareFunctionTool({
149
176
  tool,
150
177
  options: openaiOptions,