@ai-sdk/openai 4.0.94 → 4.0.96

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.94",
3
+ "version": "4.0.96",
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.27",
39
- "@ai-sdk/provider-utils": "5.0.60"
38
+ "@ai-sdk/provider": "4.0.29",
39
+ "@ai-sdk/provider-utils": "5.0.62"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
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';
@@ -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
+ >;
@@ -1739,14 +1739,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1739
1739
  type: 'tool-input-start',
1740
1740
  id: value.item.call_id,
1741
1741
  toolName,
1742
- });
1743
-
1744
- // Custom tool input is a string, so stream its JSON encoding
1745
- // consistently with the final tool call input.
1746
- controller.enqueue({
1747
- type: 'tool-input-delta',
1748
- id: value.item.call_id,
1749
- delta: '"',
1742
+ inputEncoding: 'text',
1750
1743
  });
1751
1744
  } else if (value.item.type === 'web_search_call') {
1752
1745
  ongoingToolCalls[value.output_index] = {
@@ -2146,12 +2139,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2146
2139
  value.item.name,
2147
2140
  );
2148
2141
 
2149
- controller.enqueue({
2150
- type: 'tool-input-delta',
2151
- id: value.item.call_id,
2152
- delta: '"',
2153
- });
2154
-
2155
2142
  controller.enqueue({
2156
2143
  type: 'tool-input-end',
2157
2144
  id: value.item.call_id,
@@ -2649,7 +2636,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
2649
2636
  controller.enqueue({
2650
2637
  type: 'tool-input-delta',
2651
2638
  id: toolCall.toolCallId,
2652
- delta: escapeJSONDelta(value.delta),
2639
+ delta: value.delta,
2653
2640
  });
2654
2641
  }
2655
2642
  } else if (isResponseApplyPatchCallOperationDiffDeltaChunk(value)) {
@@ -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,