# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- examples/docs/README.md ---
# Documentation Snippets

This directory contains small scripts used throughout the documentation. Run them with `pnpm` using the commands shown below.

- `agents-basic-configuration.ts` – Configure a weather agent with a tool and model.
  ```bash
  pnpm -F docs start:agents-basic-configuration
  ```
- `agents-cloning.ts` – Clone an agent and reuse its configuration.
  ```bash
  pnpm -F docs start:agents-cloning
  ```
- `agents-context.ts` – Access user context from tools during execution.
  ```bash
  pnpm -F docs start:agents-context
  ```
- `agents-dynamic-instructions.ts` – Build instructions dynamically from context.
  ```bash
  pnpm -F docs start:agents-dynamic-instructions
  ```
- `agents-forcing-tool-use.ts` – Require specific tools before producing output.
  ```bash
  pnpm -F docs start:agents-forcing-tool-use
  ```
- `agents-handoffs.ts` – Route requests to specialized agents using handoffs.
  ```bash
  pnpm -F docs start:agents-handoffs
  ```
- `agents-lifecycle-hooks.ts` – Log agent lifecycle events as they run.
  ```bash
  pnpm -F docs start:agents-lifecycle-hooks
  ```
- `agents-output-types.ts` – Return structured data using a Zod schema.
  ```bash
  pnpm -F docs start:agents-output-types
  ```
- `guardrails-input.ts` – Block unwanted requests using input guardrails.
  ```bash
  pnpm -F docs start:guardrails-input
  ```
- `guardrails-output.ts` – Check responses with output guardrails.
  ```bash
  pnpm -F docs start:guardrails-output
  ```
- `models-custom-providers.ts` – Create and use a custom model provider.
  ```bash
  pnpm -F docs start:models-custom-providers
  ```
- `models-openai-provider.ts` – Run agents with the OpenAI provider.
  ```bash
  pnpm -F docs start:models-openai-provider
  ```
- `quickstart.ts` – Simple triage agent that hands off questions to tutors.
  ```bash
  pnpm -F docs start:quickstart
  ```
- `readme-functions.ts` – README example showing how to call functions as tools.
  ```bash
  pnpm -F docs start:readme-functions
  ```
- `readme-handoffs.ts` – README example that demonstrates handoffs.
  ```bash
  pnpm -F docs start:readme-handoffs
  ```
- `readme-hello-world.ts` – The hello world snippet from the README.
  ```bash
  pnpm -F docs start:readme-hello-world
  ```
- `readme-voice-agent.ts` – Browser-based realtime voice agent example.
  ```bash
  pnpm -F docs start:readme-voice-agent
  ```
- `running-agents-exceptions1.ts` – Retry after a guardrail execution error.
  ```bash
  pnpm -F docs start:running-agents-exceptions1
  ```
- `running-agents-exceptions2.ts` – Retry after a failed tool call.
  ```bash
  pnpm -F docs start:running-agents-exceptions2
  ```


--- examples/docs/quickstart/index.ts ---
import { Agent, run } from '@openai/agents';

const historyTutorAgent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const mathTutorAgent = new Agent({
  name: 'Math Tutor',
  instructions:
    'You provide help with math problems. Explain your reasoning at each step and include examples',
});

const triageAgent = new Agent({
  name: 'Triage Agent',
  instructions:
    "You determine which agent to use based on the user's homework question",
  handoffs: [historyTutorAgent, mathTutorAgent],
});

async function main() {
  const result = await run(triageAgent, 'What is the capital of France?');
  console.log(result.finalOutput);
}

main().catch((err) => console.error(err));


--- examples/docs/readme/readme-functions.ts ---
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  tools: [getWeatherTool],
});

async function main() {
  const result = await run(agent, 'What is the weather in Tokyo?');
  console.log(result.finalOutput);
}

main().catch(console.error);


--- examples/docs/readme/readme-handoffs.ts ---
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const dataAgent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  handoffDescription: 'You know everything about the weather',
  tools: [getWeatherTool],
});

// Use Agent.create method to ensure the finalOutput type considers handoffs
const agent = Agent.create({
  name: 'Basic test agent',
  instructions: 'You are a basic agent',
  handoffs: [dataAgent],
});

async function main() {
  const result = await run(agent, 'What is the weather in San Francisco?');
  console.log(result.finalOutput);
}

main().catch(console.error);


--- examples/docs/readme/readme-hello-world.ts ---
import { Agent, run } from '@openai/agents';

async function main() {
  const agent = new Agent({
    name: 'Assistant',
    instructions: 'You are a helpful assistant',
  });
  const result = await run(
    agent,
    'Write a haiku about recursion in programming.',
  );
  console.log(result.finalOutput);
  // Code within the code,
  // Functions calling themselves,
  // Infinite loop's dance.
}

main().catch(console.error);


--- examples/docs/readme/readme-voice-agent.ts ---
import { z } from 'zod';
import { RealtimeAgent, RealtimeSession, tool } from '@openai/agents-realtime';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new RealtimeAgent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  tools: [getWeatherTool],
});

async function main() {
  // Intended to run in the browser
  const { apiKey } = await fetch('/path/to/ephemeral/key/generation').then(
    (resp) => resp.json(),
  );
  // Automatically configures audio input/output — start talking
  const session = new RealtimeSession(agent);
  await session.connect({ apiKey });
}

main().catch(console.error);


--- docs/README.md ---
# Docs

The documentation is generated using Astro Starlight.

## Running the docs

To run the docs from the root of the project run:

```bash
pnpm docs:dev
```

## Translating docs

All of our documentation is available in Japanese. For this we use a script to translate the docs.

```bash
pnpm docs:translate
```

## Building the docs

The docs are automatically built and deployed using GitHub Actions. To build them locally run:

```bash
pnpm docs:build
```


--- docs/src/content/docs/guides/quickstart.mdx ---
---
title: Quickstart
description: Create your first AI Agent from scratch
---

import { Steps } from '@astrojs/starlight/components';
import { Code } from '@astrojs/starlight/components';
import quickstartExample from '../../../../../examples/docs/quickstart/index.ts?raw';

## Project Setup

<Steps>

1. Create a project and initialize npm. You'll only need to do this once.

   ```bash
   mkdir my_project
   cd my_project
   npm init -y
   ```

2. Install the Agents SDK.

   ```bash
   npm install @openai/agents zod@3
   ```

3. Set an OpenAI API key. If you don't have one, follow [these instructions](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) to create an OpenAI API key.

   ```bash
   export OPENAI_API_KEY=sk-...
   ```

   Alternatively you can call `setDefaultOpenAIKey('<api key>')` to set the key
   programmatically and use `setTracingExportApiKey('<api key>')` for tracing.
   See [the config guide](/openai-agents-js/guides/config) for more details.

</Steps>

## Create your first agent

Agents are defined with instructions and a name.

```typescript
import { Agent } from '@openai/agents';

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});
```

## Run your first agent

You can use the `run` method to run your agent. You trigger a run by passing both the agent you
want to start on and the input you want to pass in.

This will return a result that contains the final output and any actions that were performed
during that run.

```typescript
import { Agent, run } from '@openai/agents';

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const result = await run(agent, 'When did sharks first appear?');

console.log(result.finalOutput);
```

## Give your agent tools

You can give an agent tools to use to look up information or perform actions.

```typescript
import { Agent, tool } from '@openai/agents';

const historyFunFact = tool({
  // The name of the tool will be used by the agent to tell what tool to use.
  name: 'history_fun_fact',
  // The description is used to describe **when** to use the tool by telling it **what** it does.
  description: 'Give a fun fact about a historical event',
  // This tool takes no parameters, so we provide an empty Zod Object.
  parameters: z.object({}),
  execute: async () => {
    // The output will be returned back to the Agent to use
    return 'Sharks are older than trees.';
  },
});

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
  // Adding the tool to the agent
  tools: [historyFunFact],
});
```

## Add a few more agents

Additional agents can be defined similarly to break down problems into smaller parts and have your
agent be more focused on the task at hand. It also allows you to use different models for different
problems by defining the model on the agent.

```typescript
const historyTutorAgent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const mathTutorAgent = new Agent({
  name: 'Math Tutor',
  instructions:
    'You provide help with math problems. Explain your reasoning at each step and include examples',
});
```

## Define your handoffs

In order to orchestrate between multiple agents, you can define `handoffs` for an agent. This will
enable the agent to pass the conversation on to the next agent. This will happen automatically
during the course of a run.

```typescript
// Using the Agent.create method to ensures type safety for the final output
const triageAgent = Agent.create({
  name: 'Triage Agent',
  instructions:
    "You determine which agent to use based on the user's homework question",
  handoffs: [historyTutorAgent, mathTutorAgent],
});
```

After your run you can see which agent generated the final response by looking at the `finalAgent`
property on the result.

## Run the agent orchestration

The Runner is in handling the execution of the invidiual agents, any potential handoffs and tool
executions.

```typescript
import { run } from '@openai/agents';

async function main() {
  const result = await run(triageAgent, 'What is the capital of France?');
  console.log(result.finalOutput);
}

main().catch((err) => console.error(err));
```

## Putting it all together

Let's put it all together into one full example. Place this into your `index.js` file and run it.

<Code lang="typescript" code={quickstartExample} title="Quickstart" />

## View your traces

The Agents SDK will automatically generate traces for you. This allows you to review how your agents
are operating, what tools they called or which agent they handed off to.

