Repository: vercel/ai (TypeScript)

Issue:
Item 'rs_...' of type 'reasoning' was provided without its required following item
### Description

When using the `openai.responses()` API with a reasoning model, a tool call results in an error.  
However, using `openai.chat()` avoids the error — but **tool calling and reasoning streaming are not available** in that mode.

---

### Code

```ts
import { openai } from "@ai-sdk/openai";
import { streamText, Output, stepCountIs, convertToModelMessages } from "ai";
import { devTools } from "../tools";
import { z } from "zod";

// Allow streaming responses up to 60 seconds for more complex generations
export const maxDuration = 60;

export function errorHandler(error: unknown) {
  if (error == null) {
    return "unknown error";
  }
  if (typeof error === "string") {
    return error;
  }
  if (error instanceof Error) {
    return error.message;
  }
  return JSON.stringify(error);
}

// Structured output schema for website code
const WebsiteSchema = z.object({
  html: z.string().describe("Whole HTML code"),
});

export async function POST(req: Request) {
  try {
    const { messages } = await req.json();

    // Validate messages
    if (!Array.isArray(messages)) {
      return new Response("Invalid messages format", { status: 400 });
    }

    const result = streamText({
      model: openai.responses(process.env.OPENAI_MODEL || "gpt-4o"),
      system: "...",
      messages: convertToModelMessages(messages),
      stopWhen: stepCountIs(100),
      tools: devTools,
      abortSignal: req.signal, // Add abort signal for proper cancellation
      experimental_output: Output.object({
        schema: WebsiteSchema,
      }),
    });

    return result.toUIMessageStreamResponse({
      onError: errorHandler,
    });
  } catch (error) {
    console.error("Dev API error:", error);
    return new Response(errorHandler(error), { status: 500 });
  }
}
```

---

### Problem

When using reasoning models with `openai.responses()`, calling a tool throws the following error:

```
[Error [AI_APICallError]: Item 'rs_...' of type 'reasoning' was provided without its required following item.] {
  cause: undefined,
  url: 'https://api.openai.com/v1/responses',
  requestBodyValues: [Object],
  statusCode: 400,
  responseHeaders: [Object],
  responseBody: '{
    "error": {
      "message": "Item 'rs_...' of type 'reasoning' was provided without its required following item.",
      "type": "invalid_request_error",
      "param": "input",
      "code": null
    }
  }',
  isRetryable: false,
  data: [Object]
}
```

---

### Workaround

Using `openai.chat()` avoids the error entirely, but **does not support streaming reasoning or tool calls**, which limits its utility in certain applications.

---

### AI SDK Versions

- `ai`: `^5.0.0-beta.7`  
- `@ai-sdk/openai`: `^2.0.0-beta.5`  
- `@ai-sdk/react`: `^2.0.0-beta.7`



Discussion: @lgrammel is there any hot-fix for this issue?
Hi can you check the latest ai sdk 5 beta and latest ai sdk 5 beta openai provider? we made fixes in that area
This is still happening in the latest beta releases
@lgrammel still there
```[Error [AI_APICallError]: Item 'rs_68705394944881a0bad1de4b641bb7a80d2db2349dd4408e' of type 'reasoning' was provided without its required following item.] {
  cause: undefined,
  url: 'https://api.openai.com/v1/responses',
  requestBodyValues: [Object],
  statusCode: 400,
  responseHeaders: [Object],
  responseBody: '{\n' +
    '  "error": {\n' +
    `    "message": "Item 'rs_68705394944881a0bad1de4b641bb7a80d2db2349dd4408e' of type 'reasoning' was provided without its required following item.",\n` +
    '    "type": "invalid_request_error",\n' +
    '    "param": "input",\n' +
    '    "code": null\n' +
    '  }\n' +
    '}',
  isRetryable: false,
  data: [Object]
}```
I am also experiencing the same issue and am on the latest version of the v5 beta.
is there any update?
we're seeing this as well in opencode
ai: "^5.0.0-beta.16" still have the issue 
@thdxr I am facing this while using opencode too. It seems the issue is at openai's end and the workaround is to set reasoning-effort=none Related: https://github.com/openai/codex/issues/1498
Starting to look into this, suspect it is related to tool calling and openai item ids.

Can someone share a minimal reproduction that leaves nothing out?
The following example works without issues with tool calling:

```ts
import { openai, OpenAIResponsesProviderOptions } from '@ai-sdk/openai';
import { stepCountIs, ModelMessage, streamText, tool } from 'ai';
import 'dotenv/config';
import * as readline from 'node:readline/promises';
import { z } from 'zod/v4';

const terminal = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const messages: ModelMessage[] = [];

async function main() {
  while (true) {
    const userInput = await terminal.question('You: ');

    messages.push({ role: 'user', content: userInput });

    const result = streamText({
      model: openai.responses('o3'),
      tools: {
        weather: tool({
          description: 'Get the weather in a location',
          inputSchema: z.object({
            location: z
              .string()
              .describe('The location to get the weather for'),
          }),
          execute: async ({ location }) => ({
            location,
            temperature: 72 + Math.floor(Math.random() * 21) - 10,
          }),
        }),
      },
      stopWhen: stepCountIs(5),
      messages,
      providerOptions: {
        openai: {
          store: false, // No data retention - makes interaction stateless
          reasoningEffort: 'medium',
          reasoningSummary: 'auto', // output reasoning
          include: ['reasoning.encrypted_content'], // Hence, we need to retrieve the model's encrypted reasoning to be able to pass it to follow-up requests
        } satisfies OpenAIResponsesProviderOptions,
      },
    });

    process.stdout.write('\nAssistant: ');
    for await (const delta of result.textStream) {
      process.stdout.write(delta);
    }
    process.stdout.write('\n\n');

    messages.push(...(await result.response).messages);
  }
}

main().catch(console.error);
```
@hamidrezaborghei can you try setting the following provider options?

```ts
 const result = streamText({
    // ....
    providerOptions: {
      openai: {
        store: false, // No data retention - makes interaction stateless
        reasoningEffort: 'medium',
        reasoningSummary: 'auto', // output reasoning
        include: ['reasoning.encrypted_content'], // Hence, we need to retrieve the model's encrypted reasoning to be able to pass it to follow-up requests
      } satisfies OpenAIResponsesProviderOptions,
    }
})
```
Problem reproduced. Tool calling with o3 and no provider options results in this error.
Most likely the issue is that function calls in reasoning sequences require a call_id and an item id.
#7285 is a basic fix for stream text. plan to expand to generatetext, test, and ship tomorrow.
Thank you for your efforts to solve the problem, but the problem is only solved for the first message, and when we send the second message, we see the exact same problem again.
I think we should reopen the issue
@lgrammel 
@hamidrezaborghei i tested this with several messages. can you provide a complete code snippet that reproduces the problem?
@dineshgadge 
```import { openai } from "@ai-sdk/openai";
import { stepCountIs, streamText, convertToModelMessages } from "ai";
import { chatTools } from "../tools";

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export function errorHandler(error: unknown) {
  if (error == null) {
    return "unknown error";
  }

  if (typeof error === "string") {
    return error;
  }

  if (error instanceof Error) {
    return error.message;
  }

  return JSON.stringify(error);
}
export async function POST(req: Request) {
  try {
    const { messages } = await req.json();

    // Validate messages
    if (!Array.isArray(messages)) {
      return new Response("Invalid messages format", { status: 400 });
    }

    const result = streamText({
      system: "...",
      model: openai.responses(process.env.OPENAI_MODEL || "gpt-4o"),
      messages: convertToModelMessages(messages),
      stopWhen: stepCountIs(100),
      tools: chatTools,
      abortSignal: req.signal, // Add abort signal for proper cancellation
    });

    return result.toUIMessageStreamResponse({
      onError: errorHandler,
      sendSources: true,
    });
  } catch (error) {
    console.error("Chat API error:", error);
    return new Response(errorHandler(error), { status: 500 });
  }
}
```
ask for a tool calling in second message after first message called a tool and it will get exact same error.
Hi,

It's also happening to me, I am using `gpt-5-nano` model.

I am using following dependencies:

```js
    "@ai-sdk/openai": "^2.0.15",
    "@ai-sdk/react": "^2.0.15",
    "ai": "^5.0.15",
```

Here is the implementation.