To review what happened during your agent run, navigate to the
[Trace viewer in the OpenAI Dashboard](https://platform.openai.com/traces).

## Next steps

Learn how to build more complex agentic flows:

- Learn about configuring [Agents](/openai-agents-js/guides/agents).
- Learn about [running agents](/openai-agents-js/guides/running-agents).
- Learn about [tools](/openai-agents-js/guides/tools), [guardrails](/openai-agents-js/guides/guardrails), and [models](/openai-agents-js/guides/models).


## Links discovered
- [these instructions](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
- [the config guide](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/config.md)
- [Trace viewer in the OpenAI Dashboard](https://platform.openai.com/traces)
- [Agents](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/agents.md)
- [running agents](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/running-agents.md)
- [tools](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/tools.md)
- [guardrails](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/guardrails.md)
- [models](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/models.md)

--- docs/src/content/docs/guides/voice-agents/quickstart.mdx ---
---
title: Voice Agents Quickstart
description: Build your first realtime voice assistant using the OpenAI Agents SDK in minutes.
---

import { Steps, Aside, Code } from '@astrojs/starlight/components';
import helloWorldExample from '../../../../../../examples/docs/voice-agents/helloWorld.ts?raw';
import createAgentExample from '../../../../../../examples/docs/voice-agents/createAgent.ts?raw';
import multiAgentsExample from '../../../../../../examples/docs/voice-agents/multiAgents.ts?raw';
import createSessionExample from '../../../../../../examples/docs/voice-agents/createSession.ts?raw';
import configureSessionExample from '../../../../../../examples/docs/voice-agents/configureSession.ts?raw';
import handleAudioExample from '../../../../../../examples/docs/voice-agents/handleAudio.ts?raw';
import defineToolExample from '../../../../../../examples/docs/voice-agents/defineTool.ts?raw';
import toolApprovalEventExample from '../../../../../../examples/docs/voice-agents/toolApprovalEvent.ts?raw';
import guardrailsExample from '../../../../../../examples/docs/voice-agents/guardrails.ts?raw';
import guardrailSettingsExample from '../../../../../../examples/docs/voice-agents/guardrailSettings.ts?raw';
import audioInterruptedExample from '../../../../../../examples/docs/voice-agents/audioInterrupted.ts?raw';
import sessionInterruptExample from '../../../../../../examples/docs/voice-agents/sessionInterrupt.ts?raw';
import sessionHistoryExample from '../../../../../../examples/docs/voice-agents/sessionHistory.ts?raw';
import historyUpdatedExample from '../../../../../../examples/docs/voice-agents/historyUpdated.ts?raw';
import updateHistoryExample from '../../../../../../examples/docs/voice-agents/updateHistory.ts?raw';
import customWebRTCTransportExample from '../../../../../../examples/docs/voice-agents/customWebRTCTransport.ts?raw';
import websocketSessionExample from '../../../../../../examples/docs/voice-agents/websocketSession.ts?raw';
import transportEventsExample from '../../../../../../examples/docs/voice-agents/transportEvents.ts?raw';
import thinClientExample from '../../../../../../examples/docs/voice-agents/thinClient.ts?raw';

<Steps>

0. **Create a project**

   In this quickstart we will create a voice agent you can use in the browser. If you want to check out a new project, you can try out [`Next.js`](https://nextjs.org/docs/getting-started/installation) or [`Vite`](https://vite.dev/guide/installation.html).

   ```bash
   npm create vite@latest my-project -- --template vanilla-ts
   ```

1. **Install the Agents SDK**

   ```bash
   npm install @openai/agents zod@3
   ```

   Alternatively you can install `@openai/agents-realtime` for a standalone browser package.

2. **Generate a client ephemeral token**

   As this application will run in the user's browser, we need a secure way to connect to the model through the Realtime API. For this we can use an [ephemeral client key](https://platform.openai.com/docs/guides/realtime#creating-an-ephemeral-token) that should be generated on your backend server. For testing purposes you can also generate a key using `curl` and your regular OpenAI API key.

   ```bash
   export OPENAI_API_KEY="sk-proj-...(your own key here)"
   curl -X POST https://api.openai.com/v1/realtime/client_secrets \
      -H "Authorization: Bearer $OPENAI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "session": {
          "type": "realtime",
          "model": "gpt-realtime"
        }
      }'
   ```

   The response will contain a "value" string a the top level, which starts with "ek\_" prefix. You can use this ephemeral key to establish a WebRTC connection later on. Note that this key is only valid for a short period of time and will need to be regenerated.

3. **Create your first Agent**

   Creating a new [`RealtimeAgent`](/openai-agents-js/openai/agents-realtime/classes/realtimeagent/) is very similar to creating a regular [`Agent`](/openai-agents-js/guides/agents).

   ```typescript
   import { RealtimeAgent } from '@openai/agents/realtime';

   const agent = new RealtimeAgent({
     name: 'Assistant',
     instructions: 'You are a helpful assistant.',
   });
   ```

4. **Create a session**

   Unlike a regular agent, a Voice Agent is continuously running and listening inside a `RealtimeSession` that handles the conversation and connection to the model over time. This session will also handle the audio processing, interruptions, and a lot of the other lifecycle functionality we will cover later on.

   ```typescript
   import { RealtimeSession } from '@openai/agents/realtime';

   const session = new RealtimeSession(agent, {
     model: 'gpt-realtime',
   });
   ```

   The `RealtimeSession` constructor takes an `agent` as the first argument. This agent will be the first agent that your user will be able to interact with.

5. **Connect to the session**

   To connect to the session you need to pass the client ephemeral token you generated earlier on.

   ```typescript
   await session.connect({ apiKey: 'ek_...(put your own key here)' });
   ```

   This will connect to the Realtime API using WebRTC in the browser and automatically configure your microphone and speaker for audio input and output. If you are running your `RealtimeSession` on a backend server (like Node.js) the SDK will automatically use WebSocket as a connection. You can learn more about the different transport layers in the [Realtime Transport Layer](/openai-agents-js/guides/voice-agents/transport) guide.

6. **Putting it all together**

   <Code lang="typescript" code={helloWorldExample} />

7. **Fire up the engines and start talking**

   Start up your webserver and navigate to the page that includes your new Realtime Agent code. You should see a request for microphone access. Once you grant access you should be able to start talking to your agent.

   ```bash
   npm run dev
   ```

</Steps>

## Next Steps

From here you can start designing and building your own voice agent. Voice agents include a lot of the same features as regular agents, but have some of their own unique features.

- Learn how to give your voice agent:
  - [Tools](/openai-agents-js/guides/voice-agents/build#tools)
  - [Handoffs](/openai-agents-js/guides/voice-agents/build#handoffs)
  - [Guardrails](/openai-agents-js/guides/voice-agents/build#guardrails)
  - [Handle audio interruptions](/openai-agents-js/guides/voice-agents/build#audio-interruptions)
  - [Manage session history](/openai-agents-js/guides/voice-agents/build#session-history)

- Learn more about the different transport layers.
  - [WebRTC](/openai-agents-js/guides/voice-agents/transport#connecting-over-webrtc)
  - [WebSocket](/openai-agents-js/guides/voice-agents/transport#connecting-over-websocket)
  - [Building your own transport mechanism](/openai-agents-js/guides/voice-agents/transport#building-your-own-transport-mechanism)


## Links discovered
- [`Next.js`](https://nextjs.org/docs/getting-started/installation)
- [`Vite`](https://vite.dev/guide/installation.html)
- [ephemeral client key](https://platform.openai.com/docs/guides/realtime#creating-an-ephemeral-token)
- [`RealtimeAgent`](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/openai/agents-realtime/classes/realtimeagent.md)
- [`Agent`](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/agents.md)
- [Realtime Transport Layer](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/transport.md)
- [Tools](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/build#tools.md)
- [Handoffs](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/build#handoffs.md)
- [Guardrails](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/build#guardrails.md)
- [Handle audio interruptions](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/build#audio-interruptions.md)
- [Manage session history](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/build#session-history.md)
- [WebRTC](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/transport#connecting-over-webrtc.md)
- [WebSocket](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/transport#connecting-over-websocket.md)
- [Building your own transport mechanism](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/guides/voice-agents/transport#building-your-own-transport-mechanism.md)

--- docs/src/content/docs/ja/guides/quickstart.mdx ---
---
title: クイックスタート
description: Create your first AI Agent from scratch
---

import { Steps } from '@astrojs/starlight/components';
import { Code } from '@astrojs/starlight/components';
import quickstartExample from '../../../../../../examples/docs/quickstart/index.ts?raw';

## プロジェクトのセットアップ

<Steps>

1. プロジェクトを作成して npm を初期化します。一度だけ行えば大丈夫です

   ```bash
   mkdir my_project
   cd my_project
   npm init -y
   ```

2. Agents SDK をインストールします

   ```bash
   npm install @openai/agents zod@3
   ```

3. OpenAI API key を設定します。まだお持ちでない場合は、OpenAI API key を作成するために[こちらの手順](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)に従ってください

   ```bash
   export OPENAI_API_KEY=sk-...
   ```

   あるいは `setDefaultOpenAIKey('<api key>')` を呼び出してプログラムから key を設定し、トレーシングには `setTracingExportApiKey('<api key>')` を使用できます。
   詳細は[SDK の設定](/openai-agents-js/ja/guides/config)を参照してください

</Steps>

## はじめてのエージェントの作成

エージェントは instructions と name で定義されます。

```typescript
import { Agent } from '@openai/agents';

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});
```

## はじめてのエージェントの実行

`run` メソッドでエージェントを実行できます。開始したいエージェントと渡したい入力の両方を指定して実行します。

これにより、最終出力と実行中に行われたすべてのアクションを含む実行結果が返されます。

```typescript
import { Agent, run } from '@openai/agents';

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const result = await run(agent, 'When did sharks first appear?');

console.log(result.finalOutput);
```

## エージェントへのツールの付与

情報の参照やアクションの実行に使えるツールをエージェントに与えられます。

```typescript
import { Agent, tool } from '@openai/agents';

const historyFunFact = tool({
  // The name of the tool will be used by the agent to tell what tool to use.
  name: 'history_fun_fact',
  // The description is used to describe **when** to use the tool by telling it **what** it does.
  description: 'Give a fun fact about a historical event',
  // This tool takes no parameters, so we provide an empty Zod Object.
  parameters: z.object({}),
  execute: async () => {
    // The output will be returned back to the Agent to use
    return 'Sharks are older than trees.';
  },
});

const agent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
  // Adding the tool to the agent
  tools: [historyFunFact],
});
```

## さらにいくつかのエージェントの追加

追加のエージェントを同様に定義して、課題を小さな部分に分解し、エージェントが目の前のタスクにより集中できるようにします。エージェントで model を定義することで、課題ごとに異なるモデルを使うこともできます。

```typescript
const historyTutorAgent = new Agent({
  name: 'History Tutor',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const mathTutorAgent = new Agent({
  name: 'Math Tutor',
  instructions:
    'You provide help with math problems. Explain your reasoning at each step and include examples',
});
```

## ハンドオフの定義

複数のエージェントをオーケストレーションするために、エージェントに `handoffs` を定義できます。これによりエージェントは会話を次のエージェントへ引き継げます。これは実行の過程で自動的に行われます。

```typescript
// Using the Agent.create method to ensures type safety for the final output
const triageAgent = Agent.create({
  name: 'Triage Agent',
  instructions:
    "You determine which agent to use based on the user's homework question",
  handoffs: [historyTutorAgent, mathTutorAgent],
});
```

実行後、結果の `finalAgent` プロパティを見ると、どのエージェントが最終応答を生成したかがわかります。

## エージェントオーケストレーションの実行

Runner は個々のエージェントの実行、必要に応じたハンドオフ、ツール実行を処理します。

```typescript
import { run } from '@openai/agents';

async function main() {
  const result = await run(triageAgent, 'What is the capital of France?');
  console.log(result.finalOutput);
}

main().catch((err) => console.error(err));
```

## すべてをまとめる

すべてを 1 つの完全な例にまとめましょう。これを `index.js` に配置して実行します。

<Code lang="typescript" code={quickstartExample} title="クイックスタート" />

## トレースの表示

Agents SDK は自動的にトレースを生成します。これにより、エージェントの動作、呼び出したツール、どのエージェントへハンドオフしたかを確認できます。

エージェント実行中に何が起きたかを確認するには、
[OpenAI ダッシュボードの Trace viewer](https://platform.openai.com/traces) に移動します。

## 次のステップ

より複雑なエージェントフローの構築方法を学びましょう。

- [エージェント](/openai-agents-js/ja/guides/agents)の設定について学ぶ
- [エージェントの実行](/openai-agents-js/ja/guides/running-agents)について学ぶ
- [ツール](/openai-agents-js/ja/guides/tools)、[ガードレール](/openai-agents-js/ja/guides/guardrails)、[モデル](/openai-agents-js/ja/guides/models)について学ぶ


## Links discovered
- [こちらの手順](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
- [SDK の設定](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/config.md)
- [OpenAI ダッシュボードの Trace viewer](https://platform.openai.com/traces)
- [エージェント](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/agents.md)
- [エージェントの実行](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/running-agents.md)
- [ツール](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/tools.md)
- [ガードレール](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/guardrails.md)
- [モデル](https://github.com/openai/openai-agents-js/blob/main/openai-agents-js/ja/guides/models.md)

--- examples/agent-patterns/README.md ---
# Agent Pattern Examples

This directory contains small scripts that demonstrate different agent patterns.
Run them with `pnpm` using the commands shown below.

- `agents-as-tools.ts` – Orchestrate translator agents using them as tools.
  ```bash
  pnpm examples:agents-as-tools
  ```
- `agents-as-tools-conditional.ts` – Enable language tools based on user preference.
  ```bash
  pnpm examples:agents-as-tools-conditional
  ```
- `deterministic.ts` – Fixed agent flow with gating and quality checks.
  ```bash
  pnpm examples:deterministic
  ```
- `forcing-tool-use.ts` – Require specific tools before final output.
  ```bash
  pnpm -F agent-patterns start:forcing-tool-use
  ```
- `human-in-the-loop.ts` – Manually approve certain tool calls.
  ```bash
  pnpm examples:human-in-the-loop
  ```
- `human-in-the-loop-stream.ts` – Streaming version of human approval.
  ```bash
  pnpm examples:streamed:human-in-the-loop
  ```
- `input-guardrails.ts` – Reject unwanted requests with guardrails.
  ```bash
  pnpm examples:input-guardrails
  ```
- `llm-as-a-judge.ts` – Evaluate and iterate on story outlines.
  ```bash
  pnpm -F agent-patterns start:llm-as-a-judge
  ```
- `output-guardrails.ts` – Block unsafe output using guardrails.
  ```bash
  pnpm examples:output-guardrails
  ```
- `parallelization.ts` – Run translations in parallel and pick the best.
  ```bash
  pnpm examples:parallelization
  ```
- `routing.ts` – Route messages to language-specific agents.
  ```bash
  pnpm examples:routing
  ```
- `streamed.ts` – Stream agent output, both text and events.
  ```bash
  pnpm examples:streamed
  ```
- `streaming-guardrails.ts` – Check streaming output against guardrails.
  ```bash
  pnpm -F agent-patterns start:streaming-guardrails
  ```


--- examples/ai-sdk-v1/README.md ---
# AI SDK Example

This example shows how to run the Agents SDK with a model provided by the [AI SDK](https://www.npmjs.com/package/@ai-sdk/openai).

The [ai-sdk-model.ts](./ai-sdk-model.ts) script:

- Wraps the AI SDK `openai` provider with `aisdk` from `@openai/agents-extensions`.
- Creates a simple `get_weather` tool that returns a mock weather string.
- Defines a data agent that uses this model and tool.
- Runs a parent agent that hands off to the data agent to answer a weather question.

## Running the script

From the repository root, execute:

```bash
pnpm -F ai-sdk start:sdk-model
```

The script prints the final output produced by the runner.


## Links discovered
- [AI SDK](https://www.npmjs.com/package/@ai-sdk/openai)
- [ai-sdk-model.ts](https://github.com/openai/openai-agents-js/blob/main/examples/ai-sdk-v1/ai-sdk-model.ts)

--- examples/ai-sdk/README.md ---
# AI SDK Example

This example shows how to run the Agents SDK with a model provided by the [AI SDK](https://www.npmjs.com/package/@ai-sdk/openai).

The [ai-sdk-model.ts](./ai-sdk-model.ts) script:

- Wraps the AI SDK `openai` provider with `aisdk` from `@openai/agents-extensions`.
- Creates a simple `get_weather` tool that returns a mock weather string.
- Defines a data agent that uses this model and tool.
- Runs a parent agent that hands off to the data agent to answer a weather question.

## Running the script

From the repository root, execute:

```bash
pnpm -F ai-sdk start:sdk-model
```

The script prints the final output produced by the runner.



## Links discovered
- [AI SDK](https://www.npmjs.com/package/@ai-sdk/openai)
- [ai-sdk-model.ts](https://github.com/openai/openai-agents-js/blob/main/examples/ai-sdk/ai-sdk-model.ts)

--- examples/basic/README.md ---
# Basic Examples

This directory contains small scripts that demonstrate features of the Agents SDK.
Run them with `pnpm` using the commands shown below.

- `hello-world.ts` – Basic agent that responds in haiku.
  ```bash
  pnpm -F basic start:hello-world
  ```
- `chat.ts` – Interactive CLI chat with a weather handoff.
  ```bash
  pnpm -F basic start:chat
  ```
- `stream-text.ts` – Stream plain text responses.
  ```bash
  pnpm -F basic start:stream-text
  ```
- `stream-items.ts` – Stream events including tool usage.
  ```bash
  pnpm -F basic start:stream-items
  ```
- `dynamic-system-prompt.ts` – Instructions picked dynamically per run.
  ```bash
  pnpm -F basic start:dynamic-system-prompt
  ```
- `lifecycle-example.ts` – Logs detailed lifecycle events and usage.
  ```bash
  pnpm -F basic start:lifecycle-example
  ```
- `agent-lifecycle-example.ts` – Minimal lifecycle hooks demo.
  ```bash
  pnpm -F basic start:agent-lifecycle-example
  ```
- `local-image.ts` – Send a local image to the agent.
  ```bash
  pnpm -F basic start:local-image
  ```
- `image-tool-output.ts` – Return an image from a tool and let the agent describe it.
  ```bash
  pnpm -F basic start:image-tool-output
  ```
- `file-tool-output.ts` – Return a file from a tool and have the agent summarize it.
  ```bash
  pnpm -F basic start:file-tool-output
  ```
- `remote-image.ts` – Send an image URL to the agent.
  ```bash
  pnpm -F basic start:remote-image
  ```
- `previous-response-id.ts` – Continue a conversation using
  `previousResponseId`.
  ```bash
  pnpm -F basic start:previous-response-id
  ```
- `json-schema-output-type.ts` – Structured output with JSON Schema.
  ```bash
  pnpm -F basic start:json-schema-output-type
  ```
- `tool-use-behavior.ts` – Require specific tools before final output.
  ```bash
  pnpm -F basic start:tool-use-behavior
  ```
- `tools.ts` – Simple tool calling example.
  ```bash
  pnpm -F basic start:tools
  ```
- `index.ts` – Basic handoff between two agents.
  ```bash
  pnpm -F basic start
  ```


--- examples/customer-service/README.md ---
# Customer Service Agent

This example demonstrates a multi-agent customer service workflow for an airline. The `index.ts` script sets up a triage agent that can delegate to specialized FAQ and seat booking agents. Tools are used to look up common questions and to update a passenger's seat. Interaction occurs through a simple CLI loop, showing how agents can hand off between each other and call tools.

Run the demo with:

```bash
pnpm examples:customer-service
```



--- examples/financial-research-agent/README.md ---
# Financial Research Agent

This example demonstrates a multi-agent workflow that produces a short financial analysis report.

The entrypoint in `main.ts` prompts for a query, then traces the run and hands control to `FinancialResearchManager`.

The manager orchestrates several specialized agents:

1. **Planner** – creates a list of search tasks for the query.
2. **Search** – runs each search in parallel and gathers summaries.
3. **Writer** – synthesizes the search results, optionally calling fundamentals and risk analyst tools.
4. **Verifier** – checks the final report for consistency and issues.

After running these steps the manager prints a short summary, the full markdown report, suggested follow-up questions, and verification results.

Run the example with:

```bash
pnpm examples:financial-research-agent
```


--- examples/handoffs/README.md ---
# Agent Handoffs

This example shows how one agent can transfer control to another. The `index.ts` script sets up two English speaking assistants and a Spanish assistant. The second agent is configured with a handoff so that if the user requests Spanish replies it hands off to the Spanish agent. A message filter strips out tool messages and the first two history items before the handoff occurs. Run it with:

```bash
pnpm -F handoffs start
```

`types.ts` demonstrates typed outputs. A triage agent inspects the message and hands off to either `firstAgent` or `secondAgent`, each with their own Zod schema for structured output. The script logs which agent produced the final result.

`is-enabled.ts` demonstrates gating handoffs with feature-like preferences. Run it with:

```bash
pnpm -F handoffs start:is-enabled
```


--- examples/mcp/README.md ---
# Model Context Protocol Example

This example demonstrates how to use the [Model Context Protocol](https://modelcontextprotocol.io/) with the OpenAI Agents SDK.

`filesystem-example.ts` starts a local MCP server exposing the files inside `sample_files/`. The agent reads those files through the protocol and can answer questions about them. The directory includes:

- `books.txt` – A list of favorite books.
- `favorite_songs.txt` – A list of favorite songs.

Run the example from the repository root:

```bash
pnpm -F mcp start:stdio
```

`tool-filter-example.ts` shows how to expose only a subset of server tools:

```bash
pnpm -F mcp start:tool-filter
```

`get-all-mcp-tools-example.ts` demonstrates how to use the `getAllMcpTools` function to fetch tools from multiple MCP servers:

```bash
pnpm -F mcp start:get-all-tools
```


## Links discovered
- [Model Context Protocol](https://modelcontextprotocol.io/)

--- examples/model-providers/README.md ---
# Model Providers Examples

This directory contains small scripts showing how to integrate custom model providers. Run them with `pnpm` using the commands shown below.

- `custom-example-agent.ts` – Pass a model instance directly to an `Agent`.
  ```bash
  pnpm -F model-providers start:custom-example-agent
  ```
- `custom-example-global.ts` – Configure a global model provider. Requires environment variables `EXAMPLE_BASE_URL`, `EXAMPLE_API_KEY`, and `EXAMPLE_MODEL_NAME`.
  ```bash
  pnpm -F model-providers start:custom-example-global
  ```
- `custom-example-provider.ts` – Create a custom `ModelProvider` for a single run (same environment variables as above).
  ```bash
  pnpm -F model-providers start:custom-example-provider
  ```


--- examples/nextjs/README.md ---
# Next.js Demo

This example shows a basic example of how to use human-in-the-loop in a Next.js application.

Right now it only uses a synchronous approach without streaming and storing in an in-memory DB.

Eventually we will add more examples.

## Run the example

Set the `OPENAI_API_KEY` environment variable and run:

```bash
pnpm -F nextjs dev
```

Open [http://localhost:3000](http://localhost:3000) in your browser and ask `What is the weather in San Francisco and Oakland?`

## Endpoints

- **`/`** – The basic example that actually handles receiving the approval requests and sending messages to the API. Code in `src/app/page.tsx`.
- **`/api/basic`** – The endpoint that gets triggered to run the agent. Code in `src/app/websocket/page.tsx`.

## Other files

- `src/components/Approvals.tsx` — renders the approval dialog
- `src/agents.ts` — contains the basic Agent configuration
- `src/db.ts` — contains the mock database implementation


## Links discovered
- [http://localhost:3000](http://localhost:3000)

--- .changeset/comprehensive-ai-sdk-error-messages.md ---
---
'@openai/agents-extensions': patch
---

Improve AI SDK error messages in tracing to include comprehensive error details like responseBody, statusCode, and responseHeaders when tracing is enabled.


--- packages/agents-extensions/src/aiSdk.ts ---
import type {
  JSONSchema7,
  LanguageModelV2,
  LanguageModelV2CallOptions,
  LanguageModelV2FunctionTool,
  LanguageModelV2Message,
  LanguageModelV2Prompt,
  LanguageModelV2ProviderDefinedTool,
  LanguageModelV2ToolCallPart,
  LanguageModelV2ToolChoice,
  LanguageModelV2ToolResultPart,
} from '@ai-sdk/provider';
import {
  createGenerationSpan,
  Model,
  ModelRequest,
  ModelResponse,
  protocol,
  resetCurrentSpan,
  ResponseStreamEvent,
  SerializedHandoff,
  SerializedOutputType,
  SerializedTool,
  setCurrentSpan,
  Usage,
  UserError,
  withGenerationSpan,
  getLogger,
  ModelSettingsToolChoice,
} from '@openai/agents';
import { isZodObject } from '@openai/agents/utils';
import { encodeUint8ArrayToBase64 } from '@openai/agents/utils';

/**
 * @internal
 * Converts a list of model items to a list of language model V2 messages.
 *
 * @param model - The model to use.
 * @param items - The items to convert.
 * @returns The list of language model V2 messages.
 */
export function itemsToLanguageV2Messages(
  model: LanguageModelV2,
  items: protocol.ModelItem[],
): LanguageModelV2Message[] {
  const messages: LanguageModelV2Message[] = [];
  let currentAssistantMessage: LanguageModelV2Message | undefined;

  for (const item of items) {
    if (item.type === 'message' || typeof item.type === 'undefined') {
      const { role, content, providerData } = item;
      if (role === 'system') {
        messages.push({
          role: 'system',
          content: content,
          providerOptions: {
            ...(providerData ?? {}),
          },
        });
        continue;
      }

      if (role === 'user') {
        messages.push({
          role,
          content:
            typeof content === 'string'
              ? [{ type: 'text', text: content }]
              : content.map((c) => {
                  const { providerData: contentProviderData } = c;
                  if (c.type === 'input_text') {
                    return {
                      type: 'text',
                      text: c.text,
                      providerOptions: {
                        ...(contentProviderData ?? {}),
                      },
                    };
                  }
                  if (c.type === 'input_image') {
                    const imageSource =
                      typeof c.image === 'string'
                        ? c.image
                        : typeof (c as any).imageUrl === 'string'
                          ? (c as any).imageUrl
                          : undefined;

                    if (!imageSource) {
                      throw new UserError(
                        'Only image URLs are supported for user inputs.',
                      );
                    }

                    const url = new URL(imageSource);
                    return {
                      type: 'file',
                      data: url,
                      mediaType: 'image/*',
                      providerOptions: {
                        ...(contentProviderData ?? {}),
                      },
                    };
                  }
                  if (c.type === 'input_file') {
                    throw new UserError('File inputs are not supported.');
                  }
                  throw new UserError(`Unknown content type: ${c.type}`);
                }),
          providerOptions: {
            ...(providerData ?? {}),
          },
        });
        continue;
      }

      if (role === 'assistant') {
        if (currentAssistantMessage) {
          messages.push(currentAssistantMessage);
          currentAssistantMessage = undefined;
        }

        messages.push({
          role,
          content: content
            .filter((c) => c.type === 'output_text')
            .map((c) => {
              const { providerData: contentProviderData } = c;
              return {
                type: 'text',
                text: c.text,
                providerOptions: {
                  ...(contentProviderData ?? {}),
                },
              };
            }),
          providerOptions: {
            ...(providerData ?? {}),
          },
        });
        continue;
      }

      const exhaustiveMessageTypeCheck = item satisfies never;
      throw new Error(`Unknown message type: ${exhaustiveMessageTypeCheck}`);
    } else if (item.type === 'function_call') {
      if (!currentAssistantMessage) {
        currentAssistantMessage = {
          role: 'assistant',
          content: [],
          providerOptions: {
            ...(item.providerData ?? {}),
          },
        };
      }

      if (
        Array.isArray(currentAssistantMessage.content) &&
        currentAssistantMessage.role === 'assistant'
      ) {
        const content: LanguageModelV2ToolCallPart = {
          type: 'tool-call',
          toolCallId: item.callId,
          toolName: item.name,
          input: parseArguments(item.arguments),
          providerOptions: {
            ...(item.providerData ?? {}),
          },
        };
        currentAssistantMessage.content.push(content);
      }
      continue;
    } else if (item.type === 'function_call_result') {
      if (currentAssistantMessage) {
        messages.push(currentAssistantMessage);
        currentAssistantMessage = undefined;
      }
      const toolResult: LanguageModelV2ToolResultPart = {
        type: 'tool-result',
        toolCallId: item.callId,
        toolName: item.name,
        output: convertToAiSdkOutput(item.output),
        providerOptions: {
          ...(item.providerData ?? {}),
        },
      };
      messages.push({
        role: 'tool',
        content: [toolResult],
        providerOptions: {
          ...(item.providerData ?? {}),
        },
      });
      continue;
    }

    if (item.type === 'hosted_tool_call') {
      throw new UserError('Hosted tool calls are not supported');
    }

    if (item.type === 'computer_call') {
      throw new UserError('Computer calls are not supported');
    }

    if (item.type === 'computer_call_result') {
      throw new UserError('Computer call results are not supported');
    }

    if (item.type === 'shell_call') {
      throw new UserError('Shell calls are not supported');
    }

    if (item.type === 'shell_call_output') {
      throw new UserError('Shell call results are not supported');
    }

    if (item.type === 'apply_patch_call') {
      throw new UserError('Apply patch calls are not supported');
    }

    if (item.type === 'apply_patch_call_output') {
      throw new UserError('Apply patch call results are not supported');
    }

    if (
      item.type === 'reasoning' &&
      item.content.length > 0 &&
      typeof item.content[0].text === 'string'
    ) {
      messages.push({
        role: 'assistant',
        content: [
          {
            type: 'reasoning',
            text: item.content[0].text,
            providerOptions: { ...(item.providerData ?? {}) },
          },
        ],
        providerOptions: {
          ...(item.providerData ?? {}),
        },
      });
      continue;
    }

    if (item.type === 'unknown') {
      messages.push({ ...(item.providerData ?? {}) } as LanguageModelV2Message);
      continue;
    }

    if (item) {
      throw new UserError(`Unknown item type: ${item.type}`);
    }

    const itemType = item satisfies never;
    throw new UserError(`Unknown item type: ${itemType}`);
  }

  if (currentAssistantMessage) {
    messages.push(currentAssistantMessage);
  }

  return messages;
}

/**
 * @internal
 * Converts a handoff to a language model V2 tool.
 *
 * @param model - The model to use.
 * @param handoff - The handoff to convert.
 */
function handoffToLanguageV2Tool(
  model: LanguageModelV2,
  handoff: SerializedHandoff,
): LanguageModelV2FunctionTool {
  return {
    type: 'function',
    name: handoff.toolName,
    description: handoff.toolDescription,
    inputSchema: handoff.inputJsonSchema as JSONSchema7,
  };
}

function convertToAiSdkOutput(
  output: protocol.FunctionCallResultItem['output'],
): LanguageModelV2ToolResultPart['output'] {
  if (typeof output === 'string') {
    return { type: 'text', value: output };
  }
  if (Array.isArray(output)) {
    return convertStructuredOutputsToAiSdkOutput(output);
  }
  if (isRecord(output) && typeof output.type === 'string') {
    if (output.type === 'text' && typeof output.text === 'string') {
      return { type: 'text', value: output.text };
    }
    if (output.type === 'image' || output.type === 'file') {
      const structuredOutputs = convertLegacyToolOutputContent(
        output as protocol.ToolCallOutputContent,
      );
      return convertStructuredOutputsToAiSdkOutput(structuredOutputs);
    }
  }
  return { type: 'text', value: String(output) };
}

/**
 * Normalises legacy ToolOutput* objects into the protocol `input_*` shapes so that the AI SDK
 * bridge can treat all tool results uniformly.
 */
function convertLegacyToolOutputContent(
  output: protocol.ToolCallOutputContent,
): protocol.ToolCallStructuredOutput[] {
  if (output.type === 'text') {
    const structured: protocol.InputText = {
      type: 'input_text',
      text: output.text,
    };
    if (output.providerData) {
      structured.providerData = output.providerData;
    }
    return [structured];
  }

  if (output.type === 'image') {
    const structured: protocol.InputImage = { type: 'input_image' };

    if (output.detail) {
      structured.detail = output.detail;
    }

    if (typeof output.image === 'string' && output.image.length > 0) {
      structured.image = output.image;
    } else if (isRecord(output.image)) {
      const imageObj = output.image as Record<string, any>;
      const inlineMediaType = getImageInlineMediaType(imageObj);
      if (typeof imageObj.url === 'string' && imageObj.url.length > 0) {
        structured.image = imageObj.url;
      } else if (
        typeof imageObj.data === 'string' &&
        imageObj.data.length > 0
      ) {
        structured.image = formatInlineData(imageObj.data, inlineMediaType);
      } else if (
        imageObj.data instanceof Uint8Array &&
        imageObj.data.length > 0
      ) {
        structured.image = formatInlineData(imageObj.data, inlineMediaType);
      } else {
        const referencedId =
          (typeof imageObj.fileId === 'string' &&
            imageObj.fileId.length > 0 &&
            imageObj.fileId) ||
          (typeof imageObj.id === 'string' && imageObj.id.length > 0
            ? imageObj.id
            : undefined);
        if (referencedId) {
          structured.image = { id: referencedId };
        }
      }
    }
    if (output.providerData) {
      structured.providerData = output.providerData;
    }
    return [structured];
  }

  if (output.type === 'file') {
    return [];
  }
  throw new UserError(
    `Unsupported tool output type: ${JSON.stringify(output)}`,
  );
}

function schemaAcceptsObject(schema: JSONSchema7 | undefined): boolean {
  if (!schema) {
    return false;
  }
  const schemaType = schema.type;
  if (Array.isArray(schemaType)) {
    if (schemaType.includes('object')) {
      return true;
    }
  } else if (schemaType === 'object') {
    return true;
  }
  return Boolean(schema.properties || schema.additionalProperties);
}

function expectsObjectArguments(
  tool: SerializedTool | SerializedHandoff | undefined,
): boolean {
  if (!tool) {
    return false;
  }
  if ('toolName' in tool) {
    return schemaAcceptsObject(tool.inputJsonSchema as JSONSchema7 | undefined);
  }
  if (tool.type === 'function') {
    return schemaAcceptsObject(tool.parameters as JSONSchema7 | undefined);
  }
  return false;
}

/**
 * Maps the protocol-level structured outputs into the Language Model V2 result primitives.
 * The AI SDK expects either plain text or content parts (text + media), so we merge multiple
 * items accordingly.
 */
function convertStructuredOutputsToAiSdkOutput(
  outputs: protocol.ToolCallStructuredOutput[],
): LanguageModelV2ToolResultPart['output'] {
  const textParts: string[] = [];
  const mediaParts: Array<{ type: 'media'; data: string; mediaType: string }> =
    [];

  for (const item of outputs) {
    if (item.type === 'input_text') {
      textParts.push(item.text);
      continue;
    }
    if (item.type === 'input_image') {
      const imageValue =
        typeof item.image === 'string'
          ? item.image
          : isRecord(item.image) && typeof item.image.id === 'string'
            ? `openai-file:${item.image.id}`
            : typeof (item as any).imageUrl === 'string'
              ? (item as any).imageUrl
              : undefined;

      const legacyFileId = (item as any).fileId;
      if (!imageValue && typeof legacyFileId === 'string') {
        textParts.push(`[image file_id=${legacyFileId}]`);
        continue;
      }
      if (!imageValue) {
        textParts.push('[image]');
        continue;
      }
      try {
        const url = new URL(imageValue);
        mediaParts.push({
          type: 'media',
          data: url.toString(),
          mediaType: 'image/*',
        });
      } catch {
        textParts.push(imageValue);
      }
      continue;
    }

    if (item.type === 'input_file') {
      textParts.push('[file output skipped]');
      continue;
    }
  }

  if (mediaParts.length === 0) {
    return { type: 'text', value: textParts.join('') };
  }

  const value: Array<
    | { type: 'text'; text: string }
    | { type: 'media'; data: string; mediaType: string }
  > = [];

  if (textParts.length > 0) {
    value.push({ type: 'text', text: textParts.join('') });
  }
  value.push(...mediaParts);
  return { type: 'content', value };
}

function isRecord(value: unknown): value is Record<string, any> {
  return typeof value === 'object' && value !== null;
}

function getImageInlineMediaType(
  source: Record<string, any>,
): string | undefined {
  if (typeof source.mediaType === 'string' && source.mediaType.length > 0) {
    return source.mediaType;
  }
  return undefined;
}

function formatInlineData(
  data: string | Uint8Array,
  mediaType?: string,
): string {
  const base64 =
    typeof data === 'string' ? data : encodeUint8ArrayToBase64(data);
  return mediaType ? `data:${mediaType};base64,${base64}` : base64;
}

/**
 * @internal
 * Converts a tool to a language model V2 tool.
 *
 * @param model - The model to use.
 * @param tool - The tool to convert.
 */
export function toolToLanguageV2Tool(
  model: LanguageModelV2,
  tool: SerializedTool,
): LanguageModelV2FunctionTool | LanguageModelV2ProviderDefinedTool {
  if (tool.type === 'function') {
    return {
      type: 'function',
      name: tool.name,
      description: tool.description,
      inputSchema: tool.parameters as JSONSchema7,
    };
  }

  if (tool.type === 'hosted_tool') {
    return {
      type: 'provider-defined',
      id: `${model.provider}.${tool.name}`,
      name: tool.name,
      args: tool.providerData?.args ?? {},
    };
  }

  if (tool.type === 'computer') {
    return {
      type: 'provider-defined',
      id: `${model.provider}.${tool.name}`,
      name: tool.name,
      args: {
        environment: tool.environment,
        display_width: tool.dimensions[0],
        display_height: tool.dimensions[1],
      },
    };
  }

  throw new Error(`Unsupported tool type: ${JSON.stringify(tool)}`);
}

/**
 * @internal
 * Converts an output type to a language model V2 response format.
 *
 * @param outputType - The output type to convert.
 * @returns The language model V2 response format.
 */
export function getResponseFormat(
  outputType: SerializedOutputType,
): LanguageModelV2CallOptions['responseFormat'] {
  if (outputType === 'text') {
    return {
      type: 'text',
    };
  }

  return {
    type: 'json',
    name: outputType.name,
    schema: outputType.schema,
  };
}

/**
 * Wraps a model from the AI SDK that adheres to the LanguageModelV2 spec to be used used as a model
 * in the OpenAI Agents SDK to use other models.
 *
 * While you can use this with the OpenAI models, it is recommended to use the default OpenAI model
 * provider instead.
 *
 * If tracing is enabled, the model will send generation spans to your traces processor.
 *
 * ```ts
 * import { aisdk } from '@openai/agents-extensions';
 * import { openai } from '@ai-sdk/openai';
 *
 * const model = aisdk(openai('gpt-4o'));
 *
 * const agent = new Agent({
 *   name: 'My Agent',
 *   model
 * });
 * ```
 *
 * @param model - The Vercel AI SDK model to wrap.
 * @returns The wrapped model.
 */
export class AiSdkModel implements Model {
  #model: LanguageModelV2;
  #logger = getLogger('openai-agents:extensions:ai-sdk');
  constructor(model: LanguageModelV2) {
    this.#model = model;
  }

  async getResponse(request: ModelRequest) {
    return withGenerationSpan(async (span) => {
      try {
        span.spanData.model = this.#model.provider + ':' + this.#model.modelId;
        span.spanData.model_config = {
          provider: this.#model.provider,
          model_impl: 'ai-sdk',
        };

        let input: LanguageModelV2Prompt =
          typeof request.input === 'string'
            ? [
                {
                  role: 'user',
                  content: [{ type: 'text', text: request.input }],
                },
              ]
            : itemsToLanguageV2Messages(this.#model, request.input);

        if (request.systemInstructions) {
          input = [
            {
              role: 'system',
              content: request.systemInstructions,
            },
            ...input,
          ];
        }

        const tools = request.tools.map((tool) =>
          toolToLanguageV2Tool(this.#model, tool),
        );

        request.handoffs.forEach((handoff) => {
          tools.push(handoffToLanguageV2Tool(this.#model, handoff));
        });

        if (span && request.tracing === true) {
          span.spanData.input = input;
        }

        if (isZodObject(request.outputType)) {
          throw new UserError('Zod output type is not yet supported');
        }

        const responseFormat: LanguageModelV2CallOptions['responseFormat'] =
          getResponseFormat(request.outputType);

        const aiSdkRequest: LanguageModelV2CallOptions = {
          tools,
          toolChoice: toolChoiceToLanguageV2Format(
            request.modelSettings.toolChoice,
          ),
          prompt: input,
          temperature: request.modelSettings.temperature,
          topP: request.modelSettings.topP,
          frequencyPenalty: request.modelSettings.frequencyPenalty,
          presencePenalty: request.modelSettings.presencePenalty,
          maxOutputTokens: request.modelSettings.maxTokens,
          responseFormat,
          abortSignal: request.signal,

          ...(request.modelSettings.providerData ?? {}),
        };

        if (this.#logger.dontLogModelData) {
          this.#logger.debug('Request sent');
        } else {
          this.#logger.debug('Request:', JSON.stringify(aiSdkRequest, null, 2));
        }

        const result = await this.#model.doGenerate(aiSdkRequest);

        const output: ModelResponse['output'] = [];

        const resultContent = (result as any).content ?? [];

        // Extract and add reasoning items FIRST (required by Anthropic: thinking blocks must precede tool_use blocks)
        const reasoningParts = resultContent.filter(
          (c: any) => c && c.type === 'reasoning',
        );
        for (const reasoningPart of reasoningParts) {
          const reasoningText =
            typeof reasoningPart.text === 'string' ? reasoningPart.text : '';
          output.push({
            type: 'reasoning',
            content: [{ type: 'input_text', text: reasoningText }],
            rawContent: [{ type: 'reasoning_text', text: reasoningText }],
            // Preserve provider-specific metadata (including signature for Anthropic extended thinking)
            providerData: reasoningPart.providerMetadata ?? undefined,
          });
        }

        const toolCalls = resultContent.filter(
          (c: any) => c && c.type === 'tool-call',
        );
        const hasToolCalls = toolCalls.length > 0;

        const toolsNameToToolMap = new Map<
          string,
          SerializedTool | SerializedHandoff
        >(request.tools.map((tool) => [tool.name, tool] as const));

        for (const handoff of request.handoffs) {
          toolsNameToToolMap.set(handoff.toolName, handoff);
        }
        for (const toolCall of toolCalls) {
          const requestedTool =
            typeof toolCall.toolName === 'string'
              ? toolsNameToToolMap.get(toolCall.toolName)
              : undefined;

          if (!requestedTool && toolCall.toolName) {
            this.#logger.warn(
              `Received tool call for unknown tool '${toolCall.toolName}'.`,
            );
          }

          let toolCallArguments: string;
          if (typeof toolCall.input === 'string') {
            toolCallArguments =
              toolCall.input === '' && expectsObjectArguments(requestedTool)
                ? JSON.stringify({})
                : toolCall.input;
          } else {
            toolCallArguments = JSON.stringify(toolCall.input ?? {});
          }
          output.push({
            type: 'function_call',
            callId: toolCall.toolCallId,
            name: toolCall.toolName,
            arguments: toolCallArguments,
            status: 'completed',
            providerData:
              toolCall.providerMetadata ??
              (hasToolCalls ? result.providerMetadata : undefined),
          });
        }

        // Some of other platforms may return both tool calls and text.
        // Putting a text message here will let the agent loop to complete,
        // so adding this item only when the tool calls are empty.
        // Note that the same support is not available for streaming mode.
        if (!hasToolCalls) {
          const textItem = resultContent.find(
            (c: any) => c && c.type === 'text' && typeof c.text === 'string',
          );
          if (textItem) {
            output.push({
              type: 'message',
              content: [{ type: 'output_text', text: textItem.text }],
              role: 'assistant',
              status: 'completed',
              providerData: (result as any).providerMetadata,
            });
          }
        }

        if (span && request.tracing === true) {
          span.spanData.output = output;
        }

        const response = {
          responseId: (result as any).response?.id ?? 'FAKE_ID',
          usage: new Usage({
            inputTokens: Number.isNaN((result as any).usage?.inputTokens)
              ? 0
              : ((result as any).usage?.inputTokens ?? 0),
            outputTokens: Number.isNaN((result as any).usage?.outputTokens)
              ? 0
              : ((result as any).usage?.outputTokens ?? 0),
            totalTokens:
              (Number.isNaN((result as any).usage?.inputTokens)
                ? 0
                : ((result as any).usage?.inputTokens ?? 0)) +
                (Number.isNaN((result as any).usage?.outputTokens)
                  ? 0
                  : ((result as any).usage?.outputTokens ?? 0)) || 0,
          }),
          output,
          providerData: result,
        } as const;

        if (span && request.tracing === true) {
          span.spanData.usage = {
            // Note that tracing supports only input and output tokens for Chat Completions.
            // So, we don't include other properties here.
            input_tokens: response.usage.inputTokens,
            output_tokens: response.usage.outputTokens,
          };
        }

        if (this.#logger.dontLogModelData) {
          this.#logger.debug('Response ready');
        } else {
          this.#logger.debug('Response:', JSON.stringify(response, null, 2));
        }

        return response;
      } catch (error) {
        if (error instanceof Error) {
          span.setError({
            message: request.tracing === true ? error.message : 'Unknown error',
            data: {
              error:
                request.tracing === true
                  ? {
                      name: error.name,
                      message: error.message,
                      // Include AI SDK specific error fields if they exist.
                      ...(typeof error === 'object' && error !== null
                        ? {
                            ...('responseBody' in error
                              ? { responseBody: (error as any).responseBody }
                              : {}),
                            ...('responseHeaders' in error
                              ? {
                                  responseHeaders: (error as any)
                                    .responseHeaders,
                                }
                              : {}),
                            ...('statusCode' in error
                              ? { statusCode: (error as any).statusCode }
                              : {}),
                            ...('cause' in error
                              ? { cause: (error as any).cause }
                              : {}),
                          }
                        : {}),
                    }
                  : error.name,
            },
          });
        } else {
          span.setError({
            message: 'Unknown error',
            data: {
              error: request.tracing === true ? String(error) : undefined,
            },
          });
        }
        throw error;
      }
    });
  }

  async *getStreamedResponse(
    request: ModelRequest,
  ): AsyncIterable<ResponseStreamEvent> {
    const span = request.tracing ? createGenerationSpan() : undefined;
    try {
      if (span) {
        span.start();
        setCurrentSpan(span);
      }

      if (span?.spanData) {
        span.spanData.model = this.#model.provider + ':' + this.#model.modelId;
        span.spanData.model_config = {
          provider: this.#model.provider,
          model_impl: 'ai-sdk',
        };
      }

      let input: LanguageModelV2Prompt =
        typeof request.input === 'string'
          ? [
              {
                role: 'user',
                content: [{ type: 'text', text: request.input }],
              },
            ]
          : itemsToLanguageV2Messages(this.#model, request.input);

      if (request.systemInstructions) {
        input = [
          {
            role: 'system',
            content: request.systemInstructions,
          },
          ...input,
        ];
      }

      const tools = request.tools.map((tool) =>
        toolToLanguageV2Tool(this.#model, tool),
      );

      request.handoffs.forEach((handoff) => {
        tools.push(handoffToLanguageV2Tool(this.#model, handoff));
      });

      if (span && request.tracing === true) {
        span.spanData.input = input;
      }

      const responseFormat: LanguageModelV2CallOptions['responseFormat'] =
        getResponseFormat(request.outputType);

      const aiSdkRequest: LanguageModelV2CallOptions = {
        tools,
        prompt: input,
        temperature: request.modelSettings.temperature,
        topP: request.modelSettings.topP,
        frequencyPenalty: request.modelSettings.frequencyPenalty,
        presencePenalty: request.modelSettings.presencePenalty,
        maxOutputTokens: request.modelSettings.maxTokens,
        responseFormat,
        abortSignal: request.signal,
        ...(request.modelSettings.providerData ?? {}),
      };

      if (this.#logger.dontLogModelData) {
        this.#logger.debug('Request received (streamed)');
      } else {
        this.#logger.debug(
          'Request (streamed):',
          JSON.stringify(aiSdkRequest, null, 2),
        );
      }

      const { stream } = await this.#model.doStream(aiSdkRequest);

      let started = false;
      let responseId: string | undefined;
      let usagePromptTokens = 0;
      let usageCompletionTokens = 0;
      const functionCalls: Record<string, protocol.FunctionCallItem> = {};
      let textOutput: protocol.OutputText | undefined;

      // State for tracking reasoning blocks (for Anthropic extended thinking)
      const reasoningBlocks: Record<
        string,
        {
          text: string;
          providerMetadata?: Record<string, any>;
        }
      > = {};

      for await (const part of stream) {
        if (!started) {
          started = true;
          yield { type: 'response_started' };
        }

        yield { type: 'model', event: part };

        switch (part.type) {
          case 'text-delta': {
            if (!textOutput) {
              textOutput = { type: 'output_text', text: '' };
            }
            textOutput.text += (part as any).delta;
            yield { type: 'output_text_delta', delta: (part as any).delta };
            break;
          }
          case 'reasoning-start': {
            // Start tracking a new reasoning block
            const reasoningId = (part as any).id ?? 'default';
            reasoningBlocks[reasoningId] = {
              text: '',
              providerMetadata: (part as any).providerMetadata,
            };
            break;
          }
          case 'reasoning-delta': {
            // Accumulate reasoning text
            const reasoningId = (part as any).id ?? 'default';
            if (!reasoningBlocks[reasoningId]) {
              reasoningBlocks[reasoningId] = {
                text: '',
                providerMetadata: (part as any).providerMetadata,
              };
            }
            reasoningBlocks[reasoningId].text += (part as any).delta ?? '';
            break;
          }
          case 'reasoning-end': {
            // Capture final provider metadata (may contain signature)
            const reasoningId = (part as any).id ?? 'default';
            if (
              reasoningBlocks[reasoningId] &&
              (part as any).providerMetadata
            ) {
              reasoningBlocks[reasoningId].providerMetadata = (
                part as any
              ).providerMetadata;
            }
            break;
          }
          case 'tool-call': {
            const toolCallId = (part as any).toolCallId;
            if (toolCallId) {
              functionCalls[toolCallId] = {
                type: 'function_call',
                callId: toolCallId,
                name: (part as any).toolName,
                arguments: (part as any).input ?? '',
                status: 'completed',
                ...((part as any).providerMetadata
                  ? { providerData: (part as any).providerMetadata }
                  : {}),
              };
            }
            break;
          }
          case 'response-metadata': {
            if ((part as any).id) {
              responseId = (part as any).id;
            }
            break;
          }
          case 'finish': {
            usagePromptTokens = Number.isNaN((part as any).usage?.inputTokens)
              ? 0
              : ((part as any).usage?.inputTokens ?? 0);
            usageCompletionTokens = Number.isNaN(
              (part as any).usage?.outputTokens,
            )
              ? 0
              : ((part as any).usage?.outputTokens ?? 0);
            break;
          }
          case 'error': {
            throw part.error;
          }
          default:
            break;
        }
      }

      const outputs: protocol.OutputModelItem[] = [];

      // Add reasoning items FIRST (required by Anthropic: thinking blocks must precede tool_use blocks)
      // Emit reasoning item even when text is empty to preserve signature in providerData for redacted thinking streams
      for (const [reasoningId, reasoningBlock] of Object.entries(
        reasoningBlocks,
      )) {
        if (reasoningBlock.text || reasoningBlock.providerMetadata) {
          outputs.push({
            type: 'reasoning',
            id: reasoningId !== 'default' ? reasoningId : undefined,
            content: [{ type: 'input_text', text: reasoningBlock.text }],
            rawContent: [{ type: 'reasoning_text', text: reasoningBlock.text }],
            // Preserve provider-specific metadata (including signature for Anthropic extended thinking)
            providerData: reasoningBlock.providerMetadata ?? undefined,
          });
        }
      }

      if (textOutput) {
        outputs.push({
          type: 'message',
          role: 'assistant',
          content: [textOutput],
          status: 'completed',
        });
      }
      for (const fc of Object.values(functionCalls)) {
        outputs.push(fc);
      }

      const finalEvent: protocol.StreamEventResponseCompleted = {
        type: 'response_done',
        response: {
          id: responseId ?? 'FAKE_ID',
          usage: {
            inputTokens: usagePromptTokens,
            outputTokens: usageCompletionTokens,
            totalTokens: usagePromptTokens + usageCompletionTokens,
          },
          output: outputs,
        },
      };

      if (span && request.tracing === true) {
        span.spanData.output = outputs;
        span.spanData.usage = {
          // Note that tracing supports only input and output tokens for Chat Completions.
          // So, we don't include other properties here.
          input_tokens: finalEvent.response.usage.inputTokens,
          output_tokens: finalEvent.response.usage.outputTokens,
        };
      }

      if (this.#logger.dontLogModelData) {
        this.#logger.debug('Response ready (streamed)');
      } else {
        this.#logger.debug(
          'Response (streamed):',
          JSON.stringify(finalEvent.response, null, 2),
        );
      }

      yield finalEvent;
    } catch (error) {
      if (span) {
        span.setError({
          message:
            error instanceof Error ? error.message : 'Error streaming response',
          data: {
            error:
              request.tracing === true
                ? error instanceof Error
                  ? {
                      name: error.name,
                      message: error.message,
                      // Include AI SDK specific error fields if they exist.
                      ...(typeof error === 'object' && error !== null
                        ? {
                            ...('responseBody' in error
                              ? { responseBody: (error as any).responseBody }
                              : {}),
                            ...('responseHeaders' in error
                              ? {
                                  responseHeaders: (error as any)
                                    .responseHeaders,
                                }
                              : {}),
                            ...('statusCode' in error
                              ? { statusCode: (error as any).statusCode }
                              : {}),
                            ...('cause' in error
                              ? { cause: (error as any).cause }
                              : {}),
                          }
                        : {}),
                    }
                  : String(error)
                : error instanceof Error
                  ? error.name
                  : undefined,
          },
        });
      }
      throw error;
    } finally {
      if (span) {
        span.end();
        resetCurrentSpan();
      }
    }
  }
}

/**
 * Wraps a model from the AI SDK that adheres to the LanguageModelV2 spec to be used used as a model
 * in the OpenAI Agents SDK to use other models.
 *
 * While you can use this with the OpenAI models, it is recommended to use the default OpenAI model
 * provider instead.
 *
 * If tracing is enabled, the model will send generation spans to your traces processor.
 *
 * ```ts
 * import { aisdk } from '@openai/agents-extensions';
 * import { openai } from '@ai-sdk/openai';
 *
 * const model = aisdk(openai('gpt-4o'));
 *
 * const agent = new Agent({
 *   name: 'My Agent',
 *   model
 * });
 * ```
 *
 * @param model - The Vercel AI SDK model to wrap.
 * @returns The wrapped model.
 */
export function aisdk(model: LanguageModelV2) {
  return new AiSdkModel(model);
}

export function parseArguments(args: string | undefined | null): any {
  if (!args) {
    return {};
  }

  try {
    return JSON.parse(args);
  } catch (_) {
    return {};
  }
}

export function toolChoiceToLanguageV2Format(
  toolChoice: ModelSettingsToolChoice | undefined,
): LanguageModelV2ToolChoice | undefined {
  if (!toolChoice) {
    return undefined;
  }
  switch (toolChoice) {
    case 'auto':
      return { type: 'auto' };
    case 'required':
      return { type: 'required' };
    case 'none':
      return { type: 'none' };
    default:
      return { type: 'tool', toolName: toolChoice };
  }
}


--- packages/agents-extensions/test/aiSdk.test.ts ---
import { describe, test, expect, vi } from 'vitest';
import {
  AiSdkModel,
  getResponseFormat,
  itemsToLanguageV2Messages,
  parseArguments,
  toolChoiceToLanguageV2Format,
  toolToLanguageV2Tool,
} from '../src/aiSdk';
import { protocol, withTrace, UserError } from '@openai/agents';
import { ReadableStream } from 'node:stream/web';
import type { LanguageModelV2 } from '@ai-sdk/provider';
import type { SerializedOutputType } from '@openai/agents';

function stubModel(
  partial: Partial<Pick<LanguageModelV2, 'doGenerate' | 'doStream'>>,
): LanguageModelV2 {
  return {
    specificationVersion: 'v2',
    provider: 'stub',
    modelId: 'm',
    supportedUrls: {} as any,
    async doGenerate(options) {
      if (partial.doGenerate) {
        return partial.doGenerate(options) as any;
      }
      return {
        content: [],
        usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
        response: { id: 'id' },
        providerMetadata: {},
        finishReason: 'stop',
        warnings: [],
      } as any;
    },
    async doStream(options) {
      if (partial.doStream) {
        return partial.doStream(options);
      }
      return {
        stream: new ReadableStream(),
      } as any;
    },
  } as LanguageModelV2;
}

function partsStream(parts: any[]): ReadableStream<any> {
  return ReadableStream.from(
    (async function* () {
      for (const p of parts) {
        yield p;
      }
    })(),
  );
}

describe('getResponseFormat', () => {
  test('converts text output type', () => {
    const outputType: SerializedOutputType = 'text';
    const result = getResponseFormat(outputType);
    expect(result).toEqual({ type: 'text' });
  });

  test('converts json schema output type', () => {
    const outputType: SerializedOutputType = {
      type: 'json_schema',
      name: 'output',
      strict: false,
      schema: {
        type: 'object',
        properties: {
          name: { type: 'string' },
        },
        required: ['name'],
        additionalProperties: false,
      },
    };
    const result = getResponseFormat(outputType);
    expect(result).toEqual({
      type: 'json',
      name: outputType.name,
      schema: outputType.schema,
    });
  });
});

describe('itemsToLanguageV2Messages', () => {
  test('converts user text and function call items', () => {
    const items: protocol.ModelItem[] = [
      {
        role: 'user',
        content: [
          {
            type: 'input_text',
            text: 'hi',
            providerData: { test: { cacheControl: { type: 'ephemeral' } } },
          },
        ],
      } as any,
      {
        type: 'function_call',
        callId: '1',
        name: 'foo',
        arguments: '{}',
        providerData: { a: 1 },
      } as any,
      {
        type: 'function_call_result',
        callId: '1',
        name: 'foo',
        output: { type: 'text', text: 'out' },
        providerData: { b: 2 },
      } as any,
    ];

    const msgs = itemsToLanguageV2Messages(stubModel({}), items);
    expect(msgs).toEqual([
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: 'hi',
            providerOptions: { test: { cacheControl: { type: 'ephemeral' } } },
          },
        ],
        providerOptions: {},
      },
      {
        role: 'assistant',
        content: [
          {
            type: 'tool-call',
            toolCallId: '1',
            toolName: 'foo',
            input: {},
            providerOptions: { a: 1 },
          },
        ],
        providerOptions: { a: 1 },
      },
      {
        role: 'tool',
        content: [
          {
            type: 'tool-result',
            toolCallId: '1',
            toolName: 'foo',
            output: { type: 'text', value: 'out' },
            providerOptions: { b: 2 },
          },
        ],
        providerOptions: { b: 2 },
      },
    ]);
  });

  test('throws on built-in tool calls', () => {
    const items: protocol.ModelItem[] = [
      { type: 'hosted_tool_call', name: 'search' } as any,
    ];
    expect(() => itemsToLanguageV2Messages(stubModel({}), items)).toThrow();
  });

  test('throws on computer tool calls and results', () => {
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [
        { type: 'computer_call' } as any,
      ]),
    ).toThrow(UserError);
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [
        { type: 'computer_call_result' } as any,
      ]),
    ).toThrow(UserError);
  });

  test('throws on shell tool calls and results', () => {
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [{ type: 'shell_call' } as any]),
    ).toThrow(UserError);
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [
        { type: 'shell_call_output' } as any,
      ]),
    ).toThrow(UserError);
  });

  test('throws on apply_patch tool calls and results', () => {
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [
        { type: 'apply_patch_call' } as any,
      ]),
    ).toThrow(UserError);
    expect(() =>
      itemsToLanguageV2Messages(stubModel({}), [
        { type: 'apply_patch_call_output' } as any,
      ]),
    ).toThrow(UserError);
  });

  test('converts user images, function results and reasoning items', () => {
    const items: protocol.ModelItem[] = [
      {
        role: 'user',
        content: [
          { type: 'input_text', text: 'hi' },
          { type: 'input_image', image: 'http://x/img' },
        ],
      } as any,
      {
        type: 'function_call',
        callId: '1',
        name: 'do',
        arguments: '{}',
      } as any,
      {
        type: 'function_call_result',
        callId: '1',
        name: 'do',
        output: { type: 'text', text: 'out' },
      } as any,
      { type: 'reasoning', content: [{ text: 'why' }] } as any,
    ];
    const msgs = itemsToLanguageV2Messages(stubModel({}), items);
    expect(msgs).toEqual([
      {
        role: 'user',
        content: [
          { type: 'text', text: 'hi', providerOptions: {} },
          {
            type: 'file',
            data: new URL('http://x/img'),
            mediaType: 'image/*',
            providerOptions: {},
          },
        ],
        providerOptions: {},
      },
      {
        role: 'assistant',
        content: [
          {
            type: 'tool-call',
            toolCallId: '1',
            toolName: 'do',
            input: {},
            providerOptions: {},
          },
        ],
        providerOptions: {},
      },
      {
        role: 'tool',
        content: [
          {
            type: 'tool-result',
            toolCallId: '1',
            toolName: 'do',
            output: { type: 'text', value: 'out' },
            providerOptions: {},
          },
        ],
        providerOptions: {},
      },
      {
        role: 'assistant',
        content: [{ type: 'reasoning', text: 'why', providerOptions: {} }],
        providerOptions: {},
      },
    ]);
  });

  test('converts structured tool output lists', () => {
    const items: protocol.ModelItem[] = [
      {
        type: 'function_call',
        callId: 'tool-1',
        name: 'describe_image',
        arguments: '{}',
      } as any,
      {
        type: 'function_call_result',
        callId: 'tool-1',
        name: 'describe_image',
        output: [
          { type: 'input_text', text: 'A scenic view.' },
          {
            type: 'input_image',
            image: 'https://example.com/image.png',
          },
        ],
      } as any,
    ];

    const msgs = itemsToLanguageV2Messages(stubModel({}), items);
    expect(msgs).toEqual([
      {
        role: 'assistant',
        content: [
          {
            type: 'tool-call',
            toolCallId: 'tool-1',
            toolName: 'describe_image',
            input: {},
            providerOptions: {},
          },
        ],
        providerOptions: {},
      },
      {
        role: 'tool',
        content: [
          {
            type: 'tool-result',
            toolCallId: 'tool-1',
            toolName: 'describe_image',
            output: {
              type: 'content',
              value: [
                { type: 'text', text: 'A scenic view.' },
                {
                  type: 'media',
                  data: 'https://example.com/image.png',
                  mediaType: 'image/*',
                },
              ],
            },
            providerOptions: {},
          },
        ],
        providerOptions: {},
      },
    ]);
  });

  test('handles undefined providerData without throwing', () => {
    const items: protocol.ModelItem[] = [
      {
        role: 'user',
        content: [{ type: 'input_text', text: 'hi' }],
        providerData: undefined,
      } as any,
    ];
    expect(() => itemsToLanguageV2Messages(stubModel({}), items)).not.toThrow();
    const msgs = itemsToLanguageV2Messages(stubModel({}), items);
    expect(msgs).toEqual([
      {
        role: 'user',
        content: [{ type: 'text', text: 'hi', providerOptions: {} }],
        providerOptions: {},
      },
    ]);
  });

  test('throws UserError for unsupported content or unknown item type', () => {
    const bad: protocol.ModelItem[] = [
      { role: 'user', content: [{ type: 'bad' as any }] } as any,
    ];
    expect(() => itemsToLanguageV2Messages(stubModel({}), bad)).toThrow(
      UserError,
    );

    const unknown: protocol.ModelItem[] = [{ type: 'bogus' } as any];
    expect(() => itemsToLanguageV2Messages(stubModel({}), unknown)).toThrow(
      UserError,
    );
  });

  test('rejects input_file content', () => {
    const items: protocol.ModelItem[] = [
      {
        role: 'user',
        content: [
          {
            type: 'input_file',
            file: 'file_123',
          },
        ],
      } as any,
    ];

    expect(() => itemsToLanguageV2Messages(stubModel({}), items)).toThrow(
      /File inputs are not supported/,
    );
  });

  test('passes through unknown items via providerData', () => {
    const custom = { role: 'system', content: 'x', providerOptions: { a: 1 } };
    const items: protocol.ModelItem[] = [
      { type: 'unknown', providerData: custom } as any,
    ];
    const msgs = itemsToLanguageV2Messages(stubModel({}), items);
    expect(msgs).toEqual([custom]);
  });
});

describe('toolToLanguageV2Tool', () => {
  const model = stubModel({});
  test('maps function tools', () => {
    const tool = {
      type: 'function',
      name: 'foo',
      description: 'd',
      parameters: {} as any,
    } as any;
    expect(toolToLanguageV2Tool(model, tool)).toEqual({
      type: 'function',
      name: 'foo',
      description: 'd',
      inputSchema: {},
    });
  });

  test('maps builtin tools', () => {
    const tool = {
      type: 'hosted_tool',
      name: 'search',
      providerData: { args: { q: 1 } },
    } as any;
    expect(toolToLanguageV2Tool(model, tool)).toEqual({
      type: 'provider-defined',
      id: `${model.provider}.search`,
      name: 'search',
      args: { q: 1 },
    });
  });

  test('maps computer tools', () => {
    const tool = {
      type: 'computer',
      name: 'comp',
      environment: 'env',
      dimensions: [2, 3],
    } as any;
    expect(toolToLanguageV2Tool(model, tool)).toEqual({
      type: 'provider-defined',
      id: `${model.provider}.comp`,
      name: 'comp',
      args: { environment: 'env', display_width: 2, display_height: 3 },
    });
  });

  test('throws on unknown type', () => {
    const tool = { type: 'x', name: 'u' } as any;
    expect(() => toolToLanguageV2Tool(model, tool)).toThrow();
  });
});

describe('AiSdkModel.getResponse', () => {
  test('handles text output', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [{ type: 'text', text: 'ok' }],
            usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
            providerMetadata: { p: 1 },
            response: { id: 'id' },
            finishReason: 'stop',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output).toEqual([
      {
        type: 'message',
        role: 'assistant',
        content: [{ type: 'output_text', text: 'ok' }],
        status: 'completed',
        providerData: { p: 1 },
      },
    ]);
  });

  test('normalizes empty string tool input for object schemas', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [
              {
                type: 'tool-call',
                toolCallId: 'call-1',
                toolName: 'objectTool',
                input: '',
              },
            ],
            usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
            providerMetadata: { meta: true },
            response: { id: 'id' },
            finishReason: 'tool-calls',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [
          {
            type: 'function',
            name: 'objectTool',
            description: 'accepts object',
            parameters: {
              type: 'object',
              properties: {},
              additionalProperties: false,
            },
          } as any,
        ],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output).toHaveLength(1);
    expect(res.output[0]).toMatchObject({
      type: 'function_call',
      arguments: '{}',
    });
  });

  test('normalizes empty string tool input for handoff schemas', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [
              {
                type: 'tool-call',
                toolCallId: 'handoff-call',
                toolName: 'handoffTool',
                input: '',
              },
            ],
            usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
            providerMetadata: { meta: true },
            response: { id: 'id' },
            finishReason: 'tool-calls',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [
          {
            toolName: 'handoffTool',
            toolDescription: 'handoff accepts object',
            inputJsonSchema: {
              type: 'object',
              properties: {},
              additionalProperties: false,
            },
            strictJsonSchema: true,
          } as any,
        ],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output).toHaveLength(1);
    expect(res.output[0]).toMatchObject({
      type: 'function_call',
      arguments: '{}',
    });
  });

  test('forwards toolChoice to AI SDK (generate)', async () => {
    const seen: any[] = [];
    const model = new AiSdkModel(
      stubModel({
        async doGenerate(options) {
          seen.push(options.toolChoice);
          return {
            content: [{ type: 'text', text: 'ok' }],
            usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
            providerMetadata: {},
            response: { id: 'id' },
            finishReason: 'stop',
            warnings: [],
          } as any;
        },
      }),
    );

    // auto
    await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: { toolChoice: 'auto' },
        outputType: 'text',
        tracing: false,
      } as any),
    );
    // required
    await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: { toolChoice: 'required' },
        outputType: 'text',
        tracing: false,
      } as any),
    );
    // none
    await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: { toolChoice: 'none' },
        outputType: 'text',
        tracing: false,
      } as any),
    );
    // specific tool
    await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: { toolChoice: 'myTool' as any },
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(seen).toEqual([
      { type: 'auto' },
      { type: 'required' },
      { type: 'none' },
      { type: 'tool', toolName: 'myTool' },
    ]);
  });

  test('aborts when signal already aborted', async () => {
    const abort = new AbortController();
    abort.abort();
    const doGenerate = vi.fn(async (opts: any) => {
      if (opts.abortSignal?.aborted) {
        throw new Error('aborted');
      }
      return {
        content: [{ type: 'text', text: 'should not' }],
        usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
        response: { id: 'id' },
        finishReason: 'stop',
        warnings: [],
      };
    });
    const model = new AiSdkModel(
      stubModel({
        // @ts-expect-error don't care about the type error here
        doGenerate,
      }),
    );

    await expect(
      withTrace('t', () =>
        model.getResponse({
          input: 'hi',
          tools: [],
          handoffs: [],
          modelSettings: {},
          outputType: 'text',
          tracing: false,
          signal: abort.signal,
        } as any),
      ),
    ).rejects.toThrow('aborted');
    expect(doGenerate).toHaveBeenCalled();
  });

  test('handles function call output', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [
              {
                type: 'tool-call',
                toolCallId: 'c1',
                toolName: 'foo',
                input: {} as any,
              },
            ],
            usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
            providerMetadata: { p: 1 },
            response: { id: 'id' },
            finishReason: 'stop',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output).toEqual([
      {
        type: 'function_call',
        callId: 'c1',
        name: 'foo',
        arguments: '{}',
        status: 'completed',
        providerData: { p: 1 },
      },
    ]);
  });

  test('preserves per-tool-call providerMetadata (e.g., Gemini thoughtSignature)', async () => {
    const toolCallProviderMetadata = {
      google: { thoughtSignature: 'sig123' },
    };
    const resultProviderMetadata = {
      google: { usageMetadata: { totalTokenCount: 100 } },
    };

    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [
              {
                type: 'tool-call',
                toolCallId: 'c1',
                toolName: 'get_weather',
                input: { location: 'Tokyo' },
                providerMetadata: toolCallProviderMetadata,
              },
            ],
            usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
            providerMetadata: resultProviderMetadata,
            response: { id: 'resp-1' },
            finishReason: 'tool-calls',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'What is the weather in Tokyo?',
        tools: [
          {
            type: 'function',
            name: 'get_weather',
            description: 'Get weather',
            parameters: { type: 'object', properties: {} },
          },
        ],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output).toHaveLength(1);
    expect(res.output[0]).toMatchObject({
      type: 'function_call',
      callId: 'c1',
      name: 'get_weather',
      providerData: toolCallProviderMetadata,
    });
    // Ensure we get per-tool-call metadata, not result-level metadata
    expect(res.output[0].providerData).not.toEqual(resultProviderMetadata);
  });

  test('falls back to result.providerMetadata when toolCall.providerMetadata is undefined', async () => {
    const resultProviderMetadata = { fallback: true };

    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [
              {
                type: 'tool-call',
                toolCallId: 'c1',
                toolName: 'foo',
                input: {},
                // No providerMetadata on tool call
              },
            ],
            usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
            providerMetadata: resultProviderMetadata,
            response: { id: 'id' },
            finishReason: 'tool-calls',
            warnings: [],
          } as any;
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.output[0].providerData).toEqual(resultProviderMetadata);
  });

  test('propagates errors', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          throw new Error('bad');
        },
      }),
    );

    await expect(
      withTrace('t', () =>
        model.getResponse({
          input: 'hi',
          tools: [],
          handoffs: [],
          modelSettings: {},
          outputType: 'text',
          tracing: false,
        } as any),
      ),
    ).rejects.toThrow('bad');
  });

  test('prepends system instructions to prompt for doGenerate', async () => {
    let received: any;
    const model = new AiSdkModel(
      stubModel({
        async doGenerate(options) {
          received = options.prompt;
          return {
            content: [],
            usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
            providerMetadata: {},
            response: { id: 'id' },
            finishReason: 'stop',
            warnings: [],
          };
        },
      }),
    );

    await withTrace('t', () =>
      model.getResponse({
        systemInstructions: 'inst',
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(received[0]).toEqual({
      role: 'system',
      content: 'inst',
    });
  });

  test('handles NaN usage in doGenerate', async () => {
    const model = new AiSdkModel(
      stubModel({
        async doGenerate() {
          return {
            content: [],
            usage: {
              inputTokens: Number.NaN,
              outputTokens: Number.NaN,
              totalTokens: Number.NaN,
            },
            providerMetadata: {},
            response: { id: 'id' },
            finishReason: 'stop',
            warnings: [],
          };
        },
      }),
    );

    const res = await withTrace('t', () =>
      model.getResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(res.usage).toEqual({
      requests: 1,
      inputTokens: 0,
      outputTokens: 0,
      totalTokens: 0,
      inputTokensDetails: [],
      outputTokensDetails: [],
      requestUsageEntries: undefined,
    });
  });
});

describe('AiSdkModel.getStreamedResponse', () => {
  test('streams events and completes', async () => {
    const parts = [
      { type: 'text-delta', delta: 'a' },
      {
        type: 'tool-call',
        toolCallId: 'c1',
        toolName: 'foo',
        input: '{"k":"v"}',
      },
      { type: 'response-metadata', id: 'id1' },
      {
        type: 'finish',
        finishReason: 'stop',
        usage: { inputTokens: 1, outputTokens: 2 },
      },
    ];
    const model = new AiSdkModel(
      stubModel({
        async doStream() {
          return {
            stream: partsStream(parts),
          } as any;
        },
      }),
    );

    const events: any[] = [];
    for await (const ev of model.getStreamedResponse({
      input: 'hi',
      tools: [],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any)) {
      events.push(ev);
    }

    const final = events.at(-1);
    expect(final.type).toBe('response_done');
    expect(final.response.output).toEqual([
      {
        type: 'message',
        role: 'assistant',
        content: [{ type: 'output_text', text: 'a' }],
        status: 'completed',
      },
      {
        type: 'function_call',
        callId: 'c1',
        name: 'foo',
        arguments: '{"k":"v"}',
        status: 'completed',
      },
    ]);
  });

  test('preserves per-tool-call providerMetadata in streaming mode (e.g., Gemini thoughtSignature)', async () => {
    const toolCallProviderMetadata = {
      google: { thoughtSignature: 'stream-sig-456' },
    };

    const parts = [
      {
        type: 'tool-call',
        toolCallId: 'c1',
        toolName: 'get_weather',
        input: '{"location":"Tokyo"}',
        providerMetadata: toolCallProviderMetadata,
      },
      { type: 'response-metadata', id: 'resp-stream-1' },
      {
        type: 'finish',
        finishReason: 'tool-calls',
        usage: { inputTokens: 10, outputTokens: 20 },
      },
    ];

    const model = new AiSdkModel(
      stubModel({
        async doStream() {
          return {
            stream: partsStream(parts),
          } as any;
        },
      }),
    );

    const events: any[] = [];
    for await (const ev of model.getStreamedResponse({
      input: 'What is the weather?',
      tools: [
        {
          type: 'function',
          name: 'get_weather',
          description: 'Get weather',
          parameters: { type: 'object', properties: {} },
        },
      ],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any)) {
      events.push(ev);
    }

    const final = events.at(-1);
    expect(final.type).toBe('response_done');
    expect(final.response.output).toHaveLength(1);
    expect(final.response.output[0]).toMatchObject({
      type: 'function_call',
      callId: 'c1',
      name: 'get_weather',
      providerData: toolCallProviderMetadata,
    });
  });

  test('omits providerData in streaming mode when providerMetadata is not present', async () => {
    const parts = [
      {
        type: 'tool-call',
        toolCallId: 'c1',
        toolName: 'foo',
        input: '{}',
        // No providerMetadata
      },
      {
        type: 'finish',
        finishReason: 'tool-calls',
        usage: { inputTokens: 1, outputTokens: 2 },
      },
    ];

    const model = new AiSdkModel(
      stubModel({
        async doStream() {
          return {
            stream: partsStream(parts),
          } as any;
        },
      }),
    );

    const events: any[] = [];
    for await (const ev of model.getStreamedResponse({
      input: 'hi',
      tools: [],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any)) {
      events.push(ev);
    }

    const final = events.at(-1);
    expect(final.type).toBe('response_done');
    expect(final.response.output[0]).toMatchObject({
      type: 'function_call',
      callId: 'c1',
      name: 'foo',
    });
    // providerData should not be present when providerMetadata was not provided
    expect(final.response.output[0].providerData).toBeUndefined();
  });

  test('propagates stream errors', async () => {
    const err = new Error('bad');
    const parts = [{ type: 'error', error: err }];
    const model = new AiSdkModel(
      stubModel({
        async doStream() {
          return {
            stream: partsStream(parts),
          } as any;
        },
      }),
    );

    await expect(async () => {
      const iter = model.getStreamedResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any);

      for await (const ev of iter) {
        if (ev.type === 'response_done') {
          expect(ev.response.id).toBeDefined();
        } else if (ev.type === 'model') {
          expect(ev.event).toBeDefined();
        }
      }
    }).rejects.toThrow('bad');
  });

  test('aborts streaming when signal already aborted', async () => {
    const abort = new AbortController();
    abort.abort();
    const doStream = vi.fn(async (opts: any) => {
      if (opts.abortSignal?.aborted) {
        throw new Error('aborted');
      }
      return {
        stream: partsStream([]),
      } as any;
    });
    const model = new AiSdkModel(
      stubModel({
        doStream,
      }),
    );

    await expect(async () => {
      const iter = model.getStreamedResponse({
        input: 'hi',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
        signal: abort.signal,
      } as any);
      for await (const _ of iter) {
        /* nothing */
      }
    }).rejects.toThrow('aborted');
    expect(doStream).toHaveBeenCalled();
  });

  test('prepends system instructions to prompt for doStream', async () => {
    let received: any;
    const model = new AiSdkModel(
      stubModel({
        async doStream(options) {
          received = options.prompt;
          return {
            stream: partsStream([]),
          } as any;
        },
      }),
    );

    const iter = model.getStreamedResponse({
      systemInstructions: 'inst',
      input: 'hi',
      tools: [],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any);

    for await (const _ of iter) {
      // exhaust iterator
    }

    expect(received[0]).toEqual({
      role: 'system',
      content: 'inst',
    });
  });

  test('handles NaN usage in stream finish event', async () => {
    const parts = [
      { type: 'text-delta', delta: 'a' },
      {
        type: 'finish',
        finishReason: 'stop',
        usage: { inputTokens: Number.NaN, outputTokens: Number.NaN },
      },
    ];
    const model = new AiSdkModel(
      stubModel({
        async doStream() {
          return {
            stream: partsStream(parts),
          } as any;
        },
      }),
    );

    let final: any;
    for await (const ev of model.getStreamedResponse({
      input: 'hi',
      tools: [],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any)) {
      if (ev.type === 'response_done') {
        final = ev.response.usage;
      }
    }

    expect(final).toEqual({ inputTokens: 0, outputTokens: 0, totalTokens: 0 });
  });

  test('prepends system instructions to prompt for doStream', async () => {
    let received: any;
    const model = new AiSdkModel(
      stubModel({
        async doStream(options) {
          received = options.prompt;
          return { stream: partsStream([]) } as any;
        },
      }),
    );

    for await (const _ of model.getStreamedResponse({
      systemInstructions: 'inst',
      input: 'hi',
      tools: [],
      handoffs: [],
      modelSettings: {},
      outputType: 'text',
      tracing: false,
    } as any)) {
      // drain
    }

    expect(received[0]).toEqual({ role: 'system', content: 'inst' });
  });
});

describe('toolChoiceToLanguageV2Format', () => {
  test('maps default choices and specific tool', () => {
    expect(toolChoiceToLanguageV2Format(undefined)).toBeUndefined();
    expect(toolChoiceToLanguageV2Format(null as any)).toBeUndefined();
    expect(toolChoiceToLanguageV2Format('auto')).toEqual({ type: 'auto' });
    expect(toolChoiceToLanguageV2Format('required')).toEqual({
      type: 'required',
    });
    expect(toolChoiceToLanguageV2Format('none')).toEqual({ type: 'none' });
    expect(toolChoiceToLanguageV2Format('runTool' as any)).toEqual({
      type: 'tool',
      toolName: 'runTool',
    });
  });
});

describe('Extended thinking / Reasoning support', () => {
  describe('Non-streaming (getResponse)', () => {
    test('captures reasoning parts and outputs them before tool calls', async () => {
      const model = new AiSdkModel(
        stubModel({
          async doGenerate() {
            return {
              content: [
                {
                  type: 'reasoning',
                  text: 'Let me think through this step by step...',
                  providerMetadata: {
                    anthropic: { signature: 'sig_abc123' },
                  },
                },
                {
                  type: 'tool-call',
                  toolCallId: 'call-1',
                  toolName: 'get_weather',
                  input: { location: 'Tokyo' },
                },
              ],
              usage: { inputTokens: 50, outputTokens: 100, totalTokens: 150 },
              providerMetadata: { anthropic: { thinkingTokens: 30 } },
              response: { id: 'resp-1' },
              finishReason: 'tool-calls',
              warnings: [],
            } as any;
          },
        }),
      );

      const res = await withTrace('t', () =>
        model.getResponse({
          input: 'What is the weather in Tokyo?',
          tools: [
            {
              type: 'function',
              name: 'get_weather',
              description: 'Get weather info',
              parameters: { type: 'object', properties: {} },
            },
          ],
          handoffs: [],
          modelSettings: {},
          outputType: 'text',
          tracing: false,
        } as any),
      );

      // Reasoning item should come FIRST, before tool calls
      expect(res.output).toHaveLength(2);
      expect(res.output[0]).toMatchObject({
        type: 'reasoning',
        content: [
          {
            type: 'input_text',
            text: 'Let me think through this step by step...',
          },
        ],
        rawContent: [
          {
            type: 'reasoning_text',
            text: 'Let me think through this step by step...',
          },
        ],
        providerData: { anthropic: { signature: 'sig_abc123' } },
      });
      expect(res.output[1]).toMatchObject({
        type: 'function_call',
        callId: 'call-1',
        name: 'get_weather',
      });
    });

    test('handles reasoning without signature (non-Anthropic providers)', async () => {
      const model = new AiSdkModel(
        stubModel({
          async doGenerate() {
            return {
              content: [
                {
                  type: 'reasoning',
                  text: 'Thinking about this problem...',
                  // No providerMetadata / signature
                },
                { type: 'text', text: 'The answer is 42.' },
              ],
              usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
              providerMetadata: {},
              response: { id: 'resp-2' },
              finishReason: 'stop',
              warnings: [],
            } as any;
          },
        }),
      );

      const res = await withTrace('t', () =>
        model.getResponse({
          input: 'What is the meaning of life?',
          tools: [],
          handoffs: [],
          modelSettings: {},
          outputType: 'text',
          tracing: false,
        } as any),
      );

      expect(res.output).toHaveLength(2);
      expect(res.output[0]).toMatchObject({
        type: 'reasoning',
        content: [
          { type: 'input_text', text: 'Thinking about this problem...' },
        ],
        providerData: undefined,
      });
      expect(res.output[1]).toMatchObject({
        type: 'message',
        content: [{ type: 'output_text', text: 'The answer is 42.' }],
      });
    });
  });

  describe('Streaming (getStreamedResponse)', () => {
    test('captures reasoning stream events and outputs them before tool calls', async () => {
      const parts = [
        {
          type: 'reasoning-start',
          id: 'reasoning-1',
          providerMetadata: { anthropic: { thinking: 'enabled' } },
        },
        {
          type: 'reasoning-delta',
          id: 'reasoning-1',
          delta: 'Let me think...',
        },
        { type: 'reasoning-delta', id: 'reasoning-1', delta: ' step by step.' },
        {
          type: 'reasoning-end',
          id: 'reasoning-1',
          providerMetadata: { anthropic: { signature: 'sig_stream_123' } },
        },
        {
          type: 'tool-call',
          toolCallId: 'c1',
          toolName: 'search',
          input: '{"query":"test"}',
        },
        { type: 'response-metadata', id: 'resp-stream-1' },
        {
          type: 'finish',
          finishReason: 'tool-calls',
          usage: { inputTokens: 20, outputTokens: 40 },
        },
      ];

      const model = new AiSdkModel(
        stubModel({
          async doStream() {
            return {
              stream: partsStream(parts),
            } as any;
          },
        }),
      );

      const events: any[] = [];
      for await (const ev of model.getStreamedResponse({
        input: 'Search for something',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any)) {
        events.push(ev);
      }

      const final = events.at(-1);
      expect(final.type).toBe('response_done');

      // Reasoning should come FIRST in output
      expect(final.response.output).toHaveLength(2);
      expect(final.response.output[0]).toMatchObject({
        type: 'reasoning',
        id: 'reasoning-1',
        content: [
          { type: 'input_text', text: 'Let me think... step by step.' },
        ],
        rawContent: [
          { type: 'reasoning_text', text: 'Let me think... step by step.' },
        ],
        providerData: { anthropic: { signature: 'sig_stream_123' } },
      });
      expect(final.response.output[1]).toMatchObject({
        type: 'function_call',
        callId: 'c1',
        name: 'search',
      });
    });

    test('handles multiple reasoning blocks in streaming', async () => {
      const parts = [
        { type: 'reasoning-start', id: 'r1' },
        { type: 'reasoning-delta', id: 'r1', delta: 'First thought.' },
        { type: 'reasoning-end', id: 'r1' },
        { type: 'reasoning-start', id: 'r2' },
        { type: 'reasoning-delta', id: 'r2', delta: 'Second thought.' },
        { type: 'reasoning-end', id: 'r2' },
        { type: 'text-delta', delta: 'Final answer.' },
        {
          type: 'finish',
          finishReason: 'stop',
          usage: { inputTokens: 10, outputTokens: 20 },
        },
      ];

      const model = new AiSdkModel(
        stubModel({
          async doStream() {
            return {
              stream: partsStream(parts),
            } as any;
          },
        }),
      );

      const events: any[] = [];
      for await (const ev of model.getStreamedResponse({
        input: 'Complex problem',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any)) {
        events.push(ev);
      }

      const final = events.at(-1);
      expect(final.type).toBe('response_done');
      expect(final.response.output).toHaveLength(3);
      expect(final.response.output[0]).toMatchObject({
        type: 'reasoning',
        content: [{ type: 'input_text', text: 'First thought.' }],
      });
      expect(final.response.output[1]).toMatchObject({
        type: 'reasoning',
        content: [{ type: 'input_text', text: 'Second thought.' }],
      });
      expect(final.response.output[2]).toMatchObject({
        type: 'message',
        content: [{ type: 'output_text', text: 'Final answer.' }],
      });
    });

    test('handles reasoning-delta without reasoning-start', async () => {
      const parts = [
        {
          type: 'reasoning-delta',
          id: 'orphan',
          delta: 'Direct thinking content',
        },
        { type: 'reasoning-end', id: 'orphan' },
        { type: 'text-delta', delta: 'Response.' },
        {
          type: 'finish',
          finishReason: 'stop',
          usage: { inputTokens: 5, outputTokens: 10 },
        },
      ];

      const model = new AiSdkModel(
        stubModel({
          async doStream() {
            return {
              stream: partsStream(parts),
            } as any;
          },
        }),
      );

      const events: any[] = [];
      for await (const ev of model.getStreamedResponse({
        input: 'test',
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any)) {
        events.push(ev);
      }

      const final = events.at(-1);
      expect(final.response.output[0]).toMatchObject({
        type: 'reasoning',
        content: [{ type: 'input_text', text: 'Direct thinking content' }],
      });
    });
  });

  describe('Round-trip conversion (ReasoningItem to AI SDK and back)', () => {
    test('preserves signature in providerData through itemsToLanguageV2Messages', () => {
      const items: protocol.ModelItem[] = [
        {
          type: 'reasoning',
          content: [{ type: 'input_text', text: 'My reasoning process...' }],
          providerData: { anthropic: { signature: 'preserved_sig_456' } },
        } as any,
      ];

      const msgs = itemsToLanguageV2Messages(stubModel({}), items);
      expect(msgs).toHaveLength(1);
      expect(msgs[0]).toEqual({
        role: 'assistant',
        content: [
          {
            type: 'reasoning',
            text: 'My reasoning process...',
            providerOptions: { anthropic: { signature: 'preserved_sig_456' } },
          },
        ],
        providerOptions: { anthropic: { signature: 'preserved_sig_456' } },
      });
    });
  });
});

describe('AiSdkModel', () => {
  test('should be available', () => {
    const model = new AiSdkModel({} as any);
    expect(model).toBeDefined();
  });

  test('converts trailing function_call items to messages', async () => {
    let received: any;
    const fakeModel = {
      specificationVersion: 'v2',
      provider: 'fake',
      modelId: 'm',
      supportedUrls: [],
      doGenerate: vi.fn(async (opts: any) => {
        received = opts.prompt;
        return {
          content: [{ type: 'text', text: 'ok' }],
          usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
          providerMetadata: {},
          finishReason: 'stop',
          warnings: [],
        };
      }),
    };

    const model = new AiSdkModel(fakeModel as any);
    await withTrace('t', () =>
      model.getResponse({
        input: [
          {
            type: 'function_call',
            id: '1',
            callId: 'call1',
            name: 'do',
            arguments: '{}',
            status: 'completed',
            providerData: { meta: 1 },
          } as protocol.FunctionCallItem,
        ],
        tools: [],
        handoffs: [],
        modelSettings: {},
        outputType: 'text',
        tracing: false,
      } as any),
    );

    expect(received).toEqual([
      {
        role: 'assistant',
        content: [
          {
            type: 'tool-call',
            toolCallId: 'call1',
            toolName: 'do',
            input: {},
            providerOptions: { meta: 1 },
          },
        ],
        providerOptions: { meta: 1 },
      },
    ]);
  });

  describe('parseArguments', () => {
    test('should parse valid JSON', () => {
      expect(parseArguments(undefined)).toEqual({});
      expect(parseArguments(null)).toEqual({});
      expect(parseArguments('')).toEqual({});
      expect(parseArguments(' ')).toEqual({});
      expect(parseArguments('{ ')).toEqual({});
      expect(parseArguments('foo')).toEqual({});
      expect(parseArguments('{}')).toEqual({});
      expect(parseArguments('{ }')).toEqual({});

      expect(parseArguments('"foo"')).toEqual('foo');
      expect(parseArguments('[]')).toEqual([]);
      expect(parseArguments('[1,2,3]')).toEqual([1, 2, 3]);
      expect(parseArguments('{"a":1}')).toEqual({ a: 1 });
      expect(parseArguments('{"a":1,"b":"c"}')).toEqual({ a: 1, b: 'c' });
    });
  });

  describe('Error handling with tracing', () => {
    test('captures comprehensive AI SDK error details when tracing enabled', async () => {
      // Simulate an AI SDK error with responseBody and other fields.
      const aiSdkError = new Error('API call failed');
      aiSdkError.name = 'AI_APICallError';
      (aiSdkError as any).responseBody = {
        error: {
          message: 'Rate limit exceeded',
          code: 'rate_limit_exceeded',
          type: 'insufficient_quota',
        },
      };
      (aiSdkError as any).responseHeaders = {
        'x-request-id': 'req_abc123',
        'retry-after': '60',
      };
      (aiSdkError as any).statusCode = 429;

      const model = new AiSdkModel(
        stubModel({
          async doGenerate() {
            throw aiSdkError;
          },
        }),
      );

      try {
        await withTrace('test-trace', () =>
          model.getResponse({
            input: 'test input',
            tools: [],
            handoffs: [],
            modelSettings: {},
            outputType: 'text',
            tracing: true,
          } as any),
        );
        expect.fail('Should have thrown error');
      } catch (error: any) {
        // Error should be re-thrown.
        expect(error.message).toBe('API call failed');
        // Verify error has the AI SDK fields.
        expect((error as any).responseBody).toBeDefined();
        expect((error as any).statusCode).toBe(429);
      }
    });

    test('propagates error with AI SDK fields in streaming mode', async () => {
      const aiSdkError = new Error('Stream failed');
      aiSdkError.name = 'AI_StreamError';
      (aiSdkError as any).responseBody = {
        error: { message: 'Connection timeout', code: 'timeout' },
      };
      (aiSdkError as any).statusCode = 504;

      const model = new AiSdkModel(
        stubModel({
          async doStream() {
            throw aiSdkError;
          },
        }),
      );

      try {
        await withTrace('test-stream', async () => {
          const iter = model.getStreamedResponse({
            input: 'test',
            tools: [],
            handoffs: [],
            modelSettings: {},
            outputType: 'text',
            tracing: true,
          } as any);

          for await (const _ of iter) {
            // Should not get here.
          }
        });
        expect.fail('Should have thrown error');
      } catch (error: any) {
        expect(error.message).toBe('Stream failed');
        // Verify error has the AI SDK fields.
        expect((error as any).responseBody).toBeDefined();
        expect((error as any).statusCode).toBe(504);
      }
    });
  });
});


--- CONTRIBUTING.md ---
# Contributing to OpenAI Agents SDK

Thank you for your interest in contributing to the OpenAI Agents SDK. This document outlines the process for reporting issues, proposing changes, and submitting pull requests.

## Repository structure

This repository is a pnpm-managed monorepo that contains several packages:

- `packages/agents-core`: Core abstractions and runtime for building agent workflows.
- `packages/agents-openai`: OpenAI SDK bindings and concrete implementations.
- `packages/agents`: Convenience bundle that re-exports core and OpenAI packages.
- `packages/agents-realtime`: Realtime bindings and implementations.
- `packages/agents-extensions`: Extensions for additional workflows.

Other important directories:

- `docs/`: Documentation site (Astro).
- `examples/`: Example projects demonstrating basic usage.
- `scripts/`: Automation scripts (e.g. embedding metadata).
- `helpers/`: Shared utilities used across tests and examples.

## Getting started

### Prerequisites

- Node.js 22 or later
- pnpm 10 or later

### Setup

```bash
# Clone the repository
git clone https://github.com/openai/openai-agents-js.git
cd openai-agents-js

# Install dependencies
pnpm install

# Build all packages
pnpm build
# Check that all packages compile
pnpm -r build-check

# Run tests
pnpm test
```

Optionally, you can run the example app or docs site:

- `pnpm examples:basic` to start the basic example
- `pnpm docs:dev` to serve the documentation locally

## Development workflow

### Building

After making code changes, run:

```bash
pnpm build
```

This compiles TypeScript into `dist/` directories for each package.

### Testing

Run the full test suite with:

```bash
CI=1 pnpm test
```

Tests use Vitest and are located alongside source files in each package under `packages/*/test`.

### Code style

- Maintain existing TypeScript style.
- Ensure that `pnpm build` completes without errors.
- Run `pnpm lint` to check formatting and unused imports.

## Changesets and versioning

This repository uses [Changesets](https://github.com/changesets/changesets) for version management and changelog generation.
If your changes affect the public API or introduce user-visible changes (bug fixes, new features, or breaking changes), create a changeset:

```bash
pnpm changeset
```

Follow the interactive prompts. Do not manually bump package versions.

## Reporting issues

Before opening a new issue, search existing issues to avoid duplicates.
When opening an issue, include:

- A clear and descriptive title
- A short summary of the problem or feature request
- Steps to reproduce (for bugs)
- A minimal code snippet or example (if applicable)
- Expected and actual behavior

## Submitting a pull request

1. Fork the repository and create a branch with a descriptive name (e.g., `fix/missing-error`, `feat/new-tool`).
2. Ensure your branch is up to date with `main`.
3. Make your changes, add or update tests, and ensure that:
   ```bash
   pnpm build && pnpm test && pnpm lint
   ```
4. If applicable, generate a changeset (`pnpm changeset`).
5. Make sure you have [Trufflehog](https://github.com/trufflesecurity/trufflehog) installed to ensure no secrets are accidentally committed.
6. Commit your changes using Conventional Commits (e.g., `feat:`, `fix:`, `docs:`).
7. Push your branch to your fork and open a pull request against the `main` branch.
8. In the pull request description, link any related issues and summarize your changes.

### Review process

- All pull requests require at least one approving review from a maintainer.
- Automated checks (build, test, docs) must pass before merging.
- We use squash merging; each pull request results in a single commit on `main`.

## Releasing

Releasing happens automatically. After every push to `main` the CI will run. After it passed,
the Changeset Action will check if there are any open changeset entries and add them to either an
open version bump PR or create a new one.

For a maintainer to release a new version, the PR from Changeset has to be merged.

## License and code of conduct

By contributing, you agree that your contributions will be licensed under the project’s MIT license.

## Questions

If you have any questions or need guidance, feel free to open an issue or ask in a pull request. Maintainers are happy to help.


## Links discovered
- [Changesets](https://github.com/changesets/changesets)
- [Trufflehog](https://github.com/trufflesecurity/trufflehog)

--- SECURITY.md ---
# Security Policy

For a more in-depth look at our security policy, please check out our [Coordinated Vulnerability Disclosure Policy](https://openai.com/security/disclosure/#:~:text=Disclosure%20Policy,-Security%20is%20essential&text=OpenAI%27s%20coordinated%20vulnerability%20disclosure%20policy,expect%20from%20us%20in%20return.).

Our PGP key can located [at this address.](https://cdn.openai.com/security.txt)


## Links discovered
- [Coordinated Vulnerability Disclosure Policy](https://openai.com/security/disclosure/#:~:text=Disclosure%20Policy,-Security%20is%20essential&text=OpenAI%27s%20coordinated%20vulnerability%20disclosure%20policy,expect%20from%20us%20in%20return.)
- [at this address.](https://cdn.openai.com/security.txt)

--- packages/agents-core/CHANGELOG.md ---
# @openai/agents-core

## 0.3.4

### Patch Changes

- 2e09baf: fix: #699 Forward fetch parameter to SSEClientTransport in MCPServerSSE
- d1d7842: feat: Add ToolOptions to agents-core package export
- c252cb5: feat: #713 Access tool call items in an output guardrail
- 0345a4c: feat: #695 Customizable MCP list tool caching

## 0.3.3

### Patch Changes

- 18fec56: feat: #679 Add runInParallel option to input guardrail initialization
- b94432b: fix: #683 Failing to run MCP servers when deserializing run state data
- 0404173: fix: #316 developer-friendly message for output type errors
- ef0a6d8: feat: Add prompt_cache_retention option to ModelSettings
- 22865ae: feat: #678 Add a list of per-request usage data to Usage

## 0.3.2

### Patch Changes

- 184e5d0: feat: Add reasoning.effort: none parameter for gpt-5.1
- 0a808d2: fix: Omit tools parameter when prompt ID is set but tools in the agent is absent

## 0.3.1

### Patch Changes

- 2b57c4e: introduce new shell and apply_patch tools

## 0.3.0

### Minor Changes

- 1a5326f: feat: fix #272 add memory feature

## 0.2.1

### Patch Changes

- 76e5adb: fix: ugprade openai package from v5 to v6

## 0.2.0

### Minor Changes

- 0e01da0: feat: #313 Enable tools to return image/file data to an Agent
- 27915f7: feat: #561 support both zod3 and zod4

## 0.1.11

### Patch Changes

- 3417f25: fix: #597 hostedMcpTool fails to send authorization parameter to Responses API

## 0.1.10

### Patch Changes

- 73ee587: fix: #563 enable explicit model override for prompt
- e0b46c4: fix: improve the compatibility for conversationId / previousResponseId + tool calls

  ref: https://github.com/openai/openai-agents-python/pull/1827

- 3023dc0: Fixes a bug where `onTraceEnd` was called immediately after `onTraceStart` when streaming is enabled

## 0.1.8

### Patch Changes

- f3d1ff8: Revert "feat(mcp): support structuredContent via useStructuredContent; return full CallToolResult"

## 0.1.7

### Patch Changes

- becabb9: fix: #247 logging for a sub-agent w/ stopAtToolNames
- 0fd8b6e: feat: #478 add isEnabled to handoffs & agents as tools
- be686e9: feat(mcp): add structuredContent support behind `useStructuredContent`; return full CallToolResult from `callTool`
  - `MCPServer#callTool` now returns the full `CallToolResult` (was `content[]`), exposing optional `structuredContent`.
  - Add `useStructuredContent` option to MCP servers (stdio/streamable-http/SSE), default `false` to avoid duplicate data by default.
  - When enabled, function tool outputs return JSON strings for consistency with Python SDK implementation.

- 74a6ca3: fix: #526 separate tool_call_item and tool_call_output_item in stream events

## 0.1.6

### Patch Changes

- 3115177: Add typed reasoning / text options to ModelSettings
- 8516799: fix(randomUUID): add fallback when crypto.randomUUID is unavailable

## 0.1.4

### Patch Changes

- 5f4e139: fix: #485 Abort during streaming throws “ReadableStream is locked” in StreamedRunResult
- 9147a6a: feat: #460 Enable to customize the internal runner for an agent as tool

## 0.1.3

### Patch Changes

- 74dd52e: fix: #473 upgrade openai package to the latest and fix breaking errors

## 0.1.2

### Patch Changes

- 01fad84: Fix #243 by enabling unified HITL interruptions from both agents and their agents as tools
- 3d652e8: fix: delay final output until tools complete

## 0.1.1

### Patch Changes

- b4d315b: feat: Fix #412 add optional details data to function tool execution
- a1c43dd: feat: enable mcp exports for cloudflare workers
- 2c43bcc: fix: #417 ensure BrowserEventEmitter off removes listeners

## 0.1.0

### Minor Changes

- f1e2f60: moving realtime to the new GA API and add MCP support

### Patch Changes

- 2260e21: Upgrade openai package to the latest version
- 94f606c: Fix #371 streaming agents not calling agent_end lifecycle hook
- 79a1999: Make docs and comments more consistent using Codex
- 42702c0: #366 Add conversations API support
- ecea142: Fix #374 add connector support
- 2b10adc: Fix #393 add domain filtering and sources to web search tool & upgrade openai package to the latest version
- 8fc01fc: Add a quick opt-in option to switch to gpt-5
- 6f1677c: fix(tracing): Fix #361 include groupId in trace export log message

## 0.0.17

### Patch Changes

- 1cd3266: feat: expose the `history` getter on `RunState` to access input and generated items.
- f825f71: Fix #187 Agent outputType type error with zod@3.25.68+
- 5d247a5: Fix #245 CJS resolution failure

## 0.0.16

### Patch Changes

- 1bb4d86: Fix #233 - eliminate confusion with "input_text" type items with role: "assistant"
- 4818d5e: fix: support snake_case usage fields from OpenAI responses
- 0858c98: fix: prevent crash when importing in cloudflare workers

  An export was missed in https://github.com/openai/openai-agents-js/pull/290 for the workerd shim, this prevents the crash when importing there. Long term we should just add an implementation for cloudflare workers (and I suspect the node implementation might just work)

- 4bfd911: Add custom fetch support to StreamableHTTP MCP transport
- c42a0a9: refactor: restructure mcp tools fetching with options object pattern

## 0.0.15

### Patch Changes

- 5f7d0d6: Add run context to handoff input filter to align with Python SDK
- 7b437d9: feat: add reasoning handling in chat completions
- b65315f: feat: add timeout parameter to callTool method
- 0fe38c0: feat: add sse server implementation for mcp

## 0.0.14

### Patch Changes

- 08dd469: agents-core, agents-realtime: add MCP tool-filtering support (fixes #162)
- d9c4ddf: include JsonSchema definitions in mcpTool inputSchema
- fba44d9: Fix #246 by exposing RunHandoffOutputItem type

## 0.0.13

### Patch Changes

- bd463ef: Fix #219 MCPServer#invalidateToolsCache() not exposed while being mentioned in the documents

## 0.0.12

### Patch Changes

- af73bfb: Rebinds cached tools to the current MCP server to avoid stale tool invocation (fixes #195)
- 046f8cc: Fix typos across repo
- ed66acf: Fixes handling of `agent_updated_stream_event` in run implementation and adds corresponding test coverage.
- 40dc0be: Fix #216 Publicly accessible PDF file URL is not yet supported in the input_file content data

## 0.0.11

### Patch Changes

- a60eabe: Fix #131 Human in the Loop MCP approval fails
- a153963: Tentative fix for #187 : Lock zod version to <=3.25.67
- 17077d8: Fix #175 by removing internal system.exit calls

## 0.0.10

### Patch Changes

- c248a7d: Fix #138 by checking the unexpected absence of state.currentAgent.handoffs
- ff63127: Fix #129 The model in run config should be used over an agent's default setting
- 9c60282: Fix a bug where some of the exceptions thrown from runImplementation.ts could be unhandled
- f61fd18: Don't enable `cacheToolsList` per default for MCP servers
- c248a7d: Fix #138 by checking the unexpected absence of currentAgent.handoffs

## 0.0.9

### Patch Changes

- 9028df4: Adjust Usage object to accept empty data
- ce62f7c: Fix #117 by adding groupId, metadata to trace data

## 0.0.8

### Patch Changes

- 6e1d67d: Add OpenAI Response object on ResponseSpanData for other exporters.
- 52eb3f9: fix(interruptions): avoid double outputting function calls for approval requests
- 9e6db14: Adding support for prompt configuration to agents
- 0565bf1: Add details to output guardrail execution
- 52eb3f9: fix(interruptions): avoid accidental infinite loop if all interruptions were not cleared. expose interruptions helper on state

## 0.0.7

### Patch Changes

- 0580b9b: Add remote MCP server (Streamable HTTP) support
- 77c603a: Add allowed_tools and headers to hosted mcp server factory method
- 1fccdca: Publishes types that were marked as internal but caused build errors when not exported in typings.
- 2fae25c: Add hosted MCP server support

## 0.0.6

### Patch Changes

- 2c6cfb1: Pass through signal to model call
- 36a401e: Add force flush to global provider. Consistently default disable logging loop in Cloudflare Workers and Browser

## 0.0.5

### Patch Changes

- 544ed4b: Continue agent execution when function calls are pending

## 0.0.4

### Patch Changes

- 25165df: fix: Process hangs on SIGINT because `process.exit` is never called
- 6683db0: fix(shims): Naively polyfill AsyncLocalStorage in browser
- 78811c6: fix(shims): Bind crypto to randomUUID
- 426ad73: ensure getTransferMessage returns valid JSON

## 0.0.3

### Patch Changes

- d7fd8dc: Export CURRENT_SCHEMA_VERSION constant and use it when serializing run state.
- 284d0ab: Update internal module in agents-core to accept a custom logger

## 0.0.2

### Patch Changes

- a2979b6: fix: ensure process.on exists and is a function before adding event handlers

## 0.0.1

### Patch Changes

- aaa6d08: Initial release

## 0.0.1-next.0

### Patch Changes

- Initial release


--- packages/agents-extensions/CHANGELOG.md ---
# @openai/agents-extensions

## 0.3.4

### Patch Changes

- 870cc20: fix: preserve Gemini thought_signature in multi-turn tool calls
- 4ea9550: fix: #708 data: string in an input_image message item does not work with some providers
- Updated dependencies [2e09baf]
- Updated dependencies [d1d7842]
- Updated dependencies [c252cb5]
- Updated dependencies [0345a4c]
  - @openai/agents-core@0.3.4
  - @openai/agents@0.3.4

## 0.3.3

### Patch Changes

- 22865ae: feat: #678 Add a list of per-request usage data to Usage
- Updated dependencies [18fec56]
- Updated dependencies [b94432b]
- Updated dependencies [0404173]
- Updated dependencies [ef0a6d8]
- Updated dependencies [22865ae]
  - @openai/agents-core@0.3.3
  - @openai/agents@0.3.3

## 0.3.2

### Patch Changes

- Updated dependencies [184e5d0]
- Updated dependencies [0a808d2]
  - @openai/agents-core@0.3.2
  - @openai/agents@0.3.2

## 0.3.1

### Patch Changes

- 2b57c4e: introduce new shell and apply_patch tools
- Updated dependencies [2b57c4e]
  - @openai/agents-core@0.3.1
  - @openai/agents@0.3.1

## 0.3.0

### Patch Changes

- b3148a2: Fix open ai compatible models misuse '' in tools arguments call when an empty object is the valid option
- Updated dependencies [1a5326f]
  - @openai/agents-core@0.3.0
  - @openai/agents@0.3.0

## 0.2.1

### Patch Changes

- Updated dependencies [76e5adb]
  - @openai/agents-core@0.2.1
  - @openai/agents@0.2.1

## 0.2.0

### Minor Changes

- 0e01da0: feat: #313 Enable tools to return image/file data to an Agent
- 27915f7: feat: #561 support both zod3 and zod4

### Patch Changes

- Updated dependencies [0e01da0]
- Updated dependencies [27915f7]
  - @openai/agents-core@0.2.0
  - @openai/agents@0.2.0

## 0.1.5

### Patch Changes

- 2dfb4fd: feat: add factory-based Cloudflare support.
  - Realtime (WebSocket): add `createWebSocket` and `skipOpenEventListeners` options to enable
    custom socket creation and connection state control for specialized runtimes.
  - Extensions: add `CloudflareRealtimeTransportLayer`, which performs a `fetch()`-based WebSocket
    upgrade on Cloudflare/workerd and integrates via the WebSocket factory.
  - @openai/agents@0.1.5

## 0.1.2

### Patch Changes

- ffcd204: fix: #239 enable to pass toolChoice through ai-sdk
  - @openai/agents@0.1.2

## 0.1.0

### Minor Changes

- 2e6933a: Fix #283 #291 #300 migrate ai-sdk/provider to v2
- f1e2f60: moving realtime to the new GA API and add MCP support

### Patch Changes

- 03ebbaa: Loosen the `@openai/agents` dep's version range
- Updated dependencies [80e1fc1]
- Updated dependencies [2260e21]
- Updated dependencies [79a1999]
  - @openai/agents@0.1.0

## 0.0.17

### Patch Changes

- f825f71: Fix #187 Agent outputType type error with zod@3.25.68+
- 5d247a5: Fix #245 CJS resolution failure
- Updated dependencies [f825f71]
- Updated dependencies [5d247a5]
  - @openai/agents@0.0.17

## 0.0.16

### Patch Changes

- 1bb4d86: Fix #233 - eliminate confusion with "input_text" type items with role: "assistant"
- 191b82a: fix: the aisdk extension should grab output when toolCalls is a blank array

  When the output of a provider includes an empty tool calls array, we'd mistakenly skip over the text result. This patch checks for that condition.

- b487db1: Fix: clamp and floor `audio_end_ms` in interrupts to prevent Realtime API error with fractional speeds (#315)
  - @openai/agents@0.0.16

## 0.0.15

### Patch Changes

- @openai/agents@0.0.15

## 0.0.14

### Patch Changes

- 63e534b: Fix #259 Failing to send trace data with usage for ai-sdk models
  - @openai/agents@0.0.14

## 0.0.13

### Patch Changes

- @openai/agents@0.0.13

## 0.0.12

### Patch Changes

- f6e68f4: fix(realtime-ws): stop accidental cancellation error
  - @openai/agents@0.0.12

## 0.0.11

### Patch Changes

- a153963: Tentative fix for #187 : Lock zod version to <=3.25.67
- 0664056: Add tracing usage telemetry to aiSdk
  - @openai/agents@0.0.11

## 0.0.10

### Patch Changes

- 955e6f1: Fix #152 empty arguments parsing error in ai-sdk extension
- 787968b: fix: use web standard event apis for twilio websocket
- Updated dependencies [787968b]
  - @openai/agents@0.0.10

## 0.0.9

### Patch Changes

- fb9ca4f: fix(aisdk): make providerData less opinionated and pass to content
  - @openai/agents@0.0.9

## 0.0.8

### Patch Changes

- ef64938: fix(aisdk): handle non number token values
- 0565bf1: Add details to output guardrail execution
  - @openai/agents@0.0.8

## 0.0.7

### Patch Changes

- @openai/agents@0.0.7

## 0.0.6

### Patch Changes

- @openai/agents@0.0.6

## 0.0.5

### Patch Changes

- @openai/agents@0.0.5

## 0.0.4

### Patch Changes

- 0f4850e: Fix #34 by adjusting the internals of ai-sdk integration
  - @openai/agents@0.0.4

## 0.0.3

### Patch Changes

- @openai/agents@0.0.3

## 0.0.2

### Patch Changes

- @openai/agents@0.0.2

## 0.0.1

### Patch Changes

- aaa6d08: Initial release
- Updated dependencies [aaa6d08]
  - @openai/agents@0.0.1

## 0.0.1-next.0

### Patch Changes

- Initial release
- Updated dependencies
  - @openai/agents@0.0.1-next.0


--- packages/agents-openai/CHANGELOG.md ---
# @openai/agents-openai

## 0.3.4

### Patch Changes

- d552b50: Fix streaming tool call arguments when providers like Bedrock return an initial empty `{}` followed by actual arguments, resulting in malformed `{}{...}` JSON.
- Updated dependencies [2e09baf]
- Updated dependencies [d1d7842]
- Updated dependencies [c252cb5]
- Updated dependencies [0345a4c]
  - @openai/agents-core@0.3.4

## 0.3.3

### Patch Changes

- ef0a6d8: feat: Add prompt_cache_retention option to ModelSettings
- 22865ae: feat: #678 Add a list of per-request usage data to Usage
- Updated dependencies [18fec56]
- Updated dependencies [b94432b]
- Updated dependencies [0404173]
- Updated dependencies [ef0a6d8]
- Updated dependencies [22865ae]
  - @openai/agents-core@0.3.3

## 0.3.2

### Patch Changes

- 184e5d0: feat: Add reasoning.effort: none parameter for gpt-5.1
- 0a808d2: fix: Omit tools parameter when prompt ID is set but tools in the agent is absent
- 4734e27: Export usage data from Chat Completions response for trace
- Updated dependencies [184e5d0]
- Updated dependencies [0a808d2]
  - @openai/agents-core@0.3.2

## 0.3.1

### Patch Changes

- 2b57c4e: introduce new shell and apply_patch tools
- Updated dependencies [2b57c4e]
  - @openai/agents-core@0.3.1

## 0.3.0

### Minor Changes

- 1a5326f: feat: fix #272 add memory feature

### Patch Changes

- Updated dependencies [1a5326f]
  - @openai/agents-core@0.3.0

## 0.2.1

### Patch Changes

- 76e5adb: fix: ugprade openai package from v5 to v6
- Updated dependencies [76e5adb]
  - @openai/agents-core@0.2.1

## 0.2.0

### Minor Changes

- 0e01da0: feat: #313 Enable tools to return image/file data to an Agent
- 27915f7: feat: #561 support both zod3 and zod4

### Patch Changes

- Updated dependencies [0e01da0]
- Updated dependencies [27915f7]
  - @openai/agents-core@0.2.0

## 0.1.11

### Patch Changes

- Updated dependencies [3417f25]
  - @openai/agents-core@0.1.11

## 0.1.10

### Patch Changes

- 73ee587: fix: #563 enable explicit model override for prompt
- b07a588: fix: #562 invalid model settings when prompt is set in Agent
- Updated dependencies [73ee587]
- Updated dependencies [e0b46c4]
- Updated dependencies [3023dc0]
  - @openai/agents-core@0.1.10

## 0.1.9

### Patch Changes

- 4f27ed5: fix: #558 prompt parameter does not work when being passed via an Agent

## 0.1.8

### Patch Changes

- Updated dependencies [f3d1ff8]
  - @openai/agents-core@0.1.8

## 0.1.7

### Patch Changes

- Updated dependencies [becabb9]
- Updated dependencies [0fd8b6e]
- Updated dependencies [be686e9]
- Updated dependencies [74a6ca3]
  - @openai/agents-core@0.1.7

## 0.1.6

### Patch Changes

- 3115177: Add typed reasoning / text options to ModelSettings
- Updated dependencies [3115177]
- Updated dependencies [8516799]
  - @openai/agents-core@0.1.6

## 0.1.4

### Patch Changes

- Updated dependencies [5f4e139]
- Updated dependencies [9147a6a]
  - @openai/agents-core@0.1.4

## 0.1.3

### Patch Changes

- 74dd52e: fix: #473 upgrade openai package to the latest and fix breaking errors
- Updated dependencies [74dd52e]
  - @openai/agents-core@0.1.3

## 0.1.2

### Patch Changes

- 7fa0434: Refactor audio extraction logic in converter
- Updated dependencies [01fad84]
- Updated dependencies [3d652e8]
  - @openai/agents-core@0.1.2

## 0.1.1

### Patch Changes

- Updated dependencies [b4d315b]
- Updated dependencies [a1c43dd]
- Updated dependencies [2c43bcc]
  - @openai/agents-core@0.1.1

## 0.1.0

### Patch Changes

- 2260e21: Upgrade openai package to the latest version
- 47a28ad: Fix a bug where responses api does not accept both outputType and verbosity parameter for gpt-5
- 42702c0: #366 Add conversations API support
- ecea142: Fix #374 add connector support
- 2b10adc: Fix #393 add domain filtering and sources to web search tool & upgrade openai package to the latest version
- 8fc01fc: Add a quick opt-in option to switch to gpt-5
- Updated dependencies [2260e21]
- Updated dependencies [94f606c]
- Updated dependencies [79a1999]
- Updated dependencies [42702c0]
- Updated dependencies [ecea142]
- Updated dependencies [2b10adc]
- Updated dependencies [f1e2f60]
- Updated dependencies [8fc01fc]
- Updated dependencies [6f1677c]
  - @openai/agents-core@0.1.0

## 0.0.17

### Patch Changes

- f825f71: Fix #187 Agent outputType type error with zod@3.25.68+
- 5d247a5: Fix #245 CJS resolution failure
- Updated dependencies [1cd3266]
- Updated dependencies [f825f71]
- Updated dependencies [5d247a5]
  - @openai/agents-core@0.0.17

## 0.0.16

### Patch Changes

- 1bb4d86: Fix #233 - eliminate confusion with "input_text" type items with role: "assistant"
- a51105b: Pass through strict flag for function tools when using completion
- 4818d5e: fix: support snake_case usage fields from OpenAI responses
- Updated dependencies [1bb4d86]
- Updated dependencies [4818d5e]
- Updated dependencies [0858c98]
- Updated dependencies [4bfd911]
- Updated dependencies [c42a0a9]
  - @openai/agents-core@0.0.16

## 0.0.15

### Patch Changes

- 7b437d9: feat: add reasoning handling in chat completions
- Updated dependencies [5f7d0d6]
- Updated dependencies [7b437d9]
- Updated dependencies [b65315f]
- Updated dependencies [0fe38c0]
  - @openai/agents-core@0.0.15

## 0.0.14

### Patch Changes

- b6c7e9d: Fix codeInterpreterTool run replay by correctly using container_id from providerData (fixes #253)
- Updated dependencies [08dd469]
- Updated dependencies [d9c4ddf]
- Updated dependencies [fba44d9]
  - @openai/agents-core@0.0.14

## 0.0.13

### Patch Changes

- Updated dependencies [bd463ef]
  - @openai/agents-core@0.0.13

## 0.0.12

### Patch Changes

- fe5fb97: Handle function call messages with empty content in Chat Completions
- ad05c65: fix: if prompt is not specified return undefined - fixes #159
- 886e25a: Add input_fidelity parameter support to image generation tool
- 046f8cc: Fix typos across repo
- 40dc0be: Fix #216 Publicly accessible PDF file URL is not yet supported in the input_file content data
- Updated dependencies [af73bfb]
- Updated dependencies [046f8cc]
- Updated dependencies [ed66acf]
- Updated dependencies [40dc0be]
  - @openai/agents-core@0.0.12

## 0.0.11

### Patch Changes

- a153963: Tentative fix for #187 : Lock zod version to <=3.25.67
- Updated dependencies [a60eabe]
- Updated dependencies [a153963]
- Updated dependencies [17077d8]
  - @openai/agents-core@0.0.11

## 0.0.10

### Patch Changes

- 4adbcb5: Fix #140 by resolving built-in tool call item compatibility
- Updated dependencies [c248a7d]
- Updated dependencies [ff63127]
- Updated dependencies [9c60282]
- Updated dependencies [f61fd18]
- Updated dependencies [c248a7d]
  - @openai/agents-core@0.0.10

## 0.0.9

### Patch Changes

- Updated dependencies [9028df4]
- Updated dependencies [ce62f7c]
  - @openai/agents-core@0.0.9

## 0.0.8

### Patch Changes

- 6e1d67d: Add OpenAI Response object on ResponseSpanData for other exporters.
- 9e6db14: Adding support for prompt configuration to agents
- 0565bf1: Add details to output guardrail execution
- fc99390: Fix Azure streaming annotation handling
- Updated dependencies [6e1d67d]
- Updated dependencies [52eb3f9]
- Updated dependencies [9e6db14]
- Updated dependencies [0565bf1]
- Updated dependencies [52eb3f9]
  - @openai/agents-core@0.0.8

## 0.0.7

### Patch Changes

- 77c603a: Add allowed_tools and headers to hosted mcp server factory method
- 2fae25c: Add hosted MCP server support
- Updated dependencies [0580b9b]
- Updated dependencies [77c603a]
- Updated dependencies [1fccdca]
- Updated dependencies [2fae25c]
  - @openai/agents-core@0.0.7

## 0.0.6

### Patch Changes

- Updated dependencies [2c6cfb1]
- Updated dependencies [36a401e]
  - @openai/agents-core@0.0.6

## 0.0.5

### Patch Changes

- adeb218: Ignore empty tool list when calling LLM
- cbd4deb: feat: handle unknown hosted tools in responses model
- Updated dependencies [544ed4b]
  - @openai/agents-core@0.0.5

## 0.0.4

### Patch Changes

- ded675a: chore(openai): add more accurate debug logging
- Updated dependencies [25165df]
- Updated dependencies [6683db0]
- Updated dependencies [78811c6]
- Updated dependencies [426ad73]
  - @openai/agents-core@0.0.4

## 0.0.3

### Patch Changes

- 0474de9: Fix incorrect handling of chat completions mode for handoff
- Updated dependencies [d7fd8dc]
- Updated dependencies [284d0ab]
  - @openai/agents-core@0.0.3

## 0.0.2

### Patch Changes

- b4942fa: Fix #5 setDefaultOpenAIClient issue in agents-openai package
- Updated dependencies [a2979b6]
  - @openai/agents-core@0.0.2

## 0.0.1

### Patch Changes

- aaa6d08: Initial release
- Updated dependencies [aaa6d08]
  - @openai/agents-core@0.0.1

## 0.0.1-next.0

### Patch Changes

- Initial release
- Updated dependencies
  - @openai/agents-core@0.0.1-next.0


--- packages/agents-realtime/CHANGELOG.md ---
# @openai/agents-realtime

## 0.3.4

### Patch Changes

- Updated dependencies [2e09baf]
- Updated dependencies [d1d7842]
- Updated dependencies [c252cb5]
- Updated dependencies [0345a4c]
  - @openai/agents-core@0.3.4

## 0.3.3

### Patch Changes

- 46df17d: fix: #523 transcript removal issue when being interrupted
- d84976a: fix: #675 top-level voice param in realtime session confid does not work
- Updated dependencies [18fec56]
- Updated dependencies [b94432b]
- Updated dependencies [0404173]
- Updated dependencies [ef0a6d8]
- Updated dependencies [22865ae]
  - @openai/agents-core@0.3.3

## 0.3.2

### Patch Changes

- Updated dependencies [184e5d0]
- Updated dependencies [0a808d2]
  - @openai/agents-core@0.3.2

## 0.3.1

### Patch Changes

- 2b57c4e: introduce new shell and apply_patch tools
- Updated dependencies [2b57c4e]
  - @openai/agents-core@0.3.1

## 0.3.0

### Patch Changes

- 642a79b: fix: #639 Type issue with realtime agent handoffs
- 4c1192d: fix: #633 fix a bug where tracingDisabled in realtime config does not work
- 14016fd: feat: #439 add SIP support for realtime agent runner
- 8c93873: fix: #613 Listen to peerConnection state in `OpenAIRealtimeWebRTC` to detect disconnects
- Updated dependencies [1a5326f]
  - @openai/agents-core@0.3.0

## 0.2.1

### Patch Changes

- Updated dependencies [76e5adb]
  - @openai/agents-core@0.2.1

## 0.2.0

### Minor Changes

- 27915f7: feat: #561 support both zod3 and zod4

### Patch Changes

- Updated dependencies [0e01da0]
- Updated dependencies [27915f7]
  - @openai/agents-core@0.2.0

## 0.1.11

### Patch Changes

- Updated dependencies [3417f25]
  - @openai/agents-core@0.1.11

## 0.1.10

### Patch Changes

- cad0baa: Resolved typo with usage events
- Updated dependencies [73ee587]
- Updated dependencies [e0b46c4]
- Updated dependencies [3023dc0]
  - @openai/agents-core@0.1.10

## 0.1.8

### Patch Changes

- bb18a43: fix: #552 WebSocket Realtime Agent: invalid_request_error with decimal audio_end_ms data
- Updated dependencies [f3d1ff8]
  - @openai/agents-core@0.1.8

## 0.1.7

### Patch Changes

- 0fd8b6e: feat: #478 add isEnabled to handoffs & agents as tools
- 926bc13: fix: #494 Voice input transcription failing in realtime-demo
- Updated dependencies [becabb9]
- Updated dependencies [0fd8b6e]
- Updated dependencies [be686e9]
- Updated dependencies [74a6ca3]
  - @openai/agents-core@0.1.7

## 0.1.6

### Patch Changes

- Updated dependencies [3115177]
- Updated dependencies [8516799]
  - @openai/agents-core@0.1.6

## 0.1.5

### Patch Changes

- 2dfb4fd: feat: add factory-based Cloudflare support.
  - Realtime (WebSocket): add `createWebSocket` and `skipOpenEventListeners` options to enable
    custom socket creation and connection state control for specialized runtimes.
  - Extensions: add `CloudflareRealtimeTransportLayer`, which performs a `fetch()`-based WebSocket
    upgrade on Cloudflare/workerd and integrates via the WebSocket factory.

## 0.1.4

### Patch Changes

- 18fd902: fix: #495 Realtime session config falls back to legacy format when voice is set
- 1d4984b: Realtime: expose Call ID in OpenAIRealtimeWebRTC
- Updated dependencies [5f4e139]
- Updated dependencies [9147a6a]
  - @openai/agents-core@0.1.4

## 0.1.3

### Patch Changes

- Updated dependencies [74dd52e]
  - @openai/agents-core@0.1.3

## 0.1.2

### Patch Changes

- Updated dependencies [01fad84]
- Updated dependencies [3d652e8]
  - @openai/agents-core@0.1.2

## 0.1.1

### Patch Changes

- b4d315b: feat: Fix #412 add optional details data to function tool execution
- 1cb6188: fix: allow setting an initial tracing configuration for Realtime
- Updated dependencies [b4d315b]
- Updated dependencies [a1c43dd]
- Updated dependencies [2c43bcc]
  - @openai/agents-core@0.1.1

## 0.1.0

### Minor Changes

- f1e2f60: moving realtime to the new GA API and add MCP support

### Patch Changes

- 79a1999: Make docs and comments more consistent using Codex
- 8cf5356: Fix: ensure assistant message items from `response.output_item.done` preserve API status and default to `"completed"` when missing, so `history_updated` no longer stays `"in_progress"` after completion.
- f1e2f60: Add backgroundResult as an option to return tool results without triggering a new response
- Updated dependencies [2260e21]
- Updated dependencies [94f606c]
- Updated dependencies [79a1999]
- Updated dependencies [42702c0]
- Updated dependencies [ecea142]
- Updated dependencies [2b10adc]
- Updated dependencies [f1e2f60]
- Updated dependencies [8fc01fc]
- Updated dependencies [6f1677c]
  - @openai/agents-core@0.1.0

## 0.0.17

### Patch Changes

- f825f71: Fix #187 Agent outputType type error with zod@3.25.68+
- 5d247a5: Fix #245 CJS resolution failure
- Updated dependencies [1cd3266]
- Updated dependencies [f825f71]
- Updated dependencies [5d247a5]
  - @openai/agents-core@0.0.17

## 0.0.16

### Patch Changes

- b487db1: Fix: clamp and floor `audio_end_ms` in interrupts to prevent Realtime API error with fractional speeds (#315)
- a0b1f3b: fix(realtime-session): preserve audio format & other session config fields on agent update
- Updated dependencies [1bb4d86]
- Updated dependencies [4818d5e]
- Updated dependencies [0858c98]
- Updated dependencies [4bfd911]
- Updated dependencies [c42a0a9]
  - @openai/agents-core@0.0.16

## 0.0.15

### Patch Changes

- Updated dependencies [5f7d0d6]
- Updated dependencies [7b437d9]
- Updated dependencies [b65315f]
- Updated dependencies [0fe38c0]
  - @openai/agents-core@0.0.15

## 0.0.14

### Patch Changes

- 08dd469: agents-core, agents-realtime: add MCP tool-filtering support (fixes #162)
- Updated dependencies [08dd469]
- Updated dependencies [d9c4ddf]
- Updated dependencies [fba44d9]
  - @openai/agents-core@0.0.14

## 0.0.13

### Patch Changes

- 9fdecdb: Expose configurable URL in OpenAIRealtimeWebSocket constructor and RealtimeSession.connect.
- 25241e4: Fix missing `audio_start` event; now emitted on first audio chunk per turn
- Updated dependencies [bd463ef]
  - @openai/agents-core@0.0.13

## 0.0.12

### Patch Changes

- a2f78fe: support noise reduction argument
- d9b94b3: Adds support for the speed parameter
- f6e68f4: fix(realtime-ws): stop accidental cancellation error
- 046f8cc: Fix typos across repo
- Updated dependencies [af73bfb]
- Updated dependencies [046f8cc]
- Updated dependencies [ed66acf]
- Updated dependencies [40dc0be]
  - @openai/agents-core@0.0.12

## 0.0.11

### Patch Changes

- 07939c0: Correct typo in RealtimeTransportEventTypes in code and docs
- a153963: Tentative fix for #187 : Lock zod version to <=3.25.67
- 6e0d1bd: Fixes issue #106 where overlapping user inputs caused null transcripts in history
- Updated dependencies [a60eabe]
- Updated dependencies [a153963]
- Updated dependencies [17077d8]
  - @openai/agents-core@0.0.11

## 0.0.10

### Patch Changes

- 787968b: fix: use web standard event apis for twilio websocket
- Updated dependencies [c248a7d]
- Updated dependencies [ff63127]
- Updated dependencies [9c60282]
- Updated dependencies [f61fd18]
- Updated dependencies [c248a7d]
  - @openai/agents-core@0.0.10

## 0.0.9

### Patch Changes

- 49bfe25: Improve the types of turnDetection and inputAudioTranscription in RealtimeAgent configuration
- Updated dependencies [9028df4]
- Updated dependencies [ce62f7c]
  - @openai/agents-core@0.0.9

## 0.0.8

### Patch Changes

- 0565bf1: Add details to output guardrail execution
- Updated dependencies [6e1d67d]
- Updated dependencies [52eb3f9]
- Updated dependencies [9e6db14]
- Updated dependencies [0565bf1]
- Updated dependencies [52eb3f9]
  - @openai/agents-core@0.0.8

## 0.0.7

### Patch Changes

- Updated dependencies [0580b9b]
- Updated dependencies [77c603a]
- Updated dependencies [1fccdca]
- Updated dependencies [2fae25c]
  - @openai/agents-core@0.0.7

## 0.0.6

### Patch Changes

- Updated dependencies [2c6cfb1]
- Updated dependencies [36a401e]
  - @openai/agents-core@0.0.6

## 0.0.5

### Patch Changes

- 6e2445a: Add `changePeerConnection` option to `OpenAIRealtimeWebRTC` allowing interception
  and replacement of the created `RTCPeerConnection` before the offer is made.
- ca5cf8b: fix(realtime): add zod dependency to package.json
- Updated dependencies [544ed4b]
  - @openai/agents-core@0.0.5

## 0.0.4

### Patch Changes

- Updated dependencies [25165df]
- Updated dependencies [6683db0]
- Updated dependencies [78811c6]
- Updated dependencies [426ad73]
  - @openai/agents-core@0.0.4

## 0.0.3

### Patch Changes

- 68ff0ba: fix: avoid realtime guardrail race condition and detect ongoing response
- Updated dependencies [d7fd8dc]
- Updated dependencies [284d0ab]
  - @openai/agents-core@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [a2979b6]
  - @openai/agents-core@0.0.2

## 0.0.1

### Patch Changes

- aaa6d08: Initial release
- Updated dependencies [aaa6d08]
  - @openai/agents-core@0.0.1

## 0.0.1-next.0

### Patch Changes

- Initial release
- Updated dependencies
  - @openai/agents-core@0.0.1-next.0


--- packages/agents/CHANGELOG.md ---
# @openai/agents

## 0.3.4

### Patch Changes

- Updated dependencies [2e09baf]
- Updated dependencies [d552b50]
- Updated dependencies [d1d7842]
- Updated dependencies [c252cb5]
- Updated dependencies [0345a4c]
  - @openai/agents-core@0.3.4
  - @openai/agents-openai@0.3.4
  - @openai/agents-realtime@0.3.4

## 0.3.3

### Patch Changes

- Updated dependencies [18fec56]
- Updated dependencies [46df17d]
- Updated dependencies [b94432b]
- Updated dependencies [0404173]
- Updated dependencies [ef0a6d8]
- Updated dependencies [22865ae]
- Updated dependencies [d84976a]
  - @openai/agents-core@0.3.3
  - @openai/agents-realtime@0.3.3
  - @openai/agents-openai@0.3.3

## 0.3.2

### Patch Changes

- Updated dependencies [184e5d0]
- Updated dependencies [0a808d2]
- Updated dependencies [4734e27]
  - @openai/agents-openai@0.3.2
  - @openai/agents-core@0.3.2
  - @openai/agents-realtime@0.3.2

## 0.3.1

### Patch Changes

- 2b57c4e: introduce new shell and apply_patch tools
- Updated dependencies [2b57c4e]
  - @openai/agents-realtime@0.3.1
  - @openai/agents-openai@0.3.1
  - @openai/agents-core@0.3.1

## 0.3.0

### Patch Changes

- Updated dependencies [642a79b]
- Updated dependencies [1a5326f]
- Updated dependencies [4c1192d]
- Updated dependencies [14016fd]
- Updated dependencies [8c93873]
  - @openai/agents-realtime@0.3.0
  - @openai/agents-openai@0.3.0
  - @openai/agents-core@0.3.0

## 0.2.1

### Patch Changes

- 76e5adb: fix: ugprade openai package from v5 to v6
- Updated dependencies [76e5adb]
  - @openai/agents-openai@0.2.1
  - @openai/agents-core@0.2.1
  - @openai/agents-realtime@0.2.1

## 0.2.0

### Minor Changes

- 27915f7: feat: #561 support both zod3 and zod4

### Patch Changes

- Updated dependencies [0e01da0]
- Updated dependencies [27915f7]
  - @openai/agents-openai@0.2.0
  - @openai/agents-core@0.2.0
  - @openai/agents-realtime@0.2.0

## 0.1.11

### Patch Changes

- Updated dependencies [3417f25]
  - @openai/agents-core@0.1.11
  - @openai/agents-openai@0.1.11
  - @openai/agents-realtime@0.1.11

## 0.1.10

### Patch Changes

- Updated dependencies [73ee587]
- Updated dependencies [cad0baa]
- Updated dependencies [e0b46c4]
- Updated dependencies [3023dc0]
- Updated dependencies [b07a588]
  - @openai/agents-openai@0.1.10
  - @openai/agents-core@0.1.10
  - @openai/agents-realtime@0.1.10

## 0.1.9

### Patch Changes

- Updated dependencies [4f27ed5]
  - @openai/agents-openai@0.1.9

## 0.1.8

### Patch Changes

- Updated dependencies [bb18a43]
- Updated dependencies [f3d1ff8]
  - @openai/agents-realtime@0.1.8
  - @openai/agents-core@0.1.8
  - @openai/agents-openai@0.1.8

## 0.1.7

### Patch Changes

- Updated dependencies [becabb9]
- Updated dependencies [0fd8b6e]
- Updated dependencies [be686e9]
- Updated dependencies [74a6ca3]
- Updated dependencies [926bc13]
  - @openai/agents-core@0.1.7
  - @openai/agents-realtime@0.1.7
  - @openai/agents-openai@0.1.7

## 0.1.6

### Patch Changes

- Updated dependencies [3115177]
- Updated dependencies [8516799]
  - @openai/agents-openai@0.1.6
  - @openai/agents-core@0.1.6
  - @openai/agents-realtime@0.1.6

## 0.1.5

### Patch Changes

- Updated dependencies [2dfb4fd]
  - @openai/agents-realtime@0.1.5

## 0.1.4

### Patch Changes

- Updated dependencies [18fd902]
- Updated dependencies [1d4984b]
- Updated dependencies [5f4e139]
- Updated dependencies [9147a6a]
  - @openai/agents-realtime@0.1.4
  - @openai/agents-core@0.1.4
  - @openai/agents-openai@0.1.4

## 0.1.3

### Patch Changes

- Updated dependencies [74dd52e]
  - @openai/agents-openai@0.1.3
  - @openai/agents-core@0.1.3
  - @openai/agents-realtime@0.1.3

## 0.1.2

### Patch Changes

- Updated dependencies [01fad84]
- Updated dependencies [7fa0434]
- Updated dependencies [3d652e8]
  - @openai/agents-core@0.1.2
  - @openai/agents-openai@0.1.2
  - @openai/agents-realtime@0.1.2

## 0.1.1

### Patch Changes

- Updated dependencies [b4d315b]
- Updated dependencies [1cb6188]
- Updated dependencies [a1c43dd]
- Updated dependencies [2c43bcc]
  - @openai/agents-realtime@0.1.1
  - @openai/agents-core@0.1.1
  - @openai/agents-openai@0.1.1

## 0.1.0

### Minor Changes

- 80e1fc1: Bump to v0.1 with the following chnages:
  - gpt-5 model support
  - opt-in: gpt-5 as default option
  - ai-sdk model provider v2 migration

  and more

### Patch Changes

- 2260e21: Upgrade openai package to the latest version
- 79a1999: Make docs and comments more consistent using Codex
- Updated dependencies [2260e21]
- Updated dependencies [94f606c]
- Updated dependencies [79a1999]
- Updated dependencies [8cf5356]
- Updated dependencies [47a28ad]
- Updated dependencies [f1e2f60]
- Updated dependencies [42702c0]
- Updated dependencies [ecea142]
- Updated dependencies [2b10adc]
- Updated dependencies [f1e2f60]
- Updated dependencies [8fc01fc]
- Updated dependencies [6f1677c]
  - @openai/agents-openai@0.1.0
  - @openai/agents-core@0.1.0
  - @openai/agents-realtime@0.1.0

## 0.0.17

### Patch Changes

- f825f71: Fix #187 Agent outputType type error with zod@3.25.68+
- 5d247a5: Fix #245 CJS resolution failure
- Updated dependencies [1cd3266]
- Updated dependencies [f825f71]
- Updated dependencies [5d247a5]
  - @openai/agents-core@0.0.17
  - @openai/agents-realtime@0.0.17
  - @openai/agents-openai@0.0.17

## 0.0.16

### Patch Changes

- Updated dependencies [1bb4d86]
- Updated dependencies [a51105b]
- Updated dependencies [b487db1]
- Updated dependencies [4818d5e]
- Updated dependencies [0858c98]
- Updated dependencies [a0b1f3b]
- Updated dependencies [4bfd911]
- Updated dependencies [c42a0a9]
  - @openai/agents-core@0.0.16
  - @openai/agents-openai@0.0.16
  - @openai/agents-realtime@0.0.16

## 0.0.15

### Patch Changes

- Updated dependencies [5f7d0d6]
- Updated dependencies [7b437d9]
- Updated dependencies [b65315f]
- Updated dependencies [0fe38c0]
  - @openai/agents-core@0.0.15
  - @openai/agents-openai@0.0.15
  - @openai/agents-realtime@0.0.15

## 0.0.14

### Patch Changes

- Updated dependencies [08dd469]
- Updated dependencies [d9c4ddf]
- Updated dependencies [b6c7e9d]
- Updated dependencies [fba44d9]
  - @openai/agents-realtime@0.0.14
  - @openai/agents-core@0.0.14
  - @openai/agents-openai@0.0.14

## 0.0.13

### Patch Changes

- Updated dependencies [bd463ef]
- Updated dependencies [9fdecdb]
- Updated dependencies [25241e4]
  - @openai/agents-core@0.0.13
  - @openai/agents-realtime@0.0.13
  - @openai/agents-openai@0.0.13

## 0.0.12

### Patch Changes

- Updated dependencies [af73bfb]
- Updated dependencies [fe5fb97]
- Updated dependencies [ad05c65]
- Updated dependencies [a2f78fe]
- Updated dependencies [886e25a]
- Updated dependencies [d9b94b3]
- Updated dependencies [f6e68f4]
- Updated dependencies [046f8cc]
- Updated dependencies [ed66acf]
- Updated dependencies [40dc0be]
  - @openai/agents-core@0.0.12
  - @openai/agents-openai@0.0.12
  - @openai/agents-realtime@0.0.12

## 0.0.11

### Patch Changes

- Updated dependencies [a60eabe]
- Updated dependencies [07939c0]
- Updated dependencies [a153963]
- Updated dependencies [6e0d1bd]
- Updated dependencies [17077d8]
  - @openai/agents-core@0.0.11
  - @openai/agents-realtime@0.0.11
  - @openai/agents-openai@0.0.11

## 0.0.10

### Patch Changes

- 787968b: fix: use web standard event apis for twilio websocket
- Updated dependencies [c248a7d]
- Updated dependencies [ff63127]
- Updated dependencies [9c60282]
- Updated dependencies [787968b]
- Updated dependencies [f61fd18]
- Updated dependencies [c248a7d]
- Updated dependencies [4adbcb5]
  - @openai/agents-core@0.0.10
  - @openai/agents-realtime@0.0.10
  - @openai/agents-openai@0.0.10

## 0.0.9

### Patch Changes

- Updated dependencies [9028df4]
- Updated dependencies [49bfe25]
- Updated dependencies [ce62f7c]
  - @openai/agents-core@0.0.9
  - @openai/agents-realtime@0.0.9
  - @openai/agents-openai@0.0.9

## 0.0.8

### Patch Changes

- Updated dependencies [6e1d67d]
- Updated dependencies [52eb3f9]
- Updated dependencies [9e6db14]
- Updated dependencies [0565bf1]
- Updated dependencies [fc99390]
- Updated dependencies [52eb3f9]
  - @openai/agents-core@0.0.8
  - @openai/agents-openai@0.0.8
  - @openai/agents-realtime@0.0.8

## 0.0.7

### Patch Changes

- Updated dependencies [0580b9b]
- Updated dependencies [77c603a]
- Updated dependencies [1fccdca]
- Updated dependencies [2fae25c]
  - @openai/agents-core@0.0.7
  - @openai/agents-openai@0.0.7
  - @openai/agents-realtime@0.0.7

## 0.0.6

### Patch Changes

- Updated dependencies [2c6cfb1]
- Updated dependencies [36a401e]
  - @openai/agents-core@0.0.6
  - @openai/agents-openai@0.0.6
  - @openai/agents-realtime@0.0.6

## 0.0.5

### Patch Changes

- Updated dependencies [adeb218]
- Updated dependencies [6e2445a]
- Updated dependencies [ca5cf8b]
- Updated dependencies [cbd4deb]
- Updated dependencies [544ed4b]
  - @openai/agents-openai@0.0.5
  - @openai/agents-realtime@0.0.5
  - @openai/agents-core@0.0.5

## 0.0.4

### Patch Changes

- Updated dependencies [ded675a]
- Updated dependencies [25165df]
- Updated dependencies [6683db0]
- Updated dependencies [78811c6]
- Updated dependencies [426ad73]
  - @openai/agents-openai@0.0.4
  - @openai/agents-core@0.0.4
  - @openai/agents-realtime@0.0.4

## 0.0.3

### Patch Changes

- Updated dependencies [d7fd8dc]
- Updated dependencies [284d0ab]
- Updated dependencies [0474de9]
- Updated dependencies [68ff0ba]
  - @openai/agents-core@0.0.3
  - @openai/agents-openai@0.0.3
  - @openai/agents-realtime@0.0.3

## 0.0.2

### Patch Changes

- Updated dependencies [a2979b6]
- Updated dependencies [b4942fa]
  - @openai/agents-core@0.0.2
  - @openai/agents-openai@0.0.2
  - @openai/agents-realtime@0.0.2

## 0.0.1

### Patch Changes

- aaa6d08: Initial release
- Updated dependencies [aaa6d08]
  - @openai/agents-realtime@0.0.1
  - @openai/agents-openai@0.0.1
  - @openai/agents-core@0.0.1

## 0.0.1-next.0

### Patch Changes

- Initial release
- Updated dependencies
  - @openai/agents-realtime@0.0.1-next.0
  - @openai/agents-openai@0.0.1-next.0
  - @openai/agents-core@0.0.1-next.0


--- .agent/PLANS.md ---
# Codex Execution Plans (ExecPlans):

This document describes the requirements for an execution plan ("ExecPlan"), a design document that a coding agent can follow to deliver a working feature or system change. Treat the reader as a complete beginner to this repository: they have only the current working tree and the single ExecPlan file you provide. There is no memory of prior plans and no external context.

## How to use ExecPlans and PLANS.md

When authoring an executable specification (ExecPlan), follow PLANS.md _to the letter_. If it is not in your context, refresh your memory by reading the entire PLANS.md file. Be thorough in reading (and re-reading) source material to produce an accurate specification. When creating a spec, start from the skeleton and flesh it out as you do your research.

When implementing an executable specification (ExecPlan), do not prompt the user for "next steps"; simply proceed to the next milestone. Keep all sections up to date, add or split entries in the list at every stopping point to affirmatively state the progress made and next steps. Resolve ambiguities autonomously, and commit frequently.

When discussing an executable specification (ExecPlan), record decisions in a log in the spec for posterity; it should be unambiguously clear why any change to the specification was made. ExecPlans are living documents, and it should always be possible to restart from _only_ the ExecPlan and no other work.

When researching a design with challenging requirements or significant unknowns, use milestones to implement proof of concepts, "toy implementations", etc., that allow validating whether the user's proposal is feasible. Read the source code of libraries by finding or acquiring them, research deeply, and include prototypes to guide a fuller implementation.

## Requirements

NON-NEGOTIABLE REQUIREMENTS:

- Every ExecPlan must be fully self-contained. Self-contained means that in its current form it contains all knowledge and instructions needed for a novice to succeed.
- Every ExecPlan is a living document. Contributors are required to revise it as progress is made, as discoveries occur, and as design decisions are finalized. Each revision must remain fully self-contained.
- Every ExecPlan must enable a complete novice to implement the feature end-to-end without prior knowledge of this repo.
- Every ExecPlan must produce a demonstrably working behavior, not merely code changes to "meet a definition".
- Every ExecPlan must define every term of art in plain language or do not use it.

Purpose and intent come first. Begin by explaining, in a few sentences, why the work matters from a user's perspective: what someone can do after this change that they could not do before, and how to see it working. Then guide the reader through the exact steps to achieve that outcome, including what to edit, what to run, and what they should observe.

The agent executing your plan can list files, read files, search, run the project, and run tests. It does not know any prior context and cannot infer what you meant from earlier milestones. Repeat any assumption you rely on. Do not point to external blogs or docs; if knowledge is required, embed it in the plan itself in your own words. If an ExecPlan builds upon a prior ExecPlan and that file is checked in, incorporate it by reference. If it is not, you must include all relevant context from that plan.

## Formatting

Format and envelope are simple and strict. Each ExecPlan must be one single fenced code block labeled as `md` that begins and ends with triple backticks. Do not nest additional triple-backtick code fences inside; when you need to show commands, transcripts, diffs, or code, present them as indented blocks within that single fence. Use indentation for clarity rather than code fences inside an ExecPlan to avoid prematurely closing the ExecPlan's code fence. Use two newlines after every heading, use # and ## and so on, and correct syntax for ordered and unordered lists.

When writing an ExecPlan to a Markdown (.md) file where the content of the file _is only_ the single ExecPlan, you should omit the triple backticks.

Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and long enumerations unless brevity would obscure meaning. Checklists are permitted only in the `Progress` section, where they are mandatory. Narrative sections must remain prose-first.

## Guidelines

Self-containment and plain language are paramount. If you introduce a phrase that is not ordinary English ("daemon", "middleware", "RPC gateway", "filter graph"), define it immediately and remind the reader how it manifests in this repository (for example, by naming the files or commands where it appears). Do not say "as defined previously" or "according to the architecture doc." Include the needed explanation here, even if you repeat yourself.

Avoid common failure modes. Do not rely on undefined jargon. Do not describe "the letter of a feature" so narrowly that the resulting code compiles but does nothing meaningful. Do not outsource key decisions to the reader. When ambiguity exists, resolve it in the plan itself and explain why you chose that path. Err on the side of over-explaining user-visible effects and under-specifying incidental implementation details.

Anchor the plan with observable outcomes. State what the user can do after implementation, the commands to run, and the outputs they should see. Acceptance should be phrased as behavior a human can verify ("after starting the server, navigating to [http://localhost:8080/health](http://localhost:8080/health) returns HTTP 200 with body OK") rather than internal attributes ("added a HealthCheck struct"). If a change is internal, explain how its impact can still be demonstrated (for example, by running tests that fail before and pass after, and by showing a scenario that uses the new behavior).

Specify repository context explicitly. Name files with full repository-relative paths, name functions and modules precisely, and describe where new files should be created. If touching multiple areas, include a short orientation paragraph that explains how those parts fit together so a novice can navigate confidently. When running commands, show the working directory and exact command line. When outcomes depend on environment, state the assumptions and provide alternatives when reasonable.

Be idempotent and safe. Write the steps so they can be run multiple times without causing damage or drift. If a step can fail halfway, include how to retry or adapt. If a migration or destructive operation is necessary, spell out backups or safe fallbacks. Prefer additive, testable changes that can be validated as you go.

Validation is not optional. Include instructions to run tests, to start the system if applicable, and to observe it doing something useful. Describe comprehensive testing for any new features or capabilities. Include expected outputs and error messages so a novice can tell success from failure. Where possible, show how to prove that the change is effective beyond compilation (for example, through a small end-to-end scenario, a CLI invocation, or an HTTP request/response transcript). State the exact test commands appropriate to the project’s toolchain and how to interpret their results.

Capture evidence. When your steps produce terminal output, short diffs, or logs, include them inside the single fenced block as indented examples. Keep them concise and focused on what proves success. If you need to include a patch, prefer file-scoped diffs or small excerpts that a reader can recreate by following your instructions rather than pasting large blobs.

## Milestones

Milestones are narrative, not bureaucracy. If you break the work into milestones, introduce each with a brief paragraph that describes the scope, what will exist at the end of the milestone that did not exist before, the commands to run, and the acceptance you expect to observe. Keep it readable as a story: goal, work, result, proof. Progress and milestones are distinct: milestones tell the story, progress tracks granular work. Both must exist. Never abbreviate a milestone merely for the sake of brevity, do not leave out details that could be crucial to a future implementation.

Each milestone must be independently verifiable and incrementally implement the overall goal of the execution plan.

## Living plans and design decisions

- ExecPlans are living documents. As you make key design decisions, update the plan to record both the decision and the thinking behind it. Record all decisions in the `Decision Log` section.
- ExecPlans must contain and maintain a `Progress` section, a `Surprises & Discoveries` section, a `Decision Log`, and an `Outcomes & Retrospective` section. These are not optional.
- When you discover optimizer behavior, performance tradeoffs, unexpected bugs, or inverse/unapply semantics that shaped your approach, capture those observations in the `Surprises & Discoveries` section with short evidence snippets (test output is ideal).
- If you change course mid-implementation, document why in the `Decision Log` and reflect the implications in `Progress`. Plans are guides for the next contributor as much as checklists for you.
- At completion of a major task or the full plan, write an `Outcomes & Retrospective` entry summarizing what was achieved, what remains, and lessons learned.

# Prototyping milestones and parallel implementations

It is acceptable—-and often encouraged—-to include explicit prototyping milestones when they de-risk a larger change. Examples: adding a low-level operator to a dependency to validate feasibility, or exploring two composition orders while measuring optimizer effects. Keep prototypes additive and testable. Clearly label the scope as “prototyping”; describe how to run and observe results; and state the criteria for promoting or discarding the prototype.

Prefer additive code changes followed by subtractions that keep tests passing. Parallel implementations (e.g., keeping an adapter alongside an older path during migration) are fine when they reduce risk or enable tests to continue passing during a large migration. Describe how to validate both paths and how to retire one safely with tests. When working with multiple new libraries or feature areas, consider creating spikes that evaluate the feasibility of these features _independently_ of one another, proving that the external library performs as expected and implements the features we need in isolation.

## Skeleton of a Good ExecPlan

```md
# <Short, action-oriented description>

This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.

If PLANS.md file is checked into the repo, reference the path to that file here from the repository root and note that this document must be maintained in accordance with PLANS.md.

## Purpose / Big Picture

Explain in a few sentences what someone gains after this change and how they can see it working. State the user-visible behavior you will enable.

## Progress

Use a list with checkboxes to summarize granular steps. Every stopping point must be documented here, even if it requires splitting a partially completed task into two (“done” vs. “remaining”). This section must always reflect the actual current state of the work.

- [x] (2025-10-01 13:00Z) Example completed step.
- [ ] Example incomplete step.
- [ ] Example partially completed step (completed: X; remaining: Y).

Use timestamps to measure rates of progress.

## Surprises & Discoveries

Document unexpected behaviors, bugs, optimizations, or insights discovered during implementation. Provide concise evidence.

- Observation: …
  Evidence: …

## Decision Log

Record every decision made while working on the plan in the format:

- Decision: …
  Rationale: …
  Date/Author: …

## Outcomes & Retrospective

Summarize outcomes, gaps, and lessons learned at major milestones or at completion. Compare the result against the original purpose.

## Context and Orientation

Describe the current state relevant to this task as if the reader knows nothing. Name the key files and modules by full path. Define any non-obvious term you will use. Do not refer to prior plans.

## Plan of Work

Describe, in prose, the sequence of edits and additions. For each edit, name the file and location (function, module) and what to insert or change. Keep it concrete and minimal.

## Concrete Steps

State the exact commands to run and where to run them (working directory). When a command generates output, show a short expected transcript so the reader can compare. This section must be updated as work proceeds.

## Validation and Acceptance

Describe how to start or exercise the system and what to observe. Phrase acceptance as behavior, with specific inputs and outputs. If tests are involved, say "run <project’s test command> and expect <N> passed; the new test <name> fails before the change and passes after>".

## Idempotence and Recovery

If steps can be repeated safely, say so. If a step is risky, provide a safe retry or rollback path. Keep the environment clean after completion.

## Artifacts and Notes

Include the most important transcripts, diffs, or snippets as indented examples. Keep them concise and focused on what proves success.

## Interfaces and Dependencies

Be prescriptive. Name the libraries, modules, and services to use and why. Specify the types, traits/interfaces, and function signatures that must exist at the end of the milestone.

If you follow the guidance above, a single, stateless agent -- or a human novice -- can read your ExecPlan from top to bottom and produce a working, observable result. That is the bar: SELF-CONTAINED, SELF-SUFFICIENT, NOVICE-GUIDING, OUTCOME-FOCUSED.

When you revise a plan, you must ensure your changes are comprehensively reflected across all sections, including the living document sections, and you must write a note at the bottom of the plan describing the change and the reason why. ExecPlans must describe not just the what but the why for almost everything.
```


## Links discovered
- [http://localhost:8080/health](http://localhost:8080/health)

--- AGENTS.md ---
# Contributor Guide

This guide helps new contributors get started with the OpenAI Agents JS monorepo. It covers repo structure, how to test your work, available utilities, file locations, and guidelines for commits and PRs.

**Location:** `AGENTS.md` at the repository root.

## ExecPlans

When writing complex features or significant refactors, use an ExecPlan (as described in .agent/PLANS.md) from design to implementation.

## Table of Contents

1.  [Overview](#overview)
2.  [Repo Structure & Important Files](#repo-structure--important-files)
3.  [Testing & Automated Checks](#testing--automated-checks)
4.  [Repo-Specific Utilities](#repo-specific-utilities)
5.  [Style, Linting & Type Checking](#style-linting--type-checking)
6.  [Development Workflow](#development-workflow)
7.  [Pull Request & Commit Guidelines](#pull-request--commit-guidelines)
8.  [Review Process & What Reviewers Look For](#review-process--what-reviewers-look-for)
9.  [Tips for Navigating the Repo](#tips-for-navigating-the-repo)
10. [Prerequisites](#prerequisites)

## Overview

The OpenAI Agents JS repository is a pnpm-managed monorepo that provides:

- `packages/agents`: A convenience bundle exporting core and OpenAI packages.
- `packages/agents-core`: Core abstractions and runtime for agent workflows.
- `packages/agents-openai`: OpenAI-specific bindings and implementations.
- `packages/agents-realtime`: Realtime bindings and implementations.
- `packages/agents-extensions`: Extensions for agent workflows.
- `docs`: Documentation site powered by Astro.
- `examples`: Sample projects demonstrating usage patterns.
- `scripts`: Automation scripts (`dev.mts`, `embedMeta.ts`).
- `helpers`: Shared utilities for testing and other internal use.

## Repo Structure & Important Files

- `packages/agents-core/`, `packages/agents-openai/`, `packages/agents-realtime/`, `packages/agents-extensions/`: Each has its own `package.json`, `src/`, `test/`, and build scripts.
- `docs/`: Documentation source; develop with `pnpm docs:dev` or build with `pnpm docs:build`.
- `examples/`: Subdirectories (e.g. `basic`, `agent-patterns`) with their own `package.json` and start scripts.
- `scripts/dev.mts`: Runs concurrent build-watchers and the docs dev server (`pnpm dev`).
- `scripts/embedMeta.ts`: Generates `src/metadata.ts` for each package before build.
- `helpers/tests/`: Shared test utilities.
- `README.md`: High-level overview and installation instructions.
- `CONTRIBUTING.md`: Official contribution guidelines (this guide is complementary).
- `pnpm-workspace.yaml`: Defines workspace packages.
- `tsconfig.json`, `tsc-multi.json`: TypeScript configuration.
- `vitest.config.ts`: Test runner configuration.
- `eslint.config.mjs`: ESLint configuration.
- `package.json` (root): Common scripts (`build`, `test`, `lint`, `dev`, `docs:dev`, `examples:*`).

## Testing & Automated Checks

Before submitting changes, ensure all checks pass and augment tests when you touch code:

- Add or update unit tests for any code change unless it is truly infeasible; if something prevents adding tests, explain why in the PR.

### Unit Tests and Type Checking

- Check the compilation across all packages and examples:
  ```bash
  pnpm -r build-check
  ```
  NEVER USE `-w` or other watch modes.
- Run the full test suite:
  ```bash
  CI=1 pnpm test
  ```
- Tests are located under each package in `packages/<pkg>/test/`.
- The test script already sets `CI=1` to avoid watch mode.

### Integration Tests

- Not required for typical contributions. These tests rely on a local npm registry (Verdaccio) and other environment setup.
- To run locally only if needed:
  ```bash
  pnpm local-npm:start   # starts Verdaccio on :4873
  pnpm local-npm:publish # public pacakges to the local repo
  pnpm test:integration  # runs integration tests
  ```

See [this README](integration-tests/README.md) for details.

### Code Coverage

- Generate coverage report:
  ```bash
  pnpm test:coverage
  ```
- Reports output to `coverage/`.

### Linting & Formatting

- Run ESLint:
  ```bash
  pnpm lint
  ```
- Code style follows `eslint.config.mjs` and Prettier defaults.
- Comments must end with a period.

### Type Checking

- Ensure TypeScript errors are absent via build:
  ```bash
  pnpm build
  ```
- Build runs `tsx scripts/embedMeta.ts` (prebuild) and `tsc` for each package.

### Mandatory Local Run Order

For every code change, run the full validation sequence locally:

```bash
pnpm lint && pnpm build && pnpm -r build-check && pnpm test
```

### Pre-commit Hooks

- You can skip failing precommit hooks using `--no-verify` during commit.

## Repo-Specific Utilities

- `pnpm dev`:
  Runs concurrent watch builds for all packages and starts the docs dev server.
  ```bash
  pnpm dev
  ```
- Documentation site:
  ```bash
  pnpm docs:dev
  pnpm docs:build
  ```
- Examples:
  ```bash
  pnpm examples:basic
  pnpm examples:agents-as-tools
  pnpm examples:deterministic
  pnpm examples:tools-shell
  pnpm examples:tools-apply-patch
  # See root package.json "examples:*" scripts for full list
  ```
- Metadata embedding (prebuild):
  ```bash
  pnpm -F <package> build
  # runs embedMeta.ts automatically
  ```
- Workspace scoping (operate on a single package):
  ```bash
  pnpm -F agents-core build
  pnpm -F agents-openai test
  ```

## Style, Linting & Type Checking

- Follow ESLint rules (`eslint.config.mjs`), no unused imports, adhere to Prettier.
- Run `pnpm lint` and fix all errors locally.
- Use `pnpm build` to catch type errors.

## Prerequisites

- Node.js 22+ recommended.
- pnpm 10+ (`corepack enable` is recommended to manage versions).

## Development Workflow

1.  Sync with `main` (or default branch).
2.  Create a feature/fix branch with a descriptive name:
    ```bash
    git checkout -b feat/<short-description>
    ```
3.  Make changes and add/update unit tests in `packages/<pkg>/test` unless doing so is truly infeasible.
4.  Run all checks (in this order):
    ```bash
    pnpm lint && pnpm build && pnpm -r build-check && pnpm test
    ```
5.  Commit using Conventional Commits.
6.  Push and open a pull request.

## Pull Request & Commit Guidelines

- Use **Conventional Commits**:
  - `feat`: new feature
  - `fix`: bug fix
  - `docs`: documentation only
  - `test`: adding or fixing tests
  - `chore`: build, CI, or tooling changes
  - `perf`: performance improvement
  - `refactor`: code changes without feature or fix
  - `build`: changes that affect the build system
  - `ci`: CI configuration
  - `style`: code style (formatting, missing semicolons, etc.)
  - `types`: type-related changes
  - `revert`: reverts a previous commit
- Commit message format:

  ```
  <type>(<scope>): <short summary>

  Optional longer description.
  ```

- Keep summary under 80 characters.
- If your change affects the public API, add a Changeset via:
  ```bash
  pnpm changeset
  ```

## Review Process & What Reviewers Look For

- ✅ All automated checks pass (build, tests, lint).
- ✅ Tests cover new behavior and edge cases.
- ✅ Code is readable and maintainable.
- ✅ Public APIs have doc comments.
- ✅ Examples updated if behavior changes.
- ✅ Documentation (in `docs/`) updated for user-facing changes.
- ✅ Commit history is clean and follows Conventional Commits.

## Tips for Navigating the Repo

- Use `pnpm -F <pkg>` to operate on a specific package.
- Study `vitest.config.ts` for test patterns (e.g., setup files, aliasing).
- Explore `scripts/embedMeta.ts` for metadata generation logic.
- Examples in `examples/` are fully functional apps—run them to understand usage.
- Docs in `docs/src/` use Astro and Starlight; authored content lives under `docs/src/content/docs/` and mirrors package APIs.


## Links discovered
- [this README](https://github.com/openai/openai-agents-js/blob/main/integration-tests/README.md)

--- README.md ---
# OpenAI Agents SDK (JavaScript/TypeScript)

[![npm version](https://badge.fury.io/js/@openai%2Fagents.svg)](https://badge.fury.io/js/@openai%2Fagents)
[![CI](https://github.com/openai/openai-agents-js/actions/workflows/test.yml/badge.svg)](https://github.com/openai/openai-agents-js/actions/workflows/test.yml)

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows in JavaScript/TypeScript. It is provider-agnostic, supporting OpenAI APIs and more.

<img src="https://cdn.openai.com/API/docs/images/orchestration.png" alt="Image of the Agents Tracing UI" style="max-height: 803px;">

> [!NOTE]
> Looking for the Python version? Check out [OpenAI Agents SDK Python](https://github.com/openai/openai-agents-python).

## Core concepts

1. **Agents**: LLMs configured with instructions, tools, guardrails, and handoffs.
2. **Handoffs**: Specialized tool calls for transferring control between agents.
3. **Guardrails**: Configurable safety checks for input and output validation.
4. **Tracing**: Built-in tracking of agent runs, allowing you to view, debug, and optimize your workflows.

Explore the [`examples/`](examples/) directory to see the SDK in action.

## Supported Features

- [x] **Multi-Agent Workflows**: Compose and orchestrate multiple agents in a single workflow.
- [x] **Tool Integration**: Seamlessly call tools/functions from within agent responses.
- [x] **Handoffs**: Transfer control between agents dynamically during a run.
- [x] **Structured Outputs**: Support for both plain text and schema-validated structured outputs.
- [x] **Streaming Responses**: Stream agent outputs and events in real time.
- [x] **Tracing & Debugging**: Built-in tracing for visualizing and debugging agent runs.
- [x] **Guardrails**: Input and output validation for safety and reliability.
- [x] **Parallelization**: Run agents or tool calls in parallel and aggregate results.
- [x] **Human-in-the-Loop**: Integrate human approval or intervention into workflows.
- [x] **Realtime Voice Agents**: Build realtime voice agents using WebRTC or WebSockets
- [x] **Local MCP Server Support**: Give an Agent access to a locally running MCP server to provide tools
- [x] **Separate optimized browser package**: Dedicated package meant to run in the browser for Realtime agents.
- [x] **Broader model support**: Use non-OpenAI models through the Vercel AI SDK adapter
- [ ] **Long running functions**: Suspend an agent loop to execute a long-running function and revive it later <img src="https://img.shields.io/badge/Future-lightgrey" alt="Future" style="width: auto; height: 1em; vertical-align: middle;">
- [ ] **Voice pipeline**: Chain text-based agents using speech-to-text and text-to-speech into a voice agent <img src="https://img.shields.io/badge/Future-lightgrey" alt="Future" style="width: auto; height: 1em; vertical-align: middle;">

## Get started

### Supported environments

- Node.js 22 or later
- Deno
- Bun

Experimental support:

- Cloudflare Workers with `nodejs_compat` enabled

[Check out the documentation](https://openai.github.io/openai-agents-js/guides/troubleshooting/) for more detailed information.

### Installation

```bash
npm install @openai/agents zod@3
```

## Hello world example

```js
import { Agent, run } from '@openai/agents';

const agent = new Agent({
  name: 'Assistant',
  instructions: 'You are a helpful assistant',
});

const result = await run(
  agent,
  'Write a haiku about recursion in programming.',
);
console.log(result.finalOutput);
// Code within the code,
// Functions calling themselves,
// Infinite loop's dance.
```

(_If running this, ensure you set the `OPENAI_API_KEY` environment variable_)

## Functions example

```js
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  tools: [getWeatherTool],
});

async function main() {
  const result = await run(agent, 'What is the weather in Tokyo?');
  console.log(result.finalOutput);
}

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

## Handoffs example

```js
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const dataAgent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  handoffDescription: 'You know everything about the weather',
  tools: [getWeatherTool],
});

// Use Agent.create method to ensure the finalOutput type considers handoffs
const agent = Agent.create({
  name: 'Basic test agent',
  instructions: 'You are a basic agent',
  handoffs: [dataAgent],
});

async function main() {
  const result = await run(agent, 'What is the weather in San Francisco?');
  console.log(result.finalOutput);
}

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

## Voice Agent

```js
import { z } from 'zod';
import { RealtimeAgent, RealtimeSession, tool } from '@openai/agents-realtime';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new RealtimeAgent({
  name: 'Data agent',
  instructions:
    'You are a data agent. When you are asked to check weather, you must use the available tools.',
  tools: [getWeatherTool],
});

// Intended to run in the browser
const { apiKey } = await fetch('/path/to/ephemeral/key/generation').then(
  (resp) => resp.json(),
);
// Automatically configures audio input/output — start talking
const session = new RealtimeSession(agent);
await session.connect({ apiKey });
```

## Running Complete Examples

The [`examples/`](examples/) directory contains a series of examples to get started:

- `pnpm examples:basic` - Basic example with handoffs and tool calling
- `pnpm examples:agents-as-tools` - Using agents as tools for translation
- `pnpm examples:tools-web-search` - Using the web search tool
- `pnpm examples:tools-file-search` - Using the file search tool
- `pnpm examples:deterministic` - Deterministic multi-agent workflow
- `pnpm examples:parallelization` - Running agents in parallel and picking the best result
- `pnpm examples:human-in-the-loop` - Human approval for certain tool calls
- `pnpm examples:streamed` - Streaming agent output and events in real time
- `pnpm examples:streamed:human-in-the-loop` - Streaming output with human-in-the-loop approval
- `pnpm examples:routing` - Routing between agents based on language or context
- `pnpm examples:realtime-demo` - Framework agnostic Voice Agent example
- `pnpm examples:realtime-next` - Next.js Voice Agent example application

## The agent loop

When you call `Runner.run()`, the SDK executes a loop until a final output is produced.

1. The agent is invoked with the given input, using the model and settings configured on the agent (or globally).
2. The LLM returns a response, which may include tool calls or handoff requests.
3. If the response contains a final output (see below), the loop ends and the result is returned.
4. If the response contains a handoff, the agent is switched to the new agent and the loop continues.
5. If there are tool calls, the tools are executed, their results are appended to the message history, and the loop continues.

You can control the maximum number of iterations with the `maxTurns` parameter.

### Final output

The final output is the last thing the agent produces in the loop.

1. If the agent has an `outputType` (structured output), the loop ends when the LLM returns a response matching that type.
2. If there is no `outputType` (plain text), the first LLM response without tool calls or handoffs is considered the final output.

**Summary of the agent loop:**

- If the current agent has an `outputType`, the loop runs until structured output of that type is produced.
- If not, the loop runs until a message is produced with no tool calls or handoffs.

### Error handling

- If the maximum number of turns is exceeded, a `MaxTurnsExceededError` is thrown.
- If a guardrail is triggered, a `GuardrailTripwireTriggered` exception is raised.

## Documentation

To view the documentation locally:

```bash
pnpm docs:dev
```

Then visit [http://localhost:4321](http://localhost:4321) in your browser.

## Development

If you want to contribute or edit the SDK/examples:

1. Install dependencies

   ```bash
   pnpm install
   ```

2. Build the project

   ```bash
   pnpm build && pnpm -r build-check
   ```

3. Run tests and linter

   ```bash
   pnpm test && pnpm lint
   ```

See `AGENTS.md` and `CONTRIBUTING.md` for the full contributor guide.

## Acknowledgements

We'd like to acknowledge the excellent work of the open-source community, especially:

- [zod](https://github.com/colinhacks/zod) (schema validation)
- [Starlight](https://github.com/withastro/starlight)
- [vite](https://github.com/vitejs/vite) and [vitest](https://github.com/vitest-dev/vitest)
- [pnpm](https://pnpm.io/)
- [Next.js](https://github.com/vercel/next.js)

We're committed to building the Agents SDK as an open source framework so others in the community can expand on our approach.

For more details, see the [documentation](https://openai.github.io/openai-agents-js) or explore the [`examples/`](examples/) directory.


## Links discovered
- [![npm version](https://badge.fury.io/js/@openai%2Fagents.svg)
- [![CI](https://github.com/openai/openai-agents-js/actions/workflows/test.yml/badge.svg)
- [OpenAI Agents SDK Python](https://github.com/openai/openai-agents-python)
- [`examples/`](https://github.com/openai/openai-agents-js/blob/main/examples.md)
- [Check out the documentation](https://openai.github.io/openai-agents-js/guides/troubleshooting/)
- [http://localhost:4321](http://localhost:4321)
- [zod](https://github.com/colinhacks/zod)
- [Starlight](https://github.com/withastro/starlight)
- [vite](https://github.com/vitejs/vite)
- [vitest](https://github.com/vitest-dev/vitest)
- [pnpm](https://pnpm.io/)
- [Next.js](https://github.com/vercel/next.js)
- [documentation](https://openai.github.io/openai-agents-js)

--- helpers/tests/setup.ts ---
import { setTracingDisabled } from '../../packages/agents-core/src';

export function setup() {
  setTracingDisabled(true);
}


--- scripts/embedMeta.ts ---
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { cwd } from 'node:process';

const packageJson = JSON.parse(
  readFileSync(resolve(cwd(), 'package.json'), 'utf-8'),
);

const dependencies = Object.entries(packageJson.dependencies ?? {});
const openaiDependencies = dependencies.filter(
  ([name]) => name.startsWith('@openai/') || name === 'openai',
);

const versions = {
  [packageJson.name]: packageJson.version,
  ...Object.fromEntries(
    openaiDependencies.map(([name, version]) => [name, version]),
  ),
};

const METADATA = {
  name: packageJson.name,
  version: packageJson.version,
  versions,
};

const output = `
// This file is automatically generated

export const METADATA = ${JSON.stringify(METADATA, null, 2)};

export default METADATA;
`;

writeFileSync(resolve(cwd(), 'src/metadata.ts'), output, 'utf-8');


--- vitest.config.ts ---
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    projects: ['packages/*'],
    globalSetup: './helpers/tests/setup.ts',
    // Enable code coverage reporting with Vitest's built‑in integration. We
    // only enable it for the monorepo packages (workspaces) so that the
    // initial report focuses on our public libraries and avoids unnecessary
    // noise from docs and examples.
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'json'],
      all: true,
      // Only include source files from the published packages. This keeps the
      // metrics meaningful and prevents Vitest from trying to instrument node
      // dependencies or the compiled dist folder.
      include: ['packages/**/src/**/*.ts'],
      exclude: ['**/*.d.ts', 'packages/**/test/**', 'packages/**/dist/**'],
    },
  },
});


--- vitest.integration.config.ts ---
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    include: ['./integration-tests/*.test.ts'],
    globalSetup: './integration-tests/_helpers/setup.ts',
  },
});


--- .changeset/README.md ---
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)


## Links discovered
- [in our repository](https://github.com/changesets/changesets)
- [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

--- .changeset/anthropic-extended-thinking.md ---
---
'@openai/agents-extensions': patch
---

feat(agents-extensions): #628 add Anthropic extended thinking support


--- .changeset/five-cheetahs-work.md ---
---
"@openai/agents-realtime": patch
---

feat(realtime): Add usage field to input audio transcription completed event


--- .changeset/real-pumas-sit.md ---
---
"@openai/agents-core": patch
---

chore(deps): bump @modelcontextprotocol/sdk from 1.12.1 to 1.24.0


--- .changeset/sharp-towns-cheer.md ---
---
'@openai/agents-openai': patch
---

fix: support input_file for chat completions when possible


--- .changeset/thin-ligers-share.md ---
---
"@openai/agents-core": patch
---

fix: #701 prevent duplicate function_call items in session history after resuming from interruption


--- .changeset/warm-beans-build.md ---
---
'@openai/agents-core': patch
---

SpanData types are exported from distribution types for use when writing custom TracingExporters and Tracingprocessors


--- .github/ISSUE_TEMPLATE/bug_report.md ---
---
name: Bug report
about: Report a bug
title: ''
labels: bug
assignees: ''
---

### Please read this first

- **Have you read the docs?** [Agents SDK docs](https://openai.github.io/openai-agents-js/)
- **Have you searched for related issues?** Others may have faced similar issues.

### Describe the bug

A clear and concise description of what the bug is.

### Debug information

- Agents SDK version: (e.g. `v0.0.1`)
- Runtime environment (e.g. `Node.js 22.16.0`)

### Repro steps

Ideally provide a minimal JavaScript/TypeScript script that can be run to reproduce the bug.

### Expected behavior

A clear and concise description of what you expected to happen.


## Links discovered
- [Agents SDK docs](https://openai.github.io/openai-agents-js/)

--- .github/ISSUE_TEMPLATE/feature_request.md ---
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---

### Please read this first

- **Have you read the docs?** [Agents SDK docs](https://openai.github.io/openai-agents-js/)
- **Have you searched for related issues?** Others may have had similar requests

### Describe the feature

What is the feature you're requesting? How would it work? Please provide examples and details if possible.


## Links discovered
- [Agents SDK docs](https://openai.github.io/openai-agents-js/)

--- .github/ISSUE_TEMPLATE/model_provider.md ---
---
name: Custom model providers
about: Questions or bugs about using non-OpenAI models
title: ''
labels: bug
assignees: ''
---

### Please read this first

- **Have you read the custom model provider docs, including the 'Common issues' section?** [Model provider docs](https://openai.github.io/openai-agents-js/guides/models#custom-model-providers)
- **Have you searched for related issues?** Others may have faced similar issues.

### Describe the question

A clear and concise description of what the question or bug is.

### Debug information

- Agents SDK version: (e.g. `v0.0.1`)
- Runtime environment (e.g. `Node.js 22.16.0`)

### Repro steps

Ideally provide a minimal python script that can be run to reproduce the issue.

### Expected behavior

A clear and concise description of what you expected to happen.


## Links discovered
- [Model provider docs](https://openai.github.io/openai-agents-js/guides/models#custom-model-providers)

--- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md ---
### Summary

<!-- Please give a short summary of the change and the problem this solves. -->

### Test plan

<!-- Please explain how this was tested -->

### Issue number

<!-- For example: "Closes #1234" -->

### Checks

- [ ] I've added new tests (if relevant)
- [ ] I've added/updated the relevant documentation
- [ ] I've run `pnpm test` and `pnpm test:examples`
  - [ ] (If you made a major change) I've run `pnpm test:integration` [(see details)](https://github.com/openai/openai-agents-js/tree/main/integration-tests)
- [ ] I've made sure tests pass
- [ ] I've added a changeset using `pnpm changeset` to indicate my changes


## Links discovered
- [(see details)](https://github.com/openai/openai-agents-js/tree/main/integration-tests)

--- .github/ISSUE_TEMPLATE/question.md ---
---
name: Question
about: Questions about the SDK
title: ''
labels: question
assignees: ''
---

### Please read this first

- **Have you read the docs?** [Agents SDK docs](https://openai.github.io/openai-agents-js/)
- **Have you searched for related issues?** Others may have had similar requests

### Question

Describe your question. Provide details if available.


## Links discovered
- [Agents SDK docs](https://openai.github.io/openai-agents-js/)

--- integration-tests/README.md ---
# Integration tests

This project hosts packages to test the different environments that the Agents SDK works in.

It is intentionally not part of the `pnpm` workspace and instead installs the packages from a
local package registry using verdaccio.

## How to run integration tests

1. **Requirements:**

- Have Node.js, Bun, and Deno installed globally
- Have an `OPENAI_API_KEY` environment variable configured
- Add into `integration-tests/cloudflare-workers/worker` a file `.dev.vars` with `OPENAI_API_KEY=<your key>`
- Add into `integration-tests/vite-react` a `.env` file with `VITE_OPENAI_API_KEY=<your key>`
- Run `pnpm exec playwright install` to install playwright

2. **Local npm registry**

   We will publish packages in a local registry to emulate a real environment.

   Run in one process `pnpm run local-npm:start` and keep it running until you are done with your test.

   **Hint:** The first time you might have to run `npm adduser --registry http://localhost:4873/` (you can use any fake data)

3. **Publish your packages to run the tests**

   In order to test the packages first build them (`pnpm build`) and then run `pnpm local-npm:publish`.

4. **Run your tests**

   You can now run your integration tests:

   ```bash
   pnpm test:integration
   ```


--- integration-tests/vite-react/index.html ---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vite + React Test</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


--- integration-tests/bun.test.ts ---
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({ cwd: './integration-tests/bun' });

describe('Bun', () => {
  beforeAll(async () => {
    // remove lock file to avoid errors
    await execa`rm -f bun.lock`;
    console.log('[bun] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[bun] Installing dependencies');
    await execa`bun install`;
  }, 60000);

  test('should be able to run', async () => {
    const { stdout } = await execa`bun run index.ts`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });

  afterAll(async () => {
    await execa`rm -f bun.lock`;
  });
});


--- integration-tests/cloudflare.test.ts ---
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { execa as execaBase, ResultPromise } from 'execa';

const execa = execaBase({
  cwd: './integration-tests/cloudflare-workers/worker',
});

let server: ResultPromise;

describe('Cloudflare Workers', () => {
  beforeAll(async () => {
    // Remove lock file to avoid errors
    await execa`rm -f package-lock.json`;
    console.log('[cloudflare] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[cloudflare] Installing dependencies');
    await execa`npm install`;
    console.log('[cloudflare] Starting server');
    server = execa`npm run start`;
    await new Promise((resolve) => {
      server.stdout?.on('data', (data) => {
        if (data.toString().includes('Ready')) {
          resolve(true);
        }
      });
    });
    process.on('exit', () => {
      if (server) {
        server.kill();
      }
    });
  }, 60000);

  test(
    'should be able to run',
    async () => {
      const response = await fetch('http://localhost:8787/');
      const text = await response.text();
      expect(text).toContain('[RESPONSE]Hello there![/RESPONSE]');
    },
    {
      timeout: 60000,
    },
  );

  afterAll(async () => {
    if (server) {
      server.kill();
    }
  });
});


--- integration-tests/deno.test.ts ---
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({ cwd: './integration-tests/deno' });

describe('Deno', () => {
  beforeAll(async () => {
    // Remove lock file to avoid errors
    await execa`rm -f deno.lock`;
    console.log('[deno] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[deno] Installing dependencies');
    await execa`deno install`;
  }, 60000);

  test('should be able to run', { timeout: 60000 }, async () => {
    const { stdout } = await execa`deno --allow-net --allow-env main.ts`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });

  afterAll(async () => {
    await execa`rm -f deno.lock`;
  });
});


--- integration-tests/node-zod3.test.ts ---
import { describe, test, expect, beforeAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({
  cwd: './integration-tests/node-zod3',
  env: {
    ...process.env,
    NODE_OPTIONS: '',
    TS_NODE_PROJECT: '',
    TS_NODE_COMPILER_OPTIONS: '',
  },
});

describe('Node.js', () => {
  beforeAll(async () => {
    // remove lock file to avoid errors
    console.log('[node] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[node] Installing dependencies');
    await execa`npm install`;
  }, 60000);

  test('should be able to run using CommonJS', async () => {
    const { stdout } = await execa`npm run start:cjs`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });

  test('should be able to run using ESM', async () => {
    const { stdout } = await execa`npm run start:esm`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });
});


--- integration-tests/node-zod4.test.ts ---
import { describe, test, expect, beforeAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({
  cwd: './integration-tests/node-zod4',
  env: {
    ...process.env,
    NODE_OPTIONS: '',
    TS_NODE_PROJECT: '',
    TS_NODE_COMPILER_OPTIONS: '',
  },
});

describe('Node.js', () => {
  beforeAll(async () => {
    // remove lock file to avoid errors
    console.log('[node] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[node] Installing dependencies');
    await execa`npm install`;
  }, 60000);

  test('should be able to run using CommonJS', async () => {
    const { stdout } = await execa`npm run start:cjs`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });

  test('should be able to run using ESM', async () => {
    const { stdout } = await execa`npm run start:esm`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });
});


--- integration-tests/node.test.ts ---
import { describe, test, expect, beforeAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({
  cwd: './integration-tests/node',
  env: {
    ...process.env,
    NODE_OPTIONS: '',
    TS_NODE_PROJECT: '',
    TS_NODE_COMPILER_OPTIONS: '',
  },
});

describe('Node.js', () => {
  beforeAll(async () => {
    // remove lock file to avoid errors
    console.log('[node] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[node] Installing dependencies');
    await execa`npm install`;
  }, 60000);

  test('should be able to run using CommonJS', async () => {
    const { stdout } = await execa`npm run start:cjs`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });

  test('should be able to run using ESM', async () => {
    const { stdout } = await execa`npm run start:esm`;
    expect(stdout).toContain('[RESPONSE]Hello there![/RESPONSE]');
  });
});


--- integration-tests/vite-react.test.ts ---
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { chromium } from 'playwright';
import { execa as execaBase, ResultPromise } from 'execa';
import { writeFile, unlink } from 'node:fs/promises';
import path from 'node:path';

const execa = execaBase({
  cwd: './integration-tests/vite-react',
});

let server: ResultPromise;
const envPath = path.join(
  process.cwd(),
  'integration-tests',
  'vite-react',
  '.env',
);
let wroteEnvFile = false;

describe('Vite React', () => {
  beforeAll(async () => {
    // Remove lock file to avoid errors
    await execa`rm -f package-lock.json`;
    console.log('[vite-react] Removing node_modules');
    await execa`rm -rf node_modules`;
    console.log('[vite-react] Installing dependencies');
    await execa`npm install`;

    const apiKey = process.env.OPENAI_API_KEY;
    if (!apiKey) {
      throw new Error(
        'OPENAI_API_KEY must be set to run the Vite React integration test.',
      );
    }
    await writeFile(envPath, `VITE_OPENAI_API_KEY=${apiKey}\n`, 'utf8');
    wroteEnvFile = true;

    console.log('[vite-react] Building');
    await execa`npm run build`;
    console.log('[vite-react] Starting server');
    server = execa`npm run preview -- --port 9999`;
    server.catch(() => {});
    await new Promise((resolve) => {
      server.stdout?.on('data', (data) => {
        if (data.toString().includes('http://localhost')) {
          resolve(true);
        }
      });
    });
    process.on('exit', () => {
      if (server) {
        server.kill();
      }
    });
  }, 60000);

  test('should be able to run', { timeout: 60000 }, async () => {
    const browser = await chromium.launch();
    const page = await browser.newPage();
    await page.goto('http://localhost:9999/');
    const root = await page.$('#root');
    const span = await root?.waitForSelector('span[data-testid="response"]', {
      state: 'attached',
      timeout: 60000,
    });
    expect(await span?.textContent()).toBe('[RESPONSE]Hello there![/RESPONSE]');
    await browser.close();
  });

  afterAll(async () => {
    if (server) {
      server.kill();
    }
    if (wroteEnvFile) {
      await unlink(envPath).catch(() => {});
    }
  });
});


--- integration-tests/bun/index.ts ---
// @ts-check

import {
  Agent,
  run,
  setTraceProcessors,
  ConsoleSpanExporter,
  BatchTraceProcessor,
} from '@openai/agents';

setTraceProcessors([new BatchTraceProcessor(new ConsoleSpanExporter())]);

const agent = new Agent({
  name: 'Test Agent',
  instructions:
    'You will always only respond with "Hello there!". Not more not less.',
});

const result = await run(agent, 'Hey there!');
console.log(`[RESPONSE]${result.finalOutput}[/RESPONSE]`);


--- packages/agents-core/README.md ---
# OpenAI Agents SDK

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

## Installation

```bash
npm install @openai/agents
```

## License

MIT


--- packages/agents-extensions/README.md ---
# OpenAI Agents SDK Extensions

This package contains a collection of extension features for the OpenAI Agents SDK and is intended to be used alongside it.

## Installation

```bash
npm install @openai/agents @openai/agents-extensions
```

## License

MIT


--- packages/agents-openai/README.md ---
# OpenAI Agents SDK

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

## Installation

```bash
npm install @openai/agents
```

## License

MIT


--- packages/agents-realtime/README.md ---
# OpenAI Agents SDK

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

## Installation

```bash
npm install @openai/agents
```

## License

MIT


--- packages/agents/README.md ---
# OpenAI Agents SDK (JavaScript/TypeScript)

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows in JavaScript/TypeScript. It is provider-agnostic, supporting OpenAI APIs and more.

<img src="https://cdn.openai.com/API/docs/images/orchestration.png" alt="Image of the Agents Tracing UI" style="max-height: 803px;">

## Core concepts

1. **Agents**: LLMs configured with instructions, tools, guardrails, and handoffs.
2. **Handoffs**: Specialized tool calls for transferring control between agents.
3. **Guardrails**: Configurable safety checks for input and output validation.
4. **Tracing**: Built-in tracking of agent runs, allowing you to view, debug, and optimize your workflows.

Explore the [`examples/`](examples/) directory to see the SDK in action.

## Supported Features

- [x] **Multi-Agent Workflows**: Compose and orchestrate multiple agents in a single workflow.
- [x] **Tool Integration**: Seamlessly call tools/functions from within agent responses.
- [x] **Handoffs**: Transfer control between agents dynamically during a run.
- [x] **Structured Outputs**: Support for both plain text and schema-validated structured outputs.
- [x] **Streaming Responses**: Stream agent outputs and events in real time.
- [x] **Tracing & Debugging**: Built-in tracing for visualizing and debugging agent runs.
- [x] **Guardrails**: Input and output validation for safety and reliability.
- [x] **Parallelization**: Run agents or tool calls in parallel and aggregate results.
- [x] **Human-in-the-Loop**: Integrate human approval or intervention into workflows.
- [x] **Realtime Voice Agents**: Build realtime voice agents using WebRTC or WebSockets
- [x] **Local MCP Server Support**: Give an Agent access to a locally running MCP server to provide tools
- [x] **Separate optimized browser package**: Dedicated package meant to run in the browser for Realtime agents.
- [x] **Broader model support**: Use non-OpenAI models through the Vercel AI SDK adapter
- [ ] **Long running functions**: Suspend an agent loop to execute a long-running function and revive it later <img src="https://img.shields.io/badge/Future-lightgrey" alt="Future" style="width: auto; height: 1em; vertical-align: middle;">
- [ ] **Voice pipeline**: Chain text-based agents using speech-to-text and text-to-speech into a voice agent <img src="https://img.shields.io/badge/Future-lightgrey" alt="Future" style="width: auto; height: 1em; vertical-align: middle;">

## Get started

### Supported environments

- Node.js 22 or later
- Deno
- Bun

Experimental support:

- Cloudflare Workers with `nodejs_compat` enabled

[Check out the documentation](https://openai.github.io/openai-agents-js/guides/troubleshooting/) for more detailed information.

### Installation

```bash
npm install @openai/agents zod@3
```

## Hello world example

```js
import { Agent, run } from '@openai/agents';

const agent = new Agent({
  name: 'Assistant',
  instructions: 'You are a helpful assistant',
});

const result = await run(
  agent,
  'Write a haiku about recursion in programming.',
);
console.log(result.finalOutput);
// Code within the code,
// Functions calling themselves,
// Infinite loop's dance.
```

(_If running this, ensure you set the `OPENAI_API_KEY` environment variable_)

## Functions example

```js
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  tools: [getWeatherTool],
});

async function main() {
  const result = await run(agent, 'What is the weather in Tokyo?');
  console.log(result.finalOutput);
}

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

## Handoffs example

```js
import { z } from 'zod';
import { Agent, run, tool } from '@openai/agents';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const dataAgent = new Agent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  handoffDescription: 'You know everything about the weather',
  tools: [getWeatherTool],
});

// Use Agent.create method to ensure the finalOutput type considers handoffs
const agent = Agent.create({
  name: 'Basic test agent',
  instructions: 'You are a basic agent',
  handoffs: [dataAgent],
});

async function main() {
  const result = await run(agent, 'What is the weather in San Francisco?');
  console.log(result.finalOutput);
}

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

## Voice Agent

```js
import { z } from 'zod';
import { RealtimeAgent, RealtimeSession, tool } from '@openai/agents-realtime';

const getWeatherTool = tool({
  name: 'get_weather',
  description: 'Get the weather for a given city',
  parameters: z.object({ city: z.string() }),
  execute: async (input) => {
    return `The weather in ${input.city} is sunny`;
  },
});

const agent = new RealtimeAgent({
  name: 'Data agent',
  instructions: 'You are a data agent',
  tools: [getWeatherTool],
});

// Intended to run in the browser
const { apiKey } = await fetch('/path/to/ephemeral/key/generation').then(
  (resp) => resp.json(),
);
// Automatically configures audio input/output — start talking
const session = new RealtimeSession(agent);
await session.connect({ apiKey });
```

## The agent loop

When you call `Runner.run()`, the SDK executes a loop until a final output is produced.

1. The agent is invoked with the given input, using the model and settings configured on the agent (or globally).
2. The LLM returns a response, which may include tool calls or handoff requests.
3. If the response contains a final output (see below), the loop ends and the result is returned.
4. If the response contains a handoff, the agent is switched to the new agent and the loop continues.
5. If there are tool calls, the tools are executed, their results are appended to the message history, and the loop continues.

You can control the maximum number of iterations with the `maxTurns` parameter.

### Final output

The final output is the last thing the agent produces in the loop.

1. If the agent has an `outputType` (structured output), the loop ends when the LLM returns a response matching that type.
2. If there is no `outputType` (plain text), the first LLM response without tool calls or handoffs is considered the final output.

**Summary of the agent loop:**

- If the current agent has an `outputType`, the loop runs until structured output of that type is produced.
- If not, the loop runs until a message is produced with no tool calls or handoffs.

### Error handling

- If the maximum number of turns is exceeded, a `MaxTurnsExceededError` is thrown.
- If a guardrail is triggered, a `GuardrailTripwireTriggered` exception is raised.

## Acknowledgements

We'd like to acknowledge the excellent work of the open-source community, especially:

- [zod](https://github.com/colinhacks/zod) (schema validation)
- [Starlight](https://github.com/withastro/starlight)
- [vite](https://github.com/vitejs/vite) and [vitest](https://github.com/vitest-dev/vitest)
- [pnpm](https://pnpm.io/)
- [Next.js](https://github.com/vercel/next.js)

We're committed to building the Agents SDK as an open source framework so others in the community can expand on our approach.

For more details, see the [documentation](https://openai.github.io/openai-agents-js) or explore the [`examples/`](examples/) directory.


## Links discovered
- [`examples/`](https://github.com/openai/openai-agents-js/blob/main/packages/agents/examples.md)
- [Check out the documentation](https://openai.github.io/openai-agents-js/guides/troubleshooting/)
- [zod](https://github.com/colinhacks/zod)
- [Starlight](https://github.com/withastro/starlight)
- [vite](https://github.com/vitejs/vite)
- [vitest](https://github.com/vitest-dev/vitest)
- [pnpm](https://pnpm.io/)
- [Next.js](https://github.com/vercel/next.js)
- [documentation](https://openai.github.io/openai-agents-js)

--- packages/agents-realtime/vite.config.js ---
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';

const __dirname = dirname(fileURLToPath(import.meta.url));

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'dist/index.mjs'),
      name: 'OpenAIAgentsRealtime',
      // the proper extensions will be added
      fileName: 'openai-realtime-agents',
    },
    sourcemap: 'inline',
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: [],
      output: {
        dir: 'dist/bundle',
        banner: '/** OpenAI Agents Realtime **/',
        minifyInternalExports: false,
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          // vue: 'Vue',
        },
      },
    },
  },
});


--- packages/agents-core/src/agent.ts ---
import { z } from 'zod';

import type { InputGuardrail, OutputGuardrail } from './guardrail';
import { AgentHooks } from './lifecycle';
import { getAllMcpTools, type MCPServer } from './mcp';
import type { Model, ModelSettings, Prompt } from './model';
import {
  getDefaultModelSettings,
  gpt5ReasoningSettingsRequired,
  isGpt5Default,
} from './defaultModel';
import type { RunContext } from './runContext';
import {
  type FunctionTool,
  type FunctionToolResult,
  tool,
  type Tool,
  type ToolApprovalFunction,
  type ToolEnabledFunction,
} from './tool';
import type {
  ResolvedAgentOutput,
  JsonSchemaDefinition,
  HandoffsOutput,
  Expand,
} from './types';
import type { RunResult } from './result';
import { getHandoff, type Handoff } from './handoff';
import { NonStreamRunOptions, RunConfig, Runner } from './run';
import { toFunctionToolName } from './utils/tools';
import { getOutputText } from './utils/messages';
import { isAgentToolInput } from './utils/typeGuards';
import { isZodObject } from './utils/typeGuards';
import { ModelBehaviorError, UserError } from './errors';
import { RunToolApprovalItem } from './items';
import logger from './logger';
import { UnknownContext, TextOutput } from './types';
import type * as protocol from './types/protocol';
import type { ZodObjectLike } from './utils/zodCompat';

type AnyAgentRunResult = RunResult<any, Agent<any, any>>;
type CompletedRunResult<
  TContext,
  TAgent extends Agent<TContext, any>,
> = RunResult<TContext, TAgent> & {
  finalOutput: ResolvedAgentOutput<TAgent['outputType']>;
};

// Per-process, ephemeral map linking a function tool call to its nested
// Agent run result within the same run; entry is removed after consumption.
const agentToolRunResults = new WeakMap<
  protocol.FunctionCallItem,
  AnyAgentRunResult
>();

export function saveAgentToolRunResult(
  toolCall: protocol.FunctionCallItem | undefined,
  runResult: AnyAgentRunResult,
): void {
  if (toolCall) {
    agentToolRunResults.set(toolCall, runResult);
  }
}

export function consumeAgentToolRunResult(
  toolCall: protocol.FunctionCallItem,
): AnyAgentRunResult | undefined {
  const runResult = agentToolRunResults.get(toolCall);
  if (runResult) {
    agentToolRunResults.delete(toolCall);
  }

  return runResult;
}

export type ToolUseBehaviorFlags = 'run_llm_again' | 'stop_on_first_tool';

export type ToolsToFinalOutputResult =
  | {
      /**
       * Whether this is the final output. If `false`, the LLM will run again and receive the tool call output
       */
      isFinalOutput: false;
      /**
       * Whether the agent was interrupted by a tool approval. If `true`, the LLM will run again and receive the tool call output
       */
      isInterrupted: undefined;
    }
  | {
      isFinalOutput: false;
      /**
       * Whether the agent was interrupted by a tool approval. If `true`, the LLM will run again and receive the tool call output
       */
      isInterrupted: true;
      interruptions: RunToolApprovalItem[];
    }
  | {
      /**
       * Whether this is the final output. If `false`, the LLM will run again and receive the tool call output
       */
      isFinalOutput: true;

      /**
       * Whether the agent was interrupted by a tool approval. If `true`, the LLM will run again and receive the tool call output
       */
      isInterrupted: undefined;

      /**
       * The final output. Can be undefined if `isFinalOutput` is `false`, otherwise it must be a string
       * that will be processed based on the `outputType` of the agent.
       */
      finalOutput: string;
    };

/**
 * The type of the output object. If not provided, the output will be a string.
 * 'text' is a special type that indicates the output will be a string.
 *
 * @template HandoffOutputType The type of the output of the handoff.
 */
export type AgentOutputType<HandoffOutputType = UnknownContext> =
  | TextOutput
  | ZodObjectLike
  | JsonSchemaDefinition
  | HandoffsOutput<HandoffOutputType>;

/**
 * A function that takes a run context and a list of tool results and returns a `ToolsToFinalOutputResult`.
 */
export type ToolToFinalOutputFunction = (
  context: RunContext,
  toolResults: FunctionToolResult[],
) => ToolsToFinalOutputResult | Promise<ToolsToFinalOutputResult>;

/**
 * The behavior of the agent when a tool is called.
 */
export type ToolUseBehavior =
  | ToolUseBehaviorFlags
  | {
      /**
       * List of tool names that will stop the agent from running further. The final output will be
       * the output of the first tool in the list that was called.
       */
      stopAtToolNames: string[];
    }
  | ToolToFinalOutputFunction;

/**
 * Configuration for an agent.
 *
 * @template TContext The type of the context object.
 * @template TOutput The type of the output object.
 */
export interface AgentConfiguration<
  TContext = UnknownContext,
  TOutput extends AgentOutputType = TextOutput,
> {
  name: string;

  /**
   * The instructions for the agent. Will be used as the "system prompt" when this agent is
   * invoked. Describes what the agent should do, and how it responds.
   *
   * Can either be a string, or a function that dynamically generates instructions for the agent.
   * If you provide a function, it will be called with the context and the agent instance. It
   * must return a string.
   */
  instructions:
    | string
    | ((
        runContext: RunContext<TContext>,
        agent: Agent<TContext, TOutput>,
      ) => Promise<string> | string);

  /**
   * The prompt template to use for the agent (OpenAI Responses API only).
   *
   * Can either be a prompt template object, or a function that returns a prompt
   * template object. If a function is provided, it will be called with the run
   * context and the agent instance. It must return a prompt template object.
   */
  prompt?:
    | Prompt
    | ((
        runContext: RunContext<TContext>,
        agent: Agent<TContext, TOutput>,
      ) => Promise<Prompt> | Prompt);

  /**
   * A description of the agent. This is used when the agent is used as a handoff, so that an LLM
   * knows what it does and when to invoke it.
   */
  handoffDescription: string;

  /**
   * Handoffs are sub-agents that the agent can delegate to. You can provide a list of handoffs,
   * and the agent can choose to delegate to them if relevant. Allows for separation of concerns
   * and modularity.
   */
  handoffs: (Agent<any, any> | Handoff<any, TOutput>)[];

  /**
   * The warning log would be enabled when multiple output types by handoff agents are detected.
   */
  handoffOutputTypeWarningEnabled?: boolean;

  /**
   * The model implementation to use when invoking the LLM.
   *
   * By default, if not set, the agent will use the default model returned by
   * getDefaultModel (currently "gpt-4.1").
   */
  model: string | Model;

  /**
   * Configures model-specific tuning parameters (e.g. temperature, top_p, etc.)
   */
  modelSettings: ModelSettings;

  /**
   * A list of tools the agent can use.
   */
  tools: Tool<TContext>[];

  /**
   * A list of [Model Context Protocol](https://modelcontextprotocol.io/) servers the agent can use.
   * Every time the agent runs, it will include tools from these servers in the list of available
   * tools.
   *
   * NOTE: You are expected to manage the lifecycle of these servers. Specifically, you must call
   * `server.connect()` before passing it to the agent, and `server.cleanup()` when the server is
   * no longer needed.
   */
  mcpServers: MCPServer[];

  /**
   * A list of checks that run in parallel to the agent by default; set `runInParallel` to false to
   * block LLM/tool calls until the guardrail completes. Runs only if the agent is the first agent
   * in the chain.
   */
  inputGuardrails: InputGuardrail[];

  /**
   * A list of checks that run on the final output of the agent, after generating a response. Runs
   * only if the agent produces a final output.
   */
  outputGuardrails: OutputGuardrail<TOutput>[];

  /**
   * The type of the output object. If not provided, the output will be a string.
   */
  outputType: TOutput;

  /**
   * This lets you configure how tool use is handled.
   * - run_llm_again: The default behavior. Tools are run, and then the LLM receives the results
   *   and gets to respond.
   * - stop_on_first_tool: The output of the first tool call is used as the final output. This means
   *   that the LLM does not process the result of the tool call.
   * - A list of tool names: The agent will stop running if any of the tools in the list are called.
   *   The final output will be the output of the first matching tool call. The LLM does not process
   *   the result of the tool call.
   * - A function: if you pass a function, it will be called with the run context and the list of
   *   tool results. It must return a `ToolsToFinalOutputResult`, which determines whether the tool
   *   call resulted in a final output.
   *
   * NOTE: This configuration is specific to `FunctionTools`. Hosted tools, such as file search, web
   * search, etc. are always processed by the LLM
   */
  toolUseBehavior: ToolUseBehavior;

  /**
   * Whether to reset the tool choice to the default value after a tool has been called. Defaults
   * to `true`. This ensures that the agent doesn't enter an infinite loop of tool usage.
   */
  resetToolChoice: boolean;
}

export type AgentOptions<
  TContext = UnknownContext,
  TOutput extends AgentOutputType = TextOutput,
> = Expand<
  Pick<AgentConfiguration<TContext, TOutput>, 'name'> &
    Partial<AgentConfiguration<TContext, TOutput>>
>;

/**
 * An agent is an AI model configured with instructions, tools, guardrails, handoffs and more.
 *
 * We strongly recommend passing `instructions`, which is the "system prompt" for the agent. In
 * addition, you can pass `handoffDescription`, which is a human-readable description of the
 * agent, used when the agent is used inside tools/handoffs.
 *
 * Agents are generic on the context type. The context is a (mutable) object you create. It is
 * passed to tool functions, handoffs, guardrails, etc.
 */
// --- Type utilities for inferring output type from handoffs ---
type ExtractAgentOutput<T> = T extends Agent<any, infer O> ? O : never;
type ExtractHandoffOutput<T> = T extends Handoff<any, infer O> ? O : never;
export type HandoffsOutputUnion<
  Handoffs extends readonly (Agent<any, any> | Handoff<any, any>)[],
> =
  | ExtractAgentOutput<Handoffs[number]>
  | ExtractHandoffOutput<Handoffs[number]>;

/**
 * Helper type for config with handoffs
 *
 * @template TOutput The type of the output object.
 * @template Handoffs The type of the handoffs.
 */
export type AgentConfigWithHandoffs<
  TOutput extends AgentOutputType,
  Handoffs extends readonly (Agent<any, any> | Handoff<any, any>)[],
> = { name: string; handoffs?: Handoffs; outputType?: TOutput } & Partial<
  Omit<
    AgentConfiguration<UnknownContext, TOutput | HandoffsOutputUnion<Handoffs>>,
    'name' | 'handoffs' | 'outputType'
  >
>;

// The parameter type fo needApproval function for the tool created by Agent.asTool() method
const AgentAsToolNeedApprovalSchame = z.object({ input: z.string() });

/**
 * The class representing an AI agent configured with instructions, tools, guardrails, handoffs and more.
 *
 * We strongly recommend passing `instructions`, which is the "system prompt" for the agent. In
 * addition, you can pass `handoffDescription`, which is a human-readable description of the
 * agent, used when the agent is used inside tools/handoffs.
 *
 * Agents are generic on the context type. The context is a (mutable) object you create. It is
 * passed to tool functions, handoffs, guardrails, etc.
 */
export class Agent<
    TContext = UnknownContext,
    TOutput extends AgentOutputType = TextOutput,
  >
  extends AgentHooks<TContext, TOutput>
  implements AgentConfiguration<TContext, TOutput>
{
  /**
   * Create an Agent with handoffs and automatically infer the union type for TOutput from the handoff agents' output types.
   */
  static create<
    TOutput extends AgentOutputType = TextOutput,
    Handoffs extends readonly (Agent<any, any> | Handoff<any, any>)[] = [],
  >(
    config: AgentConfigWithHandoffs<TOutput, Handoffs>,
  ): Agent<UnknownContext, TOutput | HandoffsOutputUnion<Handoffs>> {
    return new Agent<UnknownContext, TOutput | HandoffsOutputUnion<Handoffs>>({
      ...config,
      handoffs: config.handoffs as any,
      outputType: config.outputType,
      handoffOutputTypeWarningEnabled: false,
    });
  }

  static DEFAULT_MODEL_PLACEHOLDER = '';

  name: string;
  instructions:
    | string
    | ((
        runContext: RunContext<TContext>,
        agent: Agent<TContext, TOutput>,
      ) => Promise<string> | string);
  prompt?:
    | Prompt
    | ((
        runContext: RunContext<TContext>,
        agent: Agent<TContext, TOutput>,
      ) => Promise<Prompt> | Prompt);
  handoffDescription: string;
  handoffs: (Agent<any, TOutput> | Handoff<any, TOutput>)[];
  model: string | Model;
  modelSettings: ModelSettings;
  tools: Tool<TContext>[];
  mcpServers: MCPServer[];
  inputGuardrails: InputGuardrail[];
  outputGuardrails: OutputGuardrail<AgentOutputType>[];
  outputType: TOutput = 'text' as TOutput;
  toolUseBehavior: ToolUseBehavior;
  resetToolChoice: boolean;
  private readonly _toolsExplicitlyConfigured: boolean;

  constructor(config: AgentOptions<TContext, TOutput>) {
    super();
    if (typeof config.name !== 'string' || config.name.trim() === '') {
      throw new UserError('Agent must have a name.');
    }
    this.name = config.name;
    this.instructions = config.instructions ?? Agent.DEFAULT_MODEL_PLACEHOLDER;
    this.prompt = config.prompt;
    this.handoffDescription = config.handoffDescription ?? '';
    this.handoffs = config.handoffs ?? [];
    this.model = config.model ?? '';
    this.modelSettings = config.modelSettings ?? getDefaultModelSettings();
    this.tools = config.tools ?? [];
    this._toolsExplicitlyConfigured = config.tools !== undefined;
    this.mcpServers = config.mcpServers ?? [];
    this.inputGuardrails = config.inputGuardrails ?? [];
    this.outputGuardrails = config.outputGuardrails ?? [];
    if (config.outputType) {
      this.outputType = config.outputType;
    }
    this.toolUseBehavior = config.toolUseBehavior ?? 'run_llm_again';
    this.resetToolChoice = config.resetToolChoice ?? true;

    if (
      // The user sets a non-default model
      config.model !== undefined &&
      // The default model is gpt-5
      isGpt5Default() &&
      // However, the specified model is not a gpt-5 model
      (typeof config.model !== 'string' ||
        !gpt5ReasoningSettingsRequired(config.model)) &&
      // The model settings are not customized for the specified model
      config.modelSettings === undefined
    ) {
      // In this scenario, we should use a generic model settings
      // because non-gpt-5 models are not compatible with the default gpt-5 model settings.
      // This is a best-effort attempt to make the agent work with non-gpt-5 models.
      this.modelSettings = {};
    }

    // --- Runtime warning for handoff output type compatibility ---
    if (
      config.handoffOutputTypeWarningEnabled === undefined ||
      config.handoffOutputTypeWarningEnabled
    ) {
      if (this.handoffs && this.outputType) {
        const outputTypes = new Set<string>([JSON.stringify(this.outputType)]);
        for (const h of this.handoffs) {
          if ('outputType' in h && h.outputType) {
            outputTypes.add(JSON.stringify(h.outputType));
          } else if ('agent' in h && h.agent.outputType) {
            outputTypes.add(JSON.stringify(h.agent.outputType));
          }
        }
        if (outputTypes.size > 1) {
          logger.warn(
            `[Agent] Warning: Handoff agents have different output types: ${Array.from(outputTypes).join(', ')}. You can make it type-safe by using Agent.create({ ... }) method instead.`,
          );
        }
      }
    }
  }

  /**
   * Output schema name.
   */
  get outputSchemaName(): string {
    if (this.outputType === 'text') {
      return 'text';
    } else if (isZodObject(this.outputType)) {
      return 'ZodOutput';
    } else if (typeof this.outputType === 'object') {
      return this.outputType.name;
    }

    throw new Error(`Unknown output type: ${this.outputType}`);
  }

  /**
   * Makes a copy of the agent, with the given arguments changed. For example, you could do:
   *
   * ```
   * const newAgent = agent.clone({ instructions: 'New instructions' })
   * ```
   *
   * @param config - A partial configuration to change.
   * @returns A new agent with the given changes.
   */
  clone(
    config: Partial<AgentConfiguration<TContext, TOutput>>,
  ): Agent<TContext, TOutput> {
    return new Agent({
      ...this,
      ...config,
    });
  }

  /**
   * Transform this agent into a tool, callable by other agents.
   *
   * This is different from handoffs in two ways:
   * 1. In handoffs, the new agent receives the conversation history. In this tool, the new agent
   *    receives generated input.
   * 2. In handoffs, the new agent takes over the conversation. In this tool, the new agent is
   *    called as a tool, and the conversation is continued by the original agent.
   *
   * @param options - Options for the tool.
   * @returns A tool that runs the agent and returns the output text.
   */
  asTool<TAgent extends Agent<TContext, TOutput> = Agent<TContext, TOutput>>(
    this: TAgent,
    options: {
      /**
       * The name of the tool. If not provided, the name of the agent will be used.
       */
      toolName?: string;
      /**
       * The description of the tool, which should indicate what the tool does and when to use it.
       */
      toolDescription?: string;
      /**
       * A function that extracts the output text from the agent. If not provided, the last message
       * from the agent will be used.
       */
      customOutputExtractor?: (
        output: CompletedRunResult<TContext, TAgent>,
      ) => string | Promise<string>;
      /**
       * Whether invoking this tool requires approval, matching the behavior of {@link tool} helpers.
       * When provided as a function it receives the tool arguments and can implement custom approval
       * logic.
       */
      needsApproval?:
        | boolean
        | ToolApprovalFunction<typeof AgentAsToolNeedApprovalSchame>;
      /**
       * Run configuration for initializing the internal agent runner.
       */
      runConfig?: Partial<RunConfig>;
      /**
       * Additional run options for the agent (as tool) execution.
       */
      runOptions?: NonStreamRunOptions<TContext>;
      /**
       * Determines whether this tool should be exposed to the model for the current run.
       */
      isEnabled?:
        | boolean
        | ((args: {
            runContext: RunContext<TContext>;
            agent: Agent<TContext, TOutput>;
          }) => boolean | Promise<boolean>);
    },
  ): FunctionTool<TContext, typeof AgentAsToolNeedApprovalSchame> {
    const {
      toolName,
      toolDescription,
      customOutputExtractor,
      needsApproval,
      runConfig,
      runOptions,
      isEnabled,
    } = options;
    return tool({
      name: toolName ?? toFunctionToolName(this.name),
      description: toolDescription ?? '',
      parameters: AgentAsToolNeedApprovalSchame,
      strict: true,
      needsApproval,
      isEnabled,
      execute: async (data, context, details) => {
        if (!isAgentToolInput(data)) {
          throw new ModelBehaviorError('Agent tool called with invalid input');
        }
        const runner = new Runner(runConfig ?? {});
        const result = await runner.run(this, data.input, {
          context,
          ...(runOptions ?? {}),
        });
        const completedResult = result as CompletedRunResult<TContext, TAgent>;

        const usesStopAtToolNames =
          typeof this.toolUseBehavior === 'object' &&
          this.toolUseBehavior !== null &&
          'stopAtToolNames' in this.toolUseBehavior;

        if (
          typeof customOutputExtractor !== 'function' &&
          usesStopAtToolNames
        ) {
          logger.debug(
            `You're passing the agent (name: ${this.name}) with toolUseBehavior.stopAtToolNames configured as a tool to a different agent; this may not work as you expect. You may want to have a wrapper function tool to consistently return the final output.`,
          );
        }
        const outputText =
          typeof customOutputExtractor === 'function'
            ? await customOutputExtractor(completedResult)
            : getOutputText(
                completedResult.rawResponses[
                  completedResult.rawResponses.length - 1
                ],
              );

        if (details?.toolCall) {
          saveAgentToolRunResult(
            details.toolCall,
            completedResult as RunResult<any, Agent<any, any>>,
          );
        }
        return outputText;
      },
    });
  }

  /**
   * Returns the system prompt for the agent.
   *
   * If the agent has a function as its instructions, this function will be called with the
   * runContext and the agent instance.
   */
  async getSystemPrompt(
    runContext: RunContext<TContext>,
  ): Promise<string | undefined> {
    if (typeof this.instructions === 'function') {
      return await this.instructions(runContext, this);
    }

    return this.instructions;
  }

  /**
   * Returns the prompt template for the agent, if defined.
   *
   * If the agent has a function as its prompt, this function will be called with the
   * runContext and the agent instance.
   */
  async getPrompt(
    runContext: RunContext<TContext>,
  ): Promise<Prompt | undefined> {
    if (typeof this.prompt === 'function') {
      return await this.prompt(runContext, this);
    }
    return this.prompt;
  }

  /**
   * Fetches the available tools from the MCP servers.
   * @returns the MCP powered tools
   */
  async getMcpTools(
    runContext: RunContext<TContext>,
  ): Promise<Tool<TContext>[]> {
    if (this.mcpServers.length > 0) {
      return getAllMcpTools({
        mcpServers: this.mcpServers,
        runContext,
        agent: this,
        convertSchemasToStrict: false,
      });
    }

    return [];
  }

  /**
   * ALl agent tools, including the MCPl and function tools.
   *
   * @returns all configured tools
   */
  async getAllTools(
    runContext: RunContext<TContext>,
  ): Promise<Tool<TContext>[]> {
    const mcpTools = await this.getMcpTools(runContext);
    const enabledTools: Tool<TContext>[] = [];

    for (const candidate of this.tools) {
      if (candidate.type === 'function') {
        const maybeIsEnabled = (
          candidate as { isEnabled?: ToolEnabledFunction<TContext> | boolean }
        ).isEnabled;

        const enabled =
          typeof maybeIsEnabled === 'function'
            ? await maybeIsEnabled(runContext, this)
            : typeof maybeIsEnabled === 'boolean'
              ? maybeIsEnabled
              : true;
        if (!enabled) {
          continue;
        }
      }
      enabledTools.push(candidate);
    }

    return [...mcpTools, ...enabledTools];
  }

  hasExplicitToolConfig(): boolean {
    return this._toolsExplicitlyConfigured;
  }

  /**
   * Returns the handoffs that should be exposed to the model for the current run.
   *
   * Handoffs that provide an `isEnabled` function returning `false` are omitted.
   */
  async getEnabledHandoffs(
    runContext: RunContext<TContext>,
  ): Promise<Handoff<any, any>[]> {
    const handoffs = this.handoffs?.map((h) => getHandoff(h)) ?? [];
    const enabled: Handoff<any, any>[] = [];
    for (const handoff of handoffs) {
      if (await handoff.isEnabled({ runContext, agent: this })) {
        enabled.push(handoff);
      }
    }
    return enabled;
  }

  /**
   * Processes the final output of the agent.
   *
   * @param output - The output of the agent.
   * @returns The parsed out.
   */
  processFinalOutput(output: string): ResolvedAgentOutput<TOutput> {
    if (this.outputType === 'text') {
      return output as ResolvedAgentOutput<TOutput>;
    }

    if (typeof this.outputType === 'object') {
      const parsed = JSON.parse(output);

      if (isZodObject(this.outputType)) {
        return this.outputType.parse(parsed) as ResolvedAgentOutput<TOutput>;
      }

      return parsed as ResolvedAgentOutput<TOutput>;
    }

    throw new Error(`Unknown output type: ${this.outputType}`);
  }

  /**
   * Returns a JSON representation of the agent, which is serializable.
   *
   * @returns A JSON object containing the agent's name.
   */
  toJSON() {
    return {
      name: this.name,
    };
  }
}


## Links discovered
- [Model Context Protocol](https://modelcontextprotocol.io/)

--- packages/agents-core/test/agent.test.ts ---
import { describe, it, expect, vi, afterEach } from 'vitest';
import { Agent } from '../src/agent';
import { RunContext } from '../src/runContext';
import { Handoff, handoff } from '../src/handoff';
import { tool } from '../src/tool';
import { z } from 'zod';
import { JsonSchemaDefinition, setDefaultModelProvider } from '../src';
import { FakeModelProvider } from './stubs';
import { Runner, RunConfig } from '../src/run';
import logger from '../src/logger';

describe('Agent', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should create an agent with default values', () => {
    const agent = new Agent({ name: 'TestAgent' });

    expect(agent.name).toBe('TestAgent');
    expect(agent.instructions).toBe('');
    expect(agent.handoffDescription).toBe('');
    expect(agent.handoffs).toEqual([]);
    expect(agent.model).toBe('');
    expect(agent.modelSettings).toEqual({});
    expect(agent.tools).toEqual([]);
    expect(agent.mcpServers).toEqual([]);
    expect(agent.inputGuardrails).toEqual([]);
    expect(agent.outputGuardrails).toEqual([]);
    expect(agent.outputType).toBe('text');
    expect(agent.toolUseBehavior).toBe('run_llm_again');
    expect(agent.resetToolChoice).toBe(true);
  });

  it('should throw if name is missing', () => {
    expect(() => new Agent({} as any)).toThrow('Agent must have a name.');
    expect(() => new Agent({ name: '' } as any)).toThrow(
      'Agent must have a name.',
    );
  });

  it('should create an agent with provided values', () => {
    const agent = new Agent({
      name: 'CustomAgent',
      instructions: 'Custom instructions',
      handoffDescription: 'Custom handoff description',
      model: 'gpt-4',
      modelSettings: { temperature: 0.7 },
      outputType: 'text',
      toolUseBehavior: 'stop_on_first_tool',
      resetToolChoice: false,
    });

    expect(agent.name).toBe('CustomAgent');
    expect(agent.instructions).toBe('Custom instructions');
    expect(agent.handoffDescription).toBe('Custom handoff description');
    expect(agent.model).toBe('gpt-4');
    expect(agent.modelSettings).toEqual({ temperature: 0.7 });
    expect(agent.outputType).toBe('text');
    expect(agent.toolUseBehavior).toBe('stop_on_first_tool');
    expect(agent.resetToolChoice).toBe(false);
  });

  it('should clone an agent with modified values', () => {
    const originalAgent = new Agent({
      name: 'OriginalAgent',
      instructions: 'Original instructions',
    });

    const clonedAgent = originalAgent.clone({
      name: 'ClonedAgent',
      instructions: 'Cloned instructions',
    });

    expect(clonedAgent.name).toBe('ClonedAgent');
    expect(clonedAgent.instructions).toBe('Cloned instructions');
    expect(clonedAgent.handoffDescription).toBe(
      originalAgent.handoffDescription,
    );
    expect(clonedAgent.model).toBe(originalAgent.model);
    expect(clonedAgent.modelSettings).toEqual(originalAgent.modelSettings);
    expect(clonedAgent.outputType).toBe(originalAgent.outputType);
    expect(clonedAgent.toolUseBehavior).toBe(originalAgent.toolUseBehavior);
    expect(clonedAgent.resetToolChoice).toBe(originalAgent.resetToolChoice);
  });

  it('should return static instructions as system prompt', async () => {
    const agent = new Agent({
      name: 'StaticPromptAgent',
      instructions: 'Static instructions',
    });

    const prompt = await agent.getSystemPrompt(new RunContext({}));

    expect(prompt).toBe('Static instructions');
  });

  it('should return dynamic instructions as system prompt', async () => {
    const context = { value: 'test' };

    const agent = new Agent<typeof context>({
      name: 'DynamicPromptAgent',
      instructions: (runContext) =>
        `Dynamic instructions with context: ${runContext.context.value}`,
    });

    const prompt = await agent.getSystemPrompt(new RunContext(context));

    expect(prompt).toBe('Dynamic instructions with context: test');
  });

  it('should initialize with handoffs', async () => {
    const subAgent = new Agent({
      name: 'SubAgent',
      instructions: 'Sub instructions',
    });
    const agent1 = new Agent({
      name: 'StaticPromptAgent',
      instructions: 'Static instructions',
      handoffs: [subAgent],
    });
    expect(agent1.handoffs).toEqual([subAgent]);

    const agent2 = new Agent({
      name: 'StaticPromptAgent',
      instructions: 'Static instructions',
      handoffs: [handoff(subAgent)],
    });
    expect((agent2.handoffs[0] as Handoff).agent).toEqual(subAgent);
  });

  it('should handle Zod outputType properly', async () => {
    const foo = z.object({
      foo: z.string(),
    });
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: foo,
    });
    expect(agent.outputSchemaName).toBe('ZodOutput');
  });

  it('should handle JsonSchema outputType properly', async () => {
    const foo: JsonSchemaDefinition = {
      type: 'json_schema',
      name: 'Foo',
      strict: true,
      schema: {
        type: 'object',
        properties: {
          foo: { type: 'string' },
        },
        required: ['foo'],
        additionalProperties: false,
      },
    };
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: foo,
    });
    expect(agent.outputSchemaName).toBe('Foo');
  });

  it('should generate a tool from an agent', async () => {
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
    });
    const tool = agent.asTool({
      toolName: 'Test Agent Tool',
      toolDescription: 'You act as a tool.',
    });
    expect(tool.name).toEqual('Test_Agent_Tool');
    expect(tool.description).toEqual('You act as a tool.');

    const result1 = await tool.invoke({} as any, 'hey how are you?');
    expect(result1).toBe(
      'An error occurred while running the tool. Please try again. Error: Error: Invalid JSON input for tool',
    );
    setDefaultModelProvider(new FakeModelProvider());
    const result2 = await tool.invoke(
      {} as any,
      '{"input":"hey how are you?"}',
    );
    expect(result2).toBe('Hello World');
  });

  it('warns when using asTool with stopAtToolNames behavior without custom extractor', async () => {
    const warnSpy = vi.spyOn(logger, 'debug').mockImplementation(() => {});
    const runSpy = vi.spyOn(Runner.prototype, 'run').mockResolvedValue({
      rawResponses: [{ output: [] }],
    } as any);

    const agent = new Agent({
      name: 'Stopper Agent',
      instructions: 'Stop instructions.',
      toolUseBehavior: { stopAtToolNames: ['report'] },
    });

    const tool = agent.asTool({
      toolDescription: 'desc',
    });

    await tool.invoke(new RunContext(), '{"input":"value"}');

    expect(runSpy).toHaveBeenCalledTimes(1);
    expect(warnSpy).toHaveBeenCalledWith(
      `You're passing the agent (name: Stopper Agent) with toolUseBehavior.stopAtToolNames configured as a tool to a different agent; this may not work as you expect. You may want to have a wrapper function tool to consistently return the final output.`,
    );
  });

  it('allows configuring needsApproval when using an agent as a tool', async () => {
    const approval = vi.fn().mockResolvedValue(true);
    const agent = new Agent({
      name: 'Approver Agent',
      instructions: 'Check approvals.',
    });
    const tool = agent.asTool({
      toolDescription: 'desc',
      needsApproval: approval,
    });

    const rawArgs = { input: 'hello' };
    const decision = await tool.needsApproval(
      new RunContext(),
      rawArgs,
      'call-id',
    );

    expect(approval).toHaveBeenCalledWith(
      expect.any(RunContext),
      rawArgs,
      'call-id',
    );
    expect(decision).toBe(true);
  });

  it('passes runConfig and runOptions to the runner when used as a tool', async () => {
    const agent = new Agent({
      name: 'Configurable Agent',
      instructions: 'You do tests.',
    });
    const mockResult = {} as any;
    const runSpy = vi
      .spyOn(Runner.prototype, 'run')
      .mockImplementation(async () => mockResult);

    const runConfig: Partial<RunConfig> = {
      model: 'gpt-5',
      modelSettings: {
        reasoning: { effort: 'low' },
      },
    };
    const runOptions = {
      maxTurns: 3,
      previousResponseId: 'prev-response',
    };
    const customOutputExtractor = vi.fn().mockReturnValue('custom output');

    const tool = agent.asTool({
      toolDescription: 'You act as a tool.',
      runConfig,
      runOptions,
      customOutputExtractor,
    });

    const runContext = new RunContext({ locale: 'en-US' });
    const inputPayload = { input: 'translate this' };
    const result = await tool.invoke(runContext, JSON.stringify(inputPayload));

    expect(result).toBe('custom output');
    expect(customOutputExtractor).toHaveBeenCalledWith(mockResult);
    expect(runSpy).toHaveBeenCalledTimes(1);

    const [calledAgent, calledInput, calledOptions] = runSpy.mock.calls[0];
    expect(calledAgent).toBe(agent);
    expect(calledInput).toBe(inputPayload.input);
    expect(calledOptions).toMatchObject({
      context: runContext,
      maxTurns: runOptions.maxTurns,
      previousResponseId: runOptions.previousResponseId,
    });

    const runnerInstance = runSpy.mock.instances[0] as unknown as Runner;
    expect(runnerInstance.config.model).toBe(runConfig.model);
    expect(runnerInstance.config.modelSettings).toEqual(
      runConfig.modelSettings,
    );
  });

  it('filters tools using isEnabled predicates', async () => {
    const conditionalTool = tool({
      name: 'conditional',
      description: 'conditionally available',
      parameters: z.object({}),
      execute: async () => 'ok',
      isEnabled: ({
        runContext,
      }: {
        runContext: RunContext<unknown>;
        agent: Agent<any, any>;
      }) => (runContext.context as { allowed: boolean }).allowed,
    });
    const agent = new Agent<{ allowed: boolean }>({
      name: 'Conditional Agent',
      instructions: 'test',
      tools: [conditionalTool],
    });

    const disabledTools = await agent.getAllTools(
      new RunContext({ allowed: false }),
    );
    expect(disabledTools).toEqual([]);

    const enabledTools = await agent.getAllTools(
      new RunContext({ allowed: true }),
    );
    expect(enabledTools.map((t) => t.name)).toEqual(['conditional']);
  });

  it('respects isEnabled option on Agent.asTool', async () => {
    const nestedAgent = new Agent({
      name: 'Nested',
      instructions: 'nested',
    });
    const nestedTool = nestedAgent.asTool({
      toolDescription: 'nested',
      isEnabled: ({
        runContext,
        agent,
      }: {
        runContext: RunContext<unknown>;
        agent: Agent<any, any>;
      }) => {
        expect(agent).toBe(hostAgent);
        return (runContext.context as { enabled: boolean }).enabled;
      },
    });

    const hostAgent = new Agent<{ enabled: boolean }>({
      name: 'Host',
      instructions: 'host',
      tools: [nestedTool],
    });

    const disabled = await hostAgent.getAllTools(
      new RunContext({ enabled: false }),
    );
    expect(disabled).toEqual([]);

    const enabled = await hostAgent.getAllTools(
      new RunContext({ enabled: true }),
    );
    expect(enabled.map((t) => t.name)).toEqual([nestedTool.name]);
  });

  it('enables agent tools based on language preference predicates', async () => {
    type LanguagePreference = 'spanish_only' | 'french_spanish' | 'european';

    type AppContext = {
      languagePreference: LanguagePreference;
    };

    const spanishAgent = new Agent<AppContext>({
      name: 'spanish_agent',
      instructions: 'Always respond in Spanish.',
    });

    const frenchAgent = new Agent<AppContext>({
      name: 'french_agent',
      instructions: 'Always respond in French.',
    });

    const italianAgent = new Agent<AppContext>({
      name: 'italian_agent',
      instructions: 'Always respond in Italian.',
    });

    const orchestrator = new Agent<AppContext>({
      name: 'orchestrator',
      instructions: 'Use language specialists.',
      tools: [
        spanishAgent.asTool({
          toolName: 'respond_spanish',
          toolDescription: 'Respond in Spanish.',
          isEnabled: true,
        }),
        frenchAgent.asTool({
          toolName: 'respond_french',
          toolDescription: 'Respond in French.',
          isEnabled: ({ runContext }) =>
            ['french_spanish', 'european'].includes(
              runContext.context.languagePreference,
            ),
        }),
        italianAgent.asTool({
          toolName: 'respond_italian',
          toolDescription: 'Respond in Italian.',
          isEnabled: ({ runContext }) =>
            runContext.context.languagePreference === 'european',
        }),
      ],
    });

    const collect = async (preference: LanguagePreference) =>
      (
        await orchestrator.getAllTools(
          new RunContext<AppContext>({ languagePreference: preference }),
        )
      ).map((toolInstance) => toolInstance.name);

    await expect(collect('spanish_only')).resolves.toEqual(['respond_spanish']);
    await expect(collect('french_spanish')).resolves.toEqual([
      'respond_spanish',
      'respond_french',
    ]);
    await expect(collect('european')).resolves.toEqual([
      'respond_spanish',
      'respond_french',
      'respond_italian',
    ]);
  });

  it('should process final output (text)', async () => {
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: 'text',
    });
    const result1 = agent.processFinalOutput('Hi, how are you?');
    expect(result1).toBe('Hi, how are you?');
  });
  it('should process final output (zod)', async () => {
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: z.object({ message: z.string() }),
    });
    const result1 = agent.processFinalOutput('{"message": "Hi, how are you?"}');
    expect(result1).toEqual({ message: 'Hi, how are you?' });
  });
  it('should process final output (json schema)', async () => {
    const agent = new Agent({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: {
        type: 'json_schema',
        name: 'TestOutput',
        strict: true,
        schema: {
          type: 'object',
          properties: {
            message: { type: 'string' },
          },
          required: ['message'],
          additionalProperties: false,
        },
      },
    });
    const result1 = agent.processFinalOutput('{"message": "Hi, how are you?"}');
    expect(result1).toEqual({ message: 'Hi, how are you?' });
  });
  it('should create an instance using create method', async () => {
    const agent = Agent.create({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: z.object({ message: z.string() }),
    });
    const result1 = agent.processFinalOutput('{"message": "Hi, how are you?"}');
    expect(result1).toEqual({ message: 'Hi, how are you?' });
  });
  it('should create an instance using create method + handoffs', async () => {
    const agent = Agent.create({
      name: 'Test Agent',
      instructions: 'You do tests.',
      outputType: z.object({ message: z.string() }),
      handoffs: [
        Agent.create({
          name: 'Test Agent 2',
          instructions: 'You do tests.',
          outputType: z.object({ message: z.string() }),
        }),
      ],
    });
    const result1 = agent.processFinalOutput('{"message": "Hi, how are you?"}');
    expect(result1).toEqual({ message: 'Hi, how are you?' });
  });
});


--- packages/agents-realtime/src/clientMessages.ts ---
import {
  JsonObjectSchema,
  ModelSettingsToolChoice,
  Prompt,
} from '@openai/agents-core/types';

export type RealtimeClientMessage = {
  type: string;
  [key: string]: any;
};

export type RealtimeUserInput =
  | string
  | {
      type: 'message';
      role: 'user';
      content: (
        | {
            type: 'input_text';
            text: string;
          }
        | {
            type: 'input_image';
            image: string;
            providerData?: Record<string, any>;
          }
      )[];
    };

export type RealtimeAudioFormatDefinition =
  | { type: 'audio/pcm'; rate: number }
  | { type: 'audio/pcmu' }
  | { type: 'audio/pcma' };

// Legacy format (deprecated): string shorthands
// - 'pcm16' (equivalent to { type: 'audio/pcm', rate: 24000 })
// - 'g711_ulaw' (equivalent to { type: 'audio/pcmu' })
// - 'g711_alaw' (equivalent to { type: 'audio/pcma' })
/**
 * @deprecated Use a {type: "audio/pcm"} format instead. String shorthands are deprecated.
 */
export type RealtimeAudioFormatLegacy =
  | 'pcm16'
  | 'g711_ulaw'
  | 'g711_alaw'
  | (string & {});

// User-facing union (legacy accepted, GA preferred)
export type RealtimeAudioFormat =
  | RealtimeAudioFormatLegacy
  | RealtimeAudioFormatDefinition;

export type RealtimeTracingConfig =
  | {
      workflow_name?: string;
      group_id?: string;
      metadata?: Record<string, any>;
    }
  | 'auto';

export type RealtimeInputAudioNoiseReductionConfig = {
  type: 'near_field' | 'far_field' | (string & {});
};

export type RealtimeInputAudioTranscriptionConfig = {
  language?: string;
  model?:
    | 'gpt-4o-transcribe'
    | 'gpt-4o-mini-transcribe'
    | 'whisper-1'
    | (string & {});
  prompt?: string;
};

export type RealtimeTurnDetectionConfigAsIs = {
  type?: 'semantic_vad' | 'server_vad' | (string & {});
  create_response?: boolean;
  eagerness?: 'auto' | 'low' | 'medium' | 'high';
  interrupt_response?: boolean;
  prefix_padding_ms?: number;
  silence_duration_ms?: number;
  threshold?: number;
  idle_timeout_ms?: number;
};

// The Realtime API accepts snake_cased keys, so when using this, this SDK converts the keys to snake_case ones before passing it to the API.
export type RealtimeTurnDetectionConfigCamelCase = {
  type?: 'semantic_vad' | 'server_vad' | (string & {});
  createResponse?: boolean;
  eagerness?: 'auto' | 'low' | 'medium' | 'high';
  interruptResponse?: boolean;
  prefixPaddingMs?: number;
  silenceDurationMs?: number;
  threshold?: number;
  idleTimeoutMs?: number;
};

export type RealtimeTurnDetectionConfig = (
  | RealtimeTurnDetectionConfigAsIs
  | RealtimeTurnDetectionConfigCamelCase
) &
  Record<string, any>;

export type RealtimeAudioInputConfig = {
  format?: RealtimeAudioFormat;
  noiseReduction?: RealtimeInputAudioNoiseReductionConfig | null;
  transcription?: RealtimeInputAudioTranscriptionConfig;
  turnDetection?: RealtimeTurnDetectionConfig;
};

export type RealtimeAudioOutputConfig = {
  format?: RealtimeAudioFormat;
  voice?: string;
  speed?: number;
};

export type RealtimeAudioConfig = {
  input?: RealtimeAudioInputConfig;
  output?: RealtimeAudioOutputConfig;
};

// Shared/common fields across both config shapes
export type RealtimeSessionConfigCommon = {
  model: string;
  instructions: string;
  toolChoice: ModelSettingsToolChoice;
  tools: RealtimeToolDefinition[];
  tracing?: RealtimeTracingConfig | null;
  providerData?: Record<string, any>;
  prompt?: Prompt;
};

export type RealtimeSessionConfigDefinition = RealtimeSessionConfigCommon & {
  outputModalities?: ('text' | 'audio')[];
  audio?: RealtimeAudioConfig;
  /**
   * TODO: We'll eventually migrate to audio.output.voice instead of this property.
   * Until we fully migrate to audio.output.voice for all session implementations,
   * using this top-level voice property helps with backwards compatibility.
   */
  voice?: string;
};

// Deprecated config (legacy) — cannot be mixed with new fields
export type RealtimeSessionConfigDeprecated = RealtimeSessionConfigCommon & {
  /** @deprecated Use outputModalities instead. */
  modalities: ('text' | 'audio')[];
  /** @deprecated Use audio.output.voice instead. */
  voice: string;
  /** @deprecated Use audio.input.format instead. */
  inputAudioFormat: RealtimeAudioFormatLegacy;
  /** @deprecated Use audio.output.format instead. */
  outputAudioFormat: RealtimeAudioFormatLegacy;
  /** @deprecated Use audio.input.transcription instead. */
  inputAudioTranscription: RealtimeInputAudioTranscriptionConfig;
  /** @deprecated Use audio.input.turnDetection instead. */
  turnDetection: RealtimeTurnDetectionConfig;
  /** @deprecated Use audio.input.noiseReduction instead. */
  inputAudioNoiseReduction: RealtimeInputAudioNoiseReductionConfig | null;
  /** @deprecated Use audio.output.speed instead. */
  speed: number;
};

// Union of configs; users should not mix-and-match; runtime converter will normalize
export type RealtimeSessionConfig =
  | RealtimeSessionConfigDefinition
  | RealtimeSessionConfigDeprecated;

function isDefined(
  key:
    | keyof RealtimeSessionConfigDefinition
    | keyof RealtimeSessionConfigDeprecated,
  object: Partial<RealtimeSessionConfig>,
) {
  // @ts-expect-error fudging with types here for the index types
  return key in object && typeof object[key] !== 'undefined';
}

function isDeprecatedConfig(
  config: Partial<RealtimeSessionConfig>,
): config is Partial<RealtimeSessionConfigDeprecated> {
  return (
    isDefined('modalities', config) ||
    isDefined('inputAudioFormat', config) ||
    isDefined('outputAudioFormat', config) ||
    isDefined('inputAudioTranscription', config) ||
    isDefined('turnDetection', config) ||
    isDefined('inputAudioNoiseReduction', config) ||
    isDefined('speed', config)
  );
}

/**
 * Convert any given config (old or new) to the new GA config shape.
 * If a new config is provided, it will be returned as-is (normalized shallowly).
 */
export function toNewSessionConfig(
  config: Partial<RealtimeSessionConfig>,
): Partial<RealtimeSessionConfigDefinition> {
  if (!isDeprecatedConfig(config)) {
    const inputConfig = config.audio?.input
      ? {
          format: normalizeAudioFormat(config.audio.input.format),
          noiseReduction: config.audio.input.noiseReduction ?? null,
          transcription: config.audio.input.transcription,
          turnDetection: config.audio.input.turnDetection,
        }
      : undefined;

    const requestedOutputVoice = config.audio?.output?.voice ?? config.voice;
    const outputConfig =
      config.audio?.output || typeof requestedOutputVoice !== 'undefined'
        ? {
            format: normalizeAudioFormat(config.audio?.output?.format),
            voice: requestedOutputVoice,
            speed: config.audio?.output?.speed,
          }
        : undefined;

    return {
      model: config.model,
      instructions: config.instructions,
      toolChoice: config.toolChoice,
      tools: config.tools,
      tracing: config.tracing,
      providerData: config.providerData,
      prompt: config.prompt,
      outputModalities: config.outputModalities,
      audio:
        inputConfig || outputConfig
          ? {
              input: inputConfig,
              output: outputConfig,
            }
          : undefined,
    };
  }

  return {
    model: config.model,
    instructions: config.instructions,
    toolChoice: config.toolChoice,
    tools: config.tools,
    tracing: config.tracing,
    providerData: config.providerData,
    prompt: config.prompt,
    outputModalities: config.modalities,
    audio: {
      input: {
        format: normalizeAudioFormat(config.inputAudioFormat),
        noiseReduction: config.inputAudioNoiseReduction ?? null,
        transcription: config.inputAudioTranscription,
        turnDetection: config.turnDetection,
      },
      output: {
        format: normalizeAudioFormat(config.outputAudioFormat),
        voice: config.voice,
        speed: config.speed,
      },
    },
  };
}

export function normalizeAudioFormat(
  format?: RealtimeAudioFormat | undefined,
): RealtimeAudioFormatDefinition | undefined {
  if (!format) return undefined;
  if (typeof format === 'object')
    return format as RealtimeAudioFormatDefinition;
  const f = String(format);
  if (f === 'pcm16') return { type: 'audio/pcm', rate: 24000 };
  if (f === 'g711_ulaw') return { type: 'audio/pcmu' };
  if (f === 'g711_alaw') return { type: 'audio/pcma' };
  // Default fallback: assume 24kHz PCM if unknown string
  return { type: 'audio/pcm', rate: 24000 };
}

export type FunctionToolDefinition = {
  type: 'function';
  name: string;
  description: string;
  parameters: JsonObjectSchema<any>;
  strict: boolean;
};

export type HostedToolFilter = {
  tool_names?: string[];
};

// TODO unify this with the core types
export type HostedMCPToolDefinition = {
  type: 'mcp';
  server_label: string;
  server_url?: string;
  headers?: Record<string, string>;
  allowed_tools?: string[] | HostedToolFilter;
  require_approval?:
    | 'never'
    | 'always'
    | {
        never?: HostedToolFilter;
        always?: HostedToolFilter;
      };
};

export type RealtimeToolDefinition =
  | FunctionToolDefinition
  | HostedMCPToolDefinition;

// Describes a tool as returned by an MCP server (via mcp_list_tools).
// Shape mirrors the realtime event payload (with room for extensions).
export type RealtimeMcpToolInfo = {
  name: string;
  description?: string;
  input_schema?: Record<string, any>;
  [key: string]: any;
};


--- packages/agents-extensions/src/CloudflareRealtimeTransport.ts ---
import {
  RealtimeTransportLayer,
  OpenAIRealtimeWebSocket,
  OpenAIRealtimeWebSocketOptions,
} from '@openai/agents/realtime';

/**
 * An adapter transport for Cloudflare Workers (workerd) environments.
 *
 * Cloudflare Workers cannot open outbound client WebSockets using the global `WebSocket`
 * constructor. Instead, a `fetch()` request with `Upgrade: websocket` must be performed and the
 * returned `response.webSocket` must be `accept()`ed. This transport encapsulates that pattern and
 * plugs into the Realtime SDK via the factory-based `createWebSocket` option.
 *
 * It behaves like `OpenAIRealtimeWebSocket`, but establishes the connection using `fetch()` and
 * sets `skipOpenEventListeners: true` since workerd sockets do not emit a traditional `open`
 * event after acceptance.
 *
 * Reference: Response API — `response.webSocket` (Cloudflare Workers).
 * https://developers.cloudflare.com/workers/runtime-apis/response/.
 */
export class CloudflareRealtimeTransportLayer
  extends OpenAIRealtimeWebSocket
  implements RealtimeTransportLayer
{
  protected _audioLengthMs: number = 0;

  constructor(options: OpenAIRealtimeWebSocketOptions) {
    super({
      ...options,
      createWebSocket: async ({ url, apiKey }) => {
        return await this.#buildCloudflareWebSocket({ url, apiKey });
      },
      skipOpenEventListeners: true,
    });
  }

  /**
   * Builds a WebSocket using Cloudflare's `fetch()` + `Upgrade: websocket` flow and accepts it.
   * Transforms `ws(s)` to `http(s)` for the upgrade request and forwards standard headers.
   */
  async #buildCloudflareWebSocket({
    url,
    apiKey,
  }: {
    url: string;
    apiKey: string;
  }): Promise<WebSocket> {
    const transformedUrl = url.replace(/^ws/i, 'http');
    if (!transformedUrl) {
      throw new Error('Realtime URL is not defined');
    }

    const response = await fetch(transformedUrl, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Sec-WebSocket-Protocol': 'realtime',
        Connection: 'Upgrade',
        Upgrade: 'websocket',
        ...this.getCommonRequestHeaders(),
      },
    });

    const upgradedSocket = (response as any).webSocket;
    if (!upgradedSocket) {
      const body = await response.text().catch(() => '');
      throw new Error(
        `Failed to upgrade websocket: ${response.status} ${body}`,
      );
    }

    upgradedSocket.accept();
    return upgradedSocket as unknown as WebSocket;
  }
}