```ts
export async function chatStream({ messages, model }: ChatRequestSchema) {
  const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY });

  const result = streamText({
    model: openai(model),
    messages: convertToModelMessages(messages),
    system: "You are a helpful assistant that can answer questions and help with tasks",
  });

  return result.toUIMessageStreamResponse({
    sendSources: true,
    sendReasoning: true,
  });
}
```


This was the message:

```json
 {
    "id": "msg-06JMpoUmfT8ajKij",
    "role": "user",
    "parts": [
      {
        "text": "can you reasoning how to elaborate a biscuit",
        "type": "text"
      }
    ]
  }
```

This was the error.

```bash
app:dev: [Error [AI_APICallError]: Item 'rs_68a170b8b5f081a2acba5b8d0c950e5308d016c272d19215' of type 'reasoning' was provided without its required following item.] {
app:dev:   cause: undefined,
app:dev:   url: 'https://api.openai.com/v1/responses',
app:dev:   requestBodyValues: [Object],
app:dev:   statusCode: 400,
app:dev:   responseHeaders: [Object],
app:dev:   responseBody: '{\n' +
app:dev:     '  "error": {\n' +
app:dev:     `    "message": "Item 'rs_68a170b8b5f081a2acba5b8d0c950e5308d016c272d19215' of type 'reasoning' was provided without its required following item.",\n` +
app:dev:     '    "type": "invalid_request_error",\n' +
app:dev:     '    "param": "input",\n' +
app:dev:     '    "code": null\n' +
app:dev:     '  }\n' +
app:dev:     '}',
app:dev:   isRetryable: false,
app:dev:   data: [Object]
app:dev: }
```

Thanks in advance,
Regards,
Javier
I am still facing this issue while using gpt-5-mini

My package.json -

"@ai-sdk/openai": "^2.0.16",
"@ai-sdk/react": "^2.0.16",
"ai": "^5.0.16",
@jsa4000 @anish2110 multi-step chats with reasoning and gpt-5-mini/nano work for me. are you using any tools or are you modifying the messages in any way?
@lgrammel same here - gpt-5 via openai provider, no adjustments to messages array. 


```
    const result = streamText({
      model: selectedModel as any,
      messages: convertToModelMessages(messages),
      tools: financeTools,
      toolChoice: "auto",
      providerOptions: {
        openai: {
          reasoningEffort: 'medium',
          reasoningSummary: 'auto',
        },
      },
      system: `You are a helpful assistant with acc
```

Has tools for web/financial data search with Valyu and code execution with daytona. Relevant packages:

```
    "@ai-sdk/openai": "^2.0.16",
    "@ai-sdk/react": "^2.0.16",
    "ai": "^5.0.16",
```

Works fine on first message, then get:

```
[Error [AI_APICallError]: Item 'msg_68a4710a14188198bc4a03ec83e5e5310f219b3f022c469c' of type 'message' was provided without its required 'reasoning' item: 'rs_68a4710545588198b9356c0906f23ed50f219b3f022c469c'.] {
  cause: undefined,
  url: 'https://api.openai.com/v1/responses',
  requestBodyValues: [Object],
  statusCode: 400,
  responseHeaders: [Object],
  responseBody: '{\n' +
    '  "error": {\n' +
    `    "message": "Item 'msg_68a4710a14188198bc4a03ec83e5e5310f219b3f022c469c' of type 'message' was provided without its required 'reasoning' item: 'rs_68a4710545588198b9356c0906f23ed50f219b3f022c469c'.",\n` +
    '    "type": "invalid_request_error",\n' +
    '    "param": "input",\n' +
    '    "code": null\n' +
    '  }\n' +
    '}',
  isRetryable: false,
  data: [Object]
```
@lgrammel want to apologise - cursor added a line for slightly altering messages array that I didnt spot.... suspect this may be the case for others as well.
@lgrammel 
I am using a 3 of tools, one of which is 

`web_search_preview: openai.tools.webSearchPreview({}),` 

I am not modifying any messages, simply creating an array according to ModelMessage[]

I have tried removing the provider options for reasoning but still the issue persists.
However when web_search_preview tool is not used, then no error occurs but when that is invoked the issue arises.

The issue comes in the end after the web search is completed and the result is also completed.
Both onFinish and onError are triggered.

For result the finishReason comes as unknown.

There is also an other error (not sure if related) - The input the web_search_preview requires is a JSON object but a string of the query is being passed and thus the tool gives an error. 
This used to throw an error and onError woud be triggered but after upgrading to latest version, it does not throw error or go in onError but it still tries to give string input and tool returns error but it might be retrying and then giving result.

```
{ type: 'reasoning', text: '', providerMetadata: [Object] },
    {
      type: 'tool-call',
      toolCallId: 'ws_68a468b94c4c81928c3e889f6e47a6930862726a6326c663',
      toolName: 'web_search_preview',
      input: 'abc',
      dynamic: true,
      invalid: true,
      error: [InvalidToolInputError]
    },
    {
      type: 'tool-error',
      toolCallId: 'ws_68a468b94c4c81928c3e889f6e47a6930862726a6326c663',
      toolName: 'web_search_preview',
      input: 'abc',
      error: 'Invalid input for tool web_search_preview: JSON parsing failed: Text: abc.\n' +
        `Error message: Unexpected token 'a', "abc "... is not valid JSON`,
      dynamic: true
    },
    {
      type: 'tool-result',
      toolCallId: 'ws_68a468b94c4c81928c3e889f6e47a6930862726a6326c663',
      toolName: 'web_search_preview',
      input: undefined,
      output: [Object],
      providerExecuted: true
    }
```

So, currently I am doing a workaround by ignoring the error and simply displaying the output based on the error message.
Hi @lgrammel,

I put my code in the last message. the method is very simple and i am not doing anything at all.

```ts
export async function chatStream({ messages, model }: ChatRequestSchema) {
  const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY });

  const result = streamText({
    model: openai(model),
    messages: convertToModelMessages(messages),
    system: "You are a helpful assistant that can answer questions and help with tasks",
  });

  return result.toUIMessageStreamResponse({
    sendSources: true,
    sendReasoning: true,
  });
}
```


Best regards,
JAvier
you should post a reproducible code snippet or a full repository with a code file that can reproduce the issue easily

your code doesn’t even specify which model it’s using
Hi @remorses, I already said the model in the initial post `gpt-5-nano`. I will try to upload a full functional repository. 
Sorry for the inconveniences.
@jsa4000 i had tried you code snippet and it worked for me. something must be different
@jsa4000 @anish2110 i have added similar examples and tested variants without success. you can find the code here:

- https://github.com/vercel/ai/blob/main/examples/next-openai/app/api/use-chat-reasoning/route.ts
- https://github.com/vercel/ai/blob/main/examples/next-openai/app/api/use-chat-reasoning-tools/route.ts

can you see if those examples work for you? if so do they break with any changes that make them more similar to what you have?
@lgrammel 

I was able to find the source of the issue. 
I am using `stopWhen: stepCountIs(n)`.

If n = 1, it works fine, but any value higher than 1 is giving the error.

```
import { generateText, stepCountIs } from "ai"
import { openai } from "@ai-sdk/openai"

export const agentResponse = (message) => {
    console.log('Starting agent response for message: ', message)

    return generateText({
        model: openai.responses("gpt-5-mini"),
        system: "You are a helpful assistant that can answer questions and help with tasks and you have web search tool web_search_preview",
        messages: [
            {
                role: "user",
                content: message
            }
        ],
        providerOptions: {
			openai: {
				reasoningSummary: "detailed",
				reasoningEffort: "medium",
			},
		},
        stopWhen: stepCountIs(2),
        tools: {
            web_search_preview: openai.tools.webSearchPreview({})
        },
        onChunk: (chunk) => {
            console.log('Chunk: ', chunk)
        },
        onError: (error) => {
            console.error('Error in agent response: ', error)
        },
        onFinish: (result) => {
            console.log('Agent response finished ')
        }
    })
}
```

In my original code, I have 3 tools, and there is a tool which can be called multiple times, there also if I keep the value of stepCountIs(1) it is working but any value higher than 1 is causing the issue.
Reproduced with the following snippet:

```ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';
import 'dotenv/config';

async function main() {
  const result = await generateText({
    model: openai.responses('gpt-5-mini'),
    prompt: 'What happened in the world today?',
    providerOptions: {
      openai: { reasoningSummary: 'detailed', reasoningEffort: 'medium' },
    },
    tools: {
      web_search_preview: openai.tools.webSearchPreview({}),
    },
  });

  const result2 = await generateText({
    model: openai.responses('gpt-5-mini'),
    messages: [
      ...result.response?.messages,
      { role: 'user', content: 'Summarize in 2 sentences.' },
    ],
  });

  console.log(JSON.stringify(result2, null, 2));
}

main().catch(console.error);
```

The error may be coming from the doGenerate implementation and not affect streaming.



Fix (patch):
diff --git a/.changeset/long-rules-leave.md b/.changeset/long-rules-leave.md
new file mode 100644
index 000000000000..c926e2127e9f
--- /dev/null
+++ b/.changeset/long-rules-leave.md
@@ -0,0 +1,5 @@
+---
+'@ai-sdk/openai': patch
+---
+
+fix(provider/openai): correct web search tool input
diff --git a/examples/ai-core/src/generate-text/openai-responses-reasoning-websearch.ts b/examples/ai-core/src/generate-text/openai-responses-reasoning-websearch.ts
new file mode 100644
index 000000000000..cbdfb7e417a2
--- /dev/null
+++ b/examples/ai-core/src/generate-text/openai-responses-reasoning-websearch.ts
@@ -0,0 +1,30 @@
+import { openai } from '@ai-sdk/openai';
+import { generateText } from 'ai';
+import 'dotenv/config';
+
+async function main() {
+  const result = await generateText({
+    model: openai.responses('gpt-5-mini'),
+    prompt: 'What happened in the world today?',
+    providerOptions: {
+      openai: { reasoningSummary: 'detailed', reasoningEffort: 'medium' },
+    },
+    tools: {
+      web_search_preview: openai.tools.webSearchPreview({}),
+    },
+  });
+
+  console.log(JSON.stringify(result.response?.messages, null, 2));
+
+  const result2 = await generateText({
+    model: openai.responses('gpt-5-mini'),
+    messages: [
+      ...result.response?.messages,
+      { role: 'user', content: 'Summarize in 2 sentences.' },
+    ],
+  });
+
+  console.log(JSON.stringify(result2, null, 2));
+}
+
+main().catch(console.error);
diff --git a/examples/ai-core/src/stream-text/openai-responses-reasoning-websearch.ts b/examples/ai-core/src/stream-text/openai-responses-reasoning-websearch.ts
new file mode 100644
index 000000000000..a85f09655b58
--- /dev/null
+++ b/examples/ai-core/src/stream-text/openai-responses-reasoning-websearch.ts
@@ -0,0 +1,37 @@
+import 'dotenv/config';
+import { openai } from '@ai-sdk/openai';
+import { generateText, streamText } from 'ai';
+
+async function main() {
+  const result = streamText({
+    model: openai.responses('gpt-5-mini'),
+    prompt: 'What happened in the world today?',
+    providerOptions: {
+      openai: { reasoningSummary: 'detailed', reasoningEffort: 'medium' },
+    },
+    tools: {
+      web_search_preview: openai.tools.webSearchPreview({}),
+    },
+  });
+
+  for await (const textPart of result.textStream) {
+    process.stdout.write(textPart);
+  }
+
+  const messages = (await result.response).messages;
+
+  console.log(JSON.stringify(messages, null, 2));
+
+  // switch to generate (output irrelevant)
+  const result2 = await generateText({
+    model: openai.responses('gpt-5-mini'),
+    messages: [
+      ...messages,
+      { role: 'user', content: 'Summarize in 2 sentences.' },
+    ],
+  });
+
+  console.log(JSON.stringify(result2, null, 2));
+}
+
+main().catch(console.error);
diff --git a/packages/openai/src/responses/openai-responses-language-model.ts b/packages/openai/src/responses/openai-responses-language-model.ts
index 18c1aa200b0a..f90ae7b4813c 100644
--- a/packages/openai/src/responses/openai-responses-language-model.ts
+++ b/packages/openai/src/responses/openai-responses-language-model.ts
@@ -5,7 +5,6 @@ import {
   LanguageModelV2Content,
   LanguageModelV2FinishReason,
   LanguageModelV2StreamPart,
-  LanguageModelV2Text,
   LanguageModelV2Usage,
   SharedV2ProviderMetadata,
 } from '@ai-sdk/provider';
@@ -26,6 +25,29 @@ import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-rea
 import { prepareResponsesTools } from './openai-responses-prepare-tools';
 import { OpenAIResponsesModelId } from './openai-responses-settings';
 
+const webSearchCallItem = z.object({
+  type: z.literal('web_search_call'),
+  id: z.string(),
+  status: z.string(),
+  action: z
+    .discriminatedUnion('type', [
+      z.object({
+        type: z.literal('search'),
+        query: z.string(),
+      }),
+      z.object({
+        type: z.literal('open_page'),
+        url: z.string(),
+      }),
+      z.object({
+        type: z.literal('find'),
+        url: z.string(),
+        pattern: z.string(),
+      }),
+    ])
+    .nullish(),
+});
+
 /**
  * `top_logprobs` request body argument can be set to an integer between
  * 0 and 20 specifying the number of most likely tokens to return at each
@@ -355,17 +377,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
                 arguments: z.string(),
                 id: z.string(),
               }),
-              z.object({
-                type: z.literal('web_search_call'),
-                id: z.string(),
-                status: z.string().optional(),
-                action: z
-                  .object({
-                    type: z.literal('search'),
-                    query: z.string().optional(),
-                  })
-                  .nullish(),
-              }),
+              webSearchCallItem,
               z.object({
                 type: z.literal('computer_call'),
                 id: z.string(),
@@ -513,7 +525,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
             type: 'tool-call',
             toolCallId: part.id,
             toolName: 'web_search_preview',
-            input: part.action?.query ?? '',
+            input: JSON.stringify({ action: part.action }),
             providerExecuted: true,
           });
 
@@ -521,10 +533,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
             type: 'tool-result',
             toolCallId: part.id,
             toolName: 'web_search_preview',
-            result: {
-              status: part.status || 'completed',
-              ...(part.action?.query && { query: part.action.query }),
-            },
+            result: { status: part.status },
             providerExecuted: true,
           });
           break;
@@ -792,7 +801,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
                   type: 'tool-call',
                   toolCallId: value.item.id,
                   toolName: 'web_search_preview',
-                  input: value.item.action?.query ?? '',
+                  input: JSON.stringify({ action: value.item.action }),
                   providerExecuted: true,
                 });
 
@@ -800,13 +809,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV2 {
                   type: 'tool-result',
                   toolCallId: value.item.id,
                   toolName: 'web_search_preview',
-                  result: {
-                    type: 'web_search_tool_result',
-                    status: value.item.status || 'completed',
-                    ...(value.item.action?.query && {
-                      query: value.item.action.query,
-                    }),
-                  },
+                  result: { status: value.item.status },
                   providerExecuted: true,
                 });
               } else if (value.item.type === 'computer_call') {
@@ -1135,17 +1138,7 @@ const responseOutputItemDoneSchema = z.object({
       arguments: z.string(),
       status: z.literal('completed'),
     }),
-    z.object({
-      type: z.literal('web_search_call'),
-      id: z.string(),
-      status: z.literal('completed'),
-      action: z
-        .object({
-          type: z.literal('search'),
-          query: z.string().optional(),
-        })
-        .nullish(),
-    }),
+    webSearchCallItem,
     z.object({
       type: z.literal('computer_call'),
       id: z.string(),
diff --git a/packages/openai/src/tool/web-search-preview.ts b/packages/openai/src/tool/web-search-preview.ts
index c2f0a99a361b..762b4ff25729 100644
--- a/packages/openai/src/tool/web-search-preview.ts
+++ b/packages/openai/src/tool/web-search-preview.ts
@@ -82,5 +82,23 @@ export const webSearchPreview = createProviderDefinedToolFactory<
 >({
   id: 'openai.web_search_preview',
   name: 'web_search_preview',
-  inputSchema: z.object({}),
+  inputSchema: z.object({
+    action: z
+      .discriminatedUnion('type', [
+        z.object({
+          type: z.literal('search'),
+          query: z.string(),
+        }),
+        z.object({
+          type: z.literal('open_page'),
+          url: z.string(),
+        }),
+        z.object({
+          type: z.literal('find'),
+          url: z.string(),
+          pattern: z.string(),
+        }),
+      ])
+      .nullish(),
+  }),
 });