# Table of Contents - [Sessions | OpenAI Agents SDK](#sessions-openai-agents-sdk) - [OpenAI Agents SDK TypeScript | OpenAI Agents SDK](#openai-agents-sdk-typescript-openai-agents-sdk) - [Orchestrating multiple agents | OpenAI Agents SDK](#orchestrating-multiple-agents-openai-agents-sdk) - [Quickstart | OpenAI Agents SDK](#quickstart-openai-agents-sdk) - [Handoffs | OpenAI Agents SDK](#handoffs-openai-agents-sdk) - [Context management | OpenAI Agents SDK](#context-management-openai-agents-sdk) - [Results | OpenAI Agents SDK](#results-openai-agents-sdk) - [Agents | OpenAI Agents SDK](#agents-openai-agents-sdk) - [Tools | OpenAI Agents SDK](#tools-openai-agents-sdk) - [Models | OpenAI Agents SDK](#models-openai-agents-sdk) - [Running agents | OpenAI Agents SDK](#running-agents-openai-agents-sdk) - [Streaming | OpenAI Agents SDK](#streaming-openai-agents-sdk) - [Configuring the SDK | OpenAI Agents SDK](#configuring-the-sdk-openai-agents-sdk) - [Troubleshooting | OpenAI Agents SDK](#troubleshooting-openai-agents-sdk) - [Guardrails | OpenAI Agents SDK](#guardrails-openai-agents-sdk) - [Release process | OpenAI Agents SDK](#release-process-openai-agents-sdk) - [Tracing | OpenAI Agents SDK](#tracing-openai-agents-sdk) - [Voice Agents | OpenAI Agents SDK](#voice-agents-openai-agents-sdk) - [Model Context Protocol (MCP) | OpenAI Agents SDK](#model-context-protocol-mcp-openai-agents-sdk) - [Voice Agents Quickstart | OpenAI Agents SDK](#voice-agents-quickstart-openai-agents-sdk) - [Using any model with the Vercel's AI SDK | OpenAI Agents SDK](#using-any-model-with-the-vercel-s-ai-sdk-openai-agents-sdk) - [Realtime Transport Layer | OpenAI Agents SDK](#realtime-transport-layer-openai-agents-sdk) - [Human in the loop | OpenAI Agents SDK](#human-in-the-loop-openai-agents-sdk) - [Connect Realtime Agents to Twilio | OpenAI Agents SDK](#connect-realtime-agents-to-twilio-openai-agents-sdk) - [Building Voice Agents | OpenAI Agents SDK](#building-voice-agents-openai-agents-sdk) --- # Sessions | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/sessions/#_top) Sessions ======== Sessions give the Agents SDK a **persistent memory layer**. Provide any object that implements the `Session` interface to `Runner.run`, and the SDK handles the rest. When a session is present, the runner automatically: 1. Fetches previously stored conversation items and prepends them to the next turn. 2. Persists new user input and assistant output after each run completes. 3. Keeps the session available for future turns, whether you call the runner with new user text or resume from an interrupted `RunState`. This removes the need to manually call `toInputList()` or stitch history between turns. The TypeScript SDK ships with two implementations: `OpenAIConversationsSession` for the Conversations API and `MemorySession`, which is intended for local development. Because they share the `Session` interface, you can plug in your own storage backend. For inspiration beyond the Conversations API, explore the sample session backends under `examples/memory/` (Prisma, file-backed, and more). When you use an OpenAI Responses model, wrap any session with `OpenAIResponsesCompactionSession` to automatically shrink stored transcripts via [`responses.compact`](https://platform.openai.com/docs/api-reference/responses/compact) . > Tip: To run the `OpenAIConversationsSession` examples on this page, set the `OPENAI_API_KEY` environment variable (or provide an `apiKey` when constructing the session) so the SDK can call the Conversations API. * * * Quick start ----------- [Section titled “Quick start”](https://openai.github.io/openai-agents-js/guides/sessions/#quick-start) Use `OpenAIConversationsSession` to sync memory with the [Conversations API](https://platform.openai.com/docs/api-reference/conversations) , or swap in any other `Session` implementation. import { Agent, OpenAIConversationsSession, run } from '@openai/agents'; const agent = new Agent({ name: 'TourGuide', instructions: 'Answer with compact travel facts.',}); // Any object that implements the Session interface works here. This example uses// the built-in OpenAIConversationsSession, but you can swap in a custom Session.const session = new OpenAIConversationsSession(); const firstTurn = await run(agent, 'What city is the Golden Gate Bridge in?', { session,});console.log(firstTurn.finalOutput); // "San Francisco" const secondTurn = await run(agent, 'What state is it in?', { session });console.log(secondTurn.finalOutput); // "California" Reusing the same session instance ensures the agent receives the full conversation history before every turn and automatically persists new items. Switching to a different `Session` implementation requires no other code changes. * * * How the runner uses sessions ---------------------------- [Section titled “How the runner uses sessions”](https://openai.github.io/openai-agents-js/guides/sessions/#how-the-runner-uses-sessions) * **Before each run** it retrieves the session history, merges it with the new turn’s input, and passes the combined list to your agent. * **After a non-streaming run** one call to `session.addItems()` persists both the original user input and the model outputs from the latest turn. * **For streaming runs** it writes the user input first and appends streamed outputs once the turn completes. * **When resuming from `RunResult.state`** (for approvals or other interruptions) keep passing the same `session`. The resumed turn is added to memory without re-preparing the input. * * * Inspecting and editing history ------------------------------ [Section titled “Inspecting and editing history”](https://openai.github.io/openai-agents-js/guides/sessions/#inspecting-and-editing-history) Sessions expose simple CRUD helpers so you can build “undo”, “clear chat”, or audit features. import { OpenAIConversationsSession } from '@openai/agents';import type { AgentInputItem } from '@openai/agents-core'; // Replace OpenAIConversationsSession with any other Session implementation that// supports get/add/pop/clear if you store history elsewhere.const session = new OpenAIConversationsSession({ conversationId: 'conv_123', // Resume an existing conversation if you have one.}); const history = await session.getItems();console.log(`Loaded ${history.length} prior items.`); const followUp: AgentInputItem[] = [ { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'Let’s continue later.' }], },];await session.addItems(followUp); const undone = await session.popItem(); if (undone?.type === 'message') { console.log(undone.role); // "user"} await session.clearSession(); `session.getItems()` returns the stored `AgentInputItem[]`. Call `popItem()` to remove the last entry—useful for user corrections before you rerun the agent. * * * Bring your own storage ---------------------- [Section titled “Bring your own storage”](https://openai.github.io/openai-agents-js/guides/sessions/#bring-your-own-storage) Implement the `Session` interface to back memory with Redis, DynamoDB, SQLite, or another datastore. Only five asynchronous methods are required. import { Agent, run } from '@openai/agents';import { randomUUID } from '@openai/agents-core/_shims';import { logger, Logger } from '@openai/agents-core/dist/logger';import type { AgentInputItem, Session } from '@openai/agents-core'; /** * Minimal example of a Session implementation; swap this class for any storage-backed version. */export class CustomMemorySession implements Session { private readonly sessionId: string; private readonly logger: Logger; private items: AgentInputItem[]; constructor( options: { sessionId?: string; initialItems?: AgentInputItem[]; logger?: Logger; } = {}, ) { this.sessionId = options.sessionId ?? randomUUID(); this.items = options.initialItems ? options.initialItems.map(cloneAgentItem) : []; this.logger = options.logger ?? logger; } async getSessionId(): Promise { return this.sessionId; } async getItems(limit?: number): Promise { if (limit === undefined) { const cloned = this.items.map(cloneAgentItem); this.logger.debug( `Getting items from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); return cloned; } if (limit <= 0) { return []; } const start = Math.max(this.items.length - limit, 0); const items = this.items.slice(start).map(cloneAgentItem); this.logger.debug( `Getting items from memory session (${this.sessionId}): ${JSON.stringify(items)}`, ); return items; } async addItems(items: AgentInputItem[]): Promise { if (items.length === 0) { return; } const cloned = items.map(cloneAgentItem); this.logger.debug( `Adding items to memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); this.items = [...this.items, ...cloned]; } async popItem(): Promise { if (this.items.length === 0) { return undefined; } const item = this.items[this.items.length - 1]; const cloned = cloneAgentItem(item); this.logger.debug( `Popping item from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`, ); this.items = this.items.slice(0, -1); return cloned; } async clearSession(): Promise { this.logger.debug(`Clearing memory session (${this.sessionId})`); this.items = []; }} function cloneAgentItem(item: T): T { return structuredClone(item);} const agent = new Agent({ name: 'MemoryDemo', instructions: 'Remember the running total.',}); // Using the above custom memory session implementation hereconst session = new CustomMemorySession({ sessionId: 'session-123-4567',}); const first = await run(agent, 'Add 3 to the total.', { session });console.log(first.finalOutput); const second = await run(agent, 'Add 4 more.', { session });console.log(second.finalOutput); Custom sessions let you enforce retention policies, add encryption, or attach metadata to each conversation turn before persisting it. * * * Control how history and new items merge --------------------------------------- [Section titled “Control how history and new items merge”](https://openai.github.io/openai-agents-js/guides/sessions/#control-how-history-and-new-items-merge) When you pass an array of `AgentInputItem`s as the run input, provide a `sessionInputCallback` to merge them with stored history deterministically. The runner loads the existing history, calls your callback **before the model invocation**, and hands the returned array to the model as the turn’s complete input. This hook is ideal for trimming old items, deduplicating tool results, or highlighting only the context you want the model to see. import { Agent, OpenAIConversationsSession, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents-core'; const agent = new Agent({ name: 'Planner', instructions: 'Track outstanding tasks before responding.',}); // Any Session implementation can be passed here; customize storage as needed.const session = new OpenAIConversationsSession(); const todoUpdate: AgentInputItem[] = [ { type: 'message', role: 'user', content: [ { type: 'input_text', text: 'Add booking a hotel to my todo list.' }, ], },]; await run(agent, todoUpdate, { session, // function that combines session history with new input items before the model call sessionInputCallback: (history, newItems) => { const recentHistory = history.slice(-8); return [...recentHistory, ...newItems]; },}); For string inputs the runner merges history automatically, so the callback is optional. * * * Handling approvals and resumable runs ------------------------------------- [Section titled “Handling approvals and resumable runs”](https://openai.github.io/openai-agents-js/guides/sessions/#handling-approvals-and-resumable-runs) Human-in-the-loop flows often pause a run to wait for approval: const result = await runner.run(agent, 'Search the itinerary', { session, stream: true,}); if (result.requiresApproval) { // ... collect user feedback, then resume the agent in a later turn const continuation = await runner.run(agent, result.state, { session }); console.log(continuation.finalOutput);} When you resume from a previous `RunState`, the new turn is appended to the same memory record to preserve a single conversation history. Human-in-the-loop (HITL) flows stay fully compatible—approval checkpoints still round-trip through `RunState` while the session keeps the transcript complete. * * * Compact OpenAI Responses history automatically ---------------------------------------------- [Section titled “Compact OpenAI Responses history automatically”](https://openai.github.io/openai-agents-js/guides/sessions/#compact-openai-responses-history-automatically) `OpenAIResponsesCompactionSession` decorates any `Session` and relies on the OpenAI Responses API to keep transcripts short. After each persisted turn the runner passes the latest `responseId` into `runCompaction`, which calls `responses.compact` when your decision hook returns true. The default trigger compacts once at least 10 non-user items have accumulated; override `shouldTriggerCompaction` to base the decision on token counts or custom heuristics. The decorator clears and rewrites the underlying session with the compacted output, so avoid pairing it with `OpenAIConversationsSession`, which uses a different server-managed history flow. import { Agent, MemorySession, OpenAIResponsesCompactionSession, run,} from '@openai/agents'; const agent = new Agent({ name: 'Support', instructions: 'Answer briefly and keep track of prior context.', model: 'gpt-5.2',}); // Wrap any Session to trigger responses.compact once history grows beyond your threshold.const session = new OpenAIResponsesCompactionSession({ // You can pass any Session implementation except OpenAIConversationsSession underlyingSession: new MemorySession(), // (optional) The model used for calling responses.compact API model: 'gpt-5.2', // (optional) your custom logic here shouldTriggerCompaction: ({ compactionCandidateItems }) => { return compactionCandidateItems.length >= 12; },}); await run(agent, 'Summarize order #8472 in one sentence.', { session });await run(agent, 'Remind me of the shipping address.', { session }); // Compaction runs automatically after each persisted turn. You can also force it manually.await session.runCompaction({ force: true }); You can call `runCompaction({ force: true })` at any time to shrink history before archiving or handoff. Enable debug logs with `DEBUG=openai-agents:openai:compaction` to trace compaction decisions. --- # OpenAI Agents SDK TypeScript | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/#_top) OpenAI Agents SDK TypeScript ============================ Quickstart ---------- Build your first agent in minutes. [Let’s build](https://openai.github.io/openai-agents-js/guides/quickstart) * [Text Agent](https://openai.github.io/openai-agents-js/#tab-panel-4) * [Voice Agent](https://openai.github.io/openai-agents-js/#tab-panel-5) 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); import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Assistant', instructions: 'You are a helpful assistant.',}); // Automatically connects your microphone and audio output in the browser via WebRTC.const session = new RealtimeSession(agent);await session.connect({ apiKey: '',}); Overview -------- [Section titled “Overview”](https://openai.github.io/openai-agents-js/#overview) The [OpenAI Agents SDK for TypeScript](https://github.com/openai/openai-agents-js) enables you to build agentic AI apps in a lightweight, easy-to-use package with very few abstractions. It’s a production-ready upgrade of our previous experimentation for agents, [Swarm](https://github.com/openai/swarm/tree/main) , that’s also [available in Python](https://github.com/openai/openai-agents-python) . The Agents SDK has a very small set of primitives: * **Agents**, which are LLMs equipped with instructions and tools * **Handoffs**, which allow agents to delegate to other agents for specific tasks * **Guardrails**, which enable the inputs to agents to be validated In combination with TypeScript, these primitives are powerful enough to express complex relationships between tools and agents, and allow you to build real-world applications without a steep learning curve. In addition, the SDK comes with built-in **tracing** that lets you visualize and debug your agentic flows, as well as evaluate them and even fine-tune models for your application. Why use the Agents SDK ---------------------- [Section titled “Why use the Agents SDK”](https://openai.github.io/openai-agents-js/#why-use-the-agents-sdk) The SDK has two driving design principles: 1. Enough features to be worth using, but few enough primitives to make it quick to learn. 2. Works great out of the box, but you can customize exactly what happens. Here are the main features of the SDK: * **Agent loop**: Built-in agent loop that handles calling tools, sending results to the LLM, and looping until the LLM is done. * **TypeScript-first**: Use built-in language features to orchestrate and chain agents, rather than needing to learn new abstractions. * **Handoffs**: A powerful feature to coordinate and delegate between multiple agents. * **Guardrails**: Run input validations and checks in parallel to your agents, breaking early if the checks fail. * **Function tools**: Turn any TypeScript function into a tool, with automatic schema generation and Zod-powered validation. * **Tracing**: Built-in tracing that lets you visualize, debug and monitor your workflows, as well as use the OpenAI suite of evaluation, fine-tuning and distillation tools. * **Realtime Agents**: Build powerful voice agents including automatic interruption detection, context management, guardrails, and more. Installation ------------ [Section titled “Installation”](https://openai.github.io/openai-agents-js/#installation) npm install @openai/agents zod@3 Hello world example ------------------- [Section titled “Hello world example”](https://openai.github.io/openai-agents-js/#hello-world-example) 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_) export OPENAI_API_KEY=sk-... --- # Orchestrating multiple agents | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/multi-agent/#_top) Orchestrating multiple agents ============================= Orchestration refers to the flow of agents in your app. Which agents run, in what order, and how do they decide what happens next? There are two main ways to orchestrate agents: 1. Allowing the LLM to make decisions: this uses the intelligence of an LLM to plan, reason, and decide on what steps to take based on that. 2. Orchestrating via code: determining the flow of agents via your code. You can mix and match these patterns. Each has their own tradeoffs, described below. Orchestrating via LLM --------------------- [Section titled “Orchestrating via LLM”](https://openai.github.io/openai-agents-js/guides/multi-agent/#orchestrating-via-llm) An agent is an LLM equipped with instructions, tools and handoffs. This means that given an open-ended task, the LLM can autonomously plan how it will tackle the task, using tools to take actions and acquire data, and using handoffs to delegate tasks to sub-agents. For example, a research agent could be equipped with tools like: * Web search to find information online * File search and retrieval to search through proprietary data and connections * Computer use to take actions on a computer * Code execution to do data analysis * Handoffs to specialized agents that are great at planning, report writing and more. This pattern is great when the task is open-ended and you want to rely on the intelligence of an LLM. The most important tactics here are: 1. Invest in good prompts. Make it clear what tools are available, how to use them, and what parameters it must operate within. 2. Monitor your app and iterate on it. See where things go wrong, and iterate on your prompts. 3. Allow the agent to introspect and improve. For example, run it in a loop, and let it critique itself; or, provide error messages and let it improve. 4. Have specialized agents that excel in one task, rather than having a general purpose agent that is expected to be good at anything. 5. Invest in [evals](https://platform.openai.com/docs/guides/evals) . This lets you train your agents to improve and get better at tasks. Orchestrating via code ---------------------- [Section titled “Orchestrating via code”](https://openai.github.io/openai-agents-js/guides/multi-agent/#orchestrating-via-code) While orchestrating via LLM is powerful, orchestrating via code makes tasks more deterministic and predictable, in terms of speed, cost and performance. Common patterns here are: * Using [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) to generate well formed data that you can inspect with your code. For example, you might ask an agent to classify the task into a few categories, and then pick the next agent based on the category. * Chaining multiple agents by transforming the output of one into the input of the next. You can decompose a task like writing a blog post into a series of steps - do research, write an outline, write the blog post, critique it, and then improve it. * Running the agent that performs the task in a `while` loop with an agent that evaluates and provides feedback, until the evaluator says the output passes certain criteria. * Running multiple agents in parallel, e.g. via JavaScript primitives like `Promise.all`. This is useful for speed when you have multiple tasks that don’t depend on each other. We have a number of examples in [`examples/agent-patterns`](https://github.com/openai/openai-agents-js/tree/main/examples/agent-patterns) . --- # Quickstart | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/quickstart/#_top) Quickstart ========== Project Setup ------------- [Section titled “Project Setup”](https://openai.github.io/openai-agents-js/guides/quickstart/#project-setup) 1. Create a project and initialize npm. You’ll only need to do this once. mkdir my_projectcd my_projectnpm init -y 2. Install the Agents SDK. 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. export OPENAI_API_KEY=sk-... Alternatively you can call `setDefaultOpenAIKey('')` to set the key programmatically and use `setTracingExportApiKey('')` for tracing. See [the config guide](https://openai.github.io/openai-agents-js/guides/config) for more details. Create your first agent ----------------------- [Section titled “Create your first agent”](https://openai.github.io/openai-agents-js/guides/quickstart/#create-your-first-agent) Agents are defined with instructions and a name. 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 -------------------- [Section titled “Run your first agent”](https://openai.github.io/openai-agents-js/guides/quickstart/#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. 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 --------------------- [Section titled “Give your agent tools”](https://openai.github.io/openai-agents-js/guides/quickstart/#give-your-agent-tools) You can give an agent tools to use to look up information or perform actions. 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 --------------------- [Section titled “Add a few more agents”](https://openai.github.io/openai-agents-js/guides/quickstart/#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. 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 -------------------- [Section titled “Define your handoffs”](https://openai.github.io/openai-agents-js/guides/quickstart/#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. // Using the Agent.create method to ensures type safety for the final outputconst 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 --------------------------- [Section titled “Run the agent orchestration”](https://openai.github.io/openai-agents-js/guides/quickstart/#run-the-agent-orchestration) The Runner is in handling the execution of the invidiual agents, any potential handoffs and tool executions. 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 ----------------------- [Section titled “Putting it all together”](https://openai.github.io/openai-agents-js/guides/quickstart/#putting-it-all-together) Let’s put it all together into one full example. Place this into your `index.js` file and run it. 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)); View your traces ---------------- [Section titled “View your traces”](https://openai.github.io/openai-agents-js/guides/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 ---------- [Section titled “Next steps”](https://openai.github.io/openai-agents-js/guides/quickstart/#next-steps) Learn how to build more complex agentic flows: * Learn about configuring [Agents](https://openai.github.io/openai-agents-js/guides/agents) . * Learn about [running agents](https://openai.github.io/openai-agents-js/guides/running-agents) . * Learn about [tools](https://openai.github.io/openai-agents-js/guides/tools) , [guardrails](https://openai.github.io/openai-agents-js/guides/guardrails) , and [models](https://openai.github.io/openai-agents-js/guides/models) . --- # Handoffs | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/handoffs/#_top) Handoffs ======== Handoffs let an agent delegate part of a conversation to another agent. This is useful when different agents specialise in specific areas. In a customer support app for example, you might have agents that handle bookings, refunds or FAQs. Handoffs are represented as tools to the LLM. If you hand off to an agent called `Refund Agent`, the tool name would be `transfer_to_refund_agent`. Creating a handoff ------------------ [Section titled “Creating a handoff”](https://openai.github.io/openai-agents-js/guides/handoffs/#creating-a-handoff) Every agent accepts a `handoffs` option. It can contain other `Agent` instances or `Handoff` objects returned by the `handoff()` helper. ### Basic usage [Section titled “Basic usage”](https://openai.github.io/openai-agents-js/guides/handoffs/#basic-usage) import { Agent, handoff } from '@openai/agents'; const billingAgent = new Agent({ name: 'Billing agent' });const refundAgent = new Agent({ name: 'Refund agent' }); // Use Agent.create method to ensure the finalOutput type considers handoffsconst triageAgent = Agent.create({ name: 'Triage agent', handoffs: [billingAgent, handoff(refundAgent)],}); ### Customising handoffs via `handoff()` [Section titled “Customising handoffs via handoff()”](https://openai.github.io/openai-agents-js/guides/handoffs/#customising-handoffs-via-handoff) The `handoff()` function lets you tweak the generated tool. * `agent` – the agent to hand off to. * `toolNameOverride` – override the default `transfer_to_` tool name. * `toolDescriptionOverride` – override the default tool description. * `onHandoff` – callback when the handoff occurs. Receives a `RunContext` and optionally parsed input. * `inputType` – expected input schema for the handoff. * `inputFilter` – filter the history passed to the next agent. import { z } from 'zod';import { Agent, handoff, RunContext } from '@openai/agents'; const FooSchema = z.object({ foo: z.string() }); function onHandoff(ctx: RunContext, input?: { foo: string }) { console.log('Handoff called with:', input?.foo);} const agent = new Agent({ name: 'My agent' }); const handoffObj = handoff(agent, { onHandoff, inputType: FooSchema, toolNameOverride: 'custom_handoff_tool', toolDescriptionOverride: 'Custom description',}); Handoff inputs -------------- [Section titled “Handoff inputs”](https://openai.github.io/openai-agents-js/guides/handoffs/#handoff-inputs) Sometimes you want the LLM to provide data when invoking a handoff. Define an input schema and use it in `handoff()`. import { z } from 'zod';import { Agent, handoff, RunContext } from '@openai/agents'; const EscalationData = z.object({ reason: z.string() });type EscalationData = z.infer; async function onHandoff( ctx: RunContext, input: EscalationData | undefined,) { console.log(`Escalation agent called with reason: ${input?.reason}`);} const agent = new Agent({ name: 'Escalation agent' }); const handoffObj = handoff(agent, { onHandoff, inputType: EscalationData,}); Input filters ------------- [Section titled “Input filters”](https://openai.github.io/openai-agents-js/guides/handoffs/#input-filters) By default a handoff receives the entire conversation history. To modify what gets passed to the next agent, provide an `inputFilter`. Common helpers live in `@openai/agents-core/extensions`. import { Agent, handoff } from '@openai/agents';import { removeAllTools } from '@openai/agents-core/extensions'; const agent = new Agent({ name: 'FAQ agent' }); const handoffObj = handoff(agent, { inputFilter: removeAllTools,}); Recommended prompts ------------------- [Section titled “Recommended prompts”](https://openai.github.io/openai-agents-js/guides/handoffs/#recommended-prompts) LLMs respond more reliably when your prompts mention handoffs. The SDK exposes a recommended prefix via `RECOMMENDED_PROMPT_PREFIX`. import { Agent } from '@openai/agents';import { RECOMMENDED_PROMPT_PREFIX } from '@openai/agents-core/extensions'; const billingAgent = new Agent({ name: 'Billing agent', instructions: `${RECOMMENDED_PROMPT_PREFIX}Fill in the rest of your prompt here.`,}); --- # Context management | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/context/#_top) Context management ================== Context is an overloaded term. There are two main classes of context you might care about: 1. **Local context** that your code can access during a run: dependencies or data needed by tools, callbacks like `onHandoff`, and lifecycle hooks. 2. **Agent/LLM context** that the language model can see when generating a response. Local context ------------- [Section titled “Local context”](https://openai.github.io/openai-agents-js/guides/context/#local-context) Local context is represented by the `RunContext` type. You create any object to hold your state or dependencies and pass it to `Runner.run()`. All tool calls and hooks receive a `RunContext` wrapper so they can read from or modify that object. import { Agent, run, RunContext, tool } from '@openai/agents';import { z } from 'zod'; interface UserInfo { name: string; uid: number;} const fetchUserAge = tool({ name: 'fetch_user_age', description: 'Return the age of the current user', parameters: z.object({}), execute: async ( _args, runContext?: RunContext, ): Promise => { return `User ${runContext?.context.name} is 47 years old`; },}); async function main() { const userInfo: UserInfo = { name: 'John', uid: 123 }; const agent = new Agent({ name: 'Assistant', tools: [fetchUserAge], }); const result = await run(agent, 'What is the age of the user?', { context: userInfo, }); console.log(result.finalOutput); // The user John is 47 years old.} main().catch((error) => { console.error(error); process.exit(1);}); Every agent, tool and hook participating in a single run must use the same **type** of context. Use local context for things like: * Data about the run (user name, IDs, etc.) * Dependencies such as loggers or data fetchers * Helper functions Agent/LLM context ----------------- [Section titled “Agent/LLM context”](https://openai.github.io/openai-agents-js/guides/context/#agentllm-context) When the LLM is called, the only data it can see comes from the conversation history. To make additional information available you have a few options: 1. Add it to the Agent `instructions` – also known as a system or developer message. This can be a static string or a function that receives the context and returns a string. 2. Include it in the `input` when calling `Runner.run()`. This is similar to the instructions technique but lets you place the message lower in the [chain of command](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command) . 3. Expose it via function tools so the LLM can fetch data on demand. 4. Use retrieval or web search tools to ground responses in relevant data from files, databases, or the web. --- # Results | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/results/#_top) Results ======= When you [run your agent](https://openai.github.io/openai-agents-js/guides/running-agents) , you will either receive a: * [`RunResult`](https://openai.github.io/openai-agents-js/openai/agents/classes/runresult) if you call `run` without `stream: true` * [`StreamedRunResult`](https://openai.github.io/openai-agents-js/openai/agents/classes/streamedrunresult) if you call `run` with `stream: true`. For details on streaming, also check the [streaming guide](https://openai.github.io/openai-agents-js/guides/streaming) . Final output ------------ [Section titled “Final output”](https://openai.github.io/openai-agents-js/guides/results/#final-output) The `finalOutput` property contains the final output of the last agent that ran. This result is either: * `string` — default for any agent that has no `outputType` defined * `unknown` — if the agent has a JSON schema defined as output type. In this case the JSON was parsed but you still have to verify its type manually. * `z.infer` — if the agent has a Zod schema defined as output type. The output will automatically be parsed against this schema. * `undefined` — if the agent did not produce an output (for example stopped before it could produce an output) If you are using handoffs with different output types, you should use the `Agent.create()` method instead of the `new Agent()` constructor to create your agents. This will enable the SDK to infer the output types across all possible handoffs and provide a union type for the `finalOutput` property. For example: import { Agent, run } from '@openai/agents';import { z } from 'zod'; const refundAgent = new Agent({ name: 'Refund Agent', instructions: 'You are a refund agent. You are responsible for refunding customers.', outputType: z.object({ refundApproved: z.boolean(), }),}); const orderAgent = new Agent({ name: 'Order Agent', instructions: 'You are an order agent. You are responsible for processing orders.', outputType: z.object({ orderId: z.string(), }),}); const triageAgent = Agent.create({ name: 'Triage Agent', instructions: 'You are a triage agent. You are responsible for triaging customer issues.', handoffs: [refundAgent, orderAgent],}); const result = await run(triageAgent, 'I need to a refund for my order'); const output = result.finalOutput;// ^? { refundApproved: boolean } | { orderId: string } | string | undefined Inputs for the next turn ------------------------ [Section titled “Inputs for the next turn”](https://openai.github.io/openai-agents-js/guides/results/#inputs-for-the-next-turn) There are two ways you can access the inputs for your next turn: * `result.history` — contains a copy of both your input and the output of the agents. * `result.output` — contains the output of the full agent run. `history` is a convenient way to maintain a full history in a chat-like use case: import { Agent, user, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents'; const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant knowledgeable about recent AGI research.',}); let history: AgentInputItem[] = [ // initial message user('Are we there yet?'),]; for (let i = 0; i < 10; i++) { // run 10 times const result = await run(agent, history); // update the history to the new output history = result.history; history.push(user('How about now?'));} Last agent ---------- [Section titled “Last agent”](https://openai.github.io/openai-agents-js/guides/results/#last-agent) The `lastAgent` property contains the last agent that ran. Depending on your application, this is often useful for the next time the user inputs something. For example, if you have a frontline triage agent that hands off to a language-specific agent, you can store the last agent, and reuse it the next time the user messages the agent. In streaming mode it can also be useful to access the `currentAgent` property that’s mapping to the current agent that is running. New items --------- [Section titled “New items”](https://openai.github.io/openai-agents-js/guides/results/#new-items) The `newItems` property contains the new items generated during the run. The items are [`RunItem`](https://openai.github.io/openai-agents-js/openai/agents/type-aliases/runitem) s. A run item wraps the raw item generated by the LLM. These can be used to access additionally to the output of the LLM which agent these events were associated with. * [`RunMessageOutputItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runmessageoutputitem) indicates a message from the LLM. The raw item is the message generated. * [`RunHandoffCallItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runhandoffcallitem) indicates that the LLM called the handoff tool. The raw item is the tool call item from the LLM. * [`RunHandoffOutputItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runhandoffoutputitem) indicates that a handoff occurred. The raw item is the tool response to the handoff tool call. You can also access the source/target agents from the item. * [`RunToolCallItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolcallitem) indicates that the LLM invoked a tool. * [`RunToolCallOutputItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolcalloutputitem) indicates that a tool was called. The raw item is the tool response. You can also access the tool output from the item. * [`RunReasoningItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runreasoningitem) indicates a reasoning item from the LLM. The raw item is the reasoning generated. * [`RunToolApprovalItem`](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolapprovalitem) indicates that the LLM requested approval for a tool call. The raw item is the tool call item from the LLM. State ----- [Section titled “State”](https://openai.github.io/openai-agents-js/guides/results/#state) The `state` property contains the state of the run. Most of what is attached to the `result` is derived from the `state` but the `state` is serializable/deserializable and can also be used as input for a subsequent call to `run` in case you need to [recover from an error](https://openai.github.io/openai-agents-js/guides/running-agents#exceptions) or deal with an [`interruption`](https://openai.github.io/openai-agents-js/guides/results/#interruptions) . Interruptions ------------- [Section titled “Interruptions”](https://openai.github.io/openai-agents-js/guides/results/#interruptions) If you are using `needsApproval` in your agent, your `run` might trigger some `interruptions` that you need to handle before continuing. In that case `interruptions` will be an array of `ToolApprovalItem`s that caused the interruption. Check out the [human-in-the-loop guide](https://openai.github.io/openai-agents-js/guides/human-in-the-loop) for more information on how to work with interruptions. Other information ----------------- [Section titled “Other information”](https://openai.github.io/openai-agents-js/guides/results/#other-information) ### Raw responses [Section titled “Raw responses”](https://openai.github.io/openai-agents-js/guides/results/#raw-responses) The `rawResponses` property contains the raw LLM responses generated by the model during the agent run. ### Last response ID [Section titled “Last response ID”](https://openai.github.io/openai-agents-js/guides/results/#last-response-id) The `lastResponseId` property contains the ID of the last response generated by the model during the agent run. ### Guardrail results [Section titled “Guardrail results”](https://openai.github.io/openai-agents-js/guides/results/#guardrail-results) The `inputGuardrailResults` and `outputGuardrailResults` properties contain the results of the guardrails, if any. Guardrail results can sometimes contain useful information you want to log or store, so we make these available to you. ### Original input [Section titled “Original input”](https://openai.github.io/openai-agents-js/guides/results/#original-input) The `input` property contains the original input you provided to the run method. In most cases you won’t need this, but it’s available in case you do. --- # Agents | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/agents/#_top) Agents ====== Agents are the main building‑block of the OpenAI Agents SDK. An **Agent** is a Large Language Model (LLM) that has been configured with: * **Instructions** – the system prompt that tells the model _who it is_ and _how it should respond_. * **Model** – which OpenAI model to call, plus any optional model tuning parameters. * **Tools** – a list of functions or APIs the LLM can invoke to accomplish a task. import { Agent } from '@openai/agents'; const agent = new Agent({ name: 'Haiku Agent', instructions: 'Always respond in haiku form.', model: 'gpt-5-nano', // optional – falls back to the default model}); The rest of this page walks through every Agent feature in more detail. * * * Basic configuration ------------------- [Section titled “Basic configuration”](https://openai.github.io/openai-agents-js/guides/agents/#basic-configuration) The `Agent` constructor takes a single configuration object. The most commonly‑used properties are shown below. | Property | Required | Description | | --- | --- | --- | | `name` | yes | A short human‑readable identifier. | | `instructions` | yes | System prompt (string **or** function – see [Dynamic instructions](https://openai.github.io/openai-agents-js/guides/agents/#dynamic-instructions)
). | | `model` | no | Model name **or** a custom [`Model`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/model/)
implementation. | | `modelSettings` | no | Tuning parameters (temperature, top\_p, etc.). If the properties you need aren’t at the top level, you can include them under `providerData`. | | `tools` | no | Array of [`Tool`](https://openai.github.io/openai-agents-js/openai/agents/type-aliases/tool/)
instances the model can call. | import { Agent, tool } from '@openai/agents';import { z } from 'zod'; const getWeather = tool({ name: 'get_weather', description: 'Return the weather for a given city.', parameters: z.object({ city: z.string() }), async execute({ city }) { return `The weather in ${city} is sunny.`; },}); const agent = new Agent({ name: 'Weather bot', instructions: 'You are a helpful weather bot.', model: 'gpt-4.1', tools: [getWeather],}); * * * Context ------- [Section titled “Context”](https://openai.github.io/openai-agents-js/guides/agents/#context) Agents are **generic on their context type** – i.e. `Agent`. The _context_ is a dependency‑injection object that you create and pass to `Runner.run()`. It is forwarded to every tool, guardrail, handoff, etc. and is useful for storing state or providing shared services (database connections, user metadata, feature flags, …). import { Agent } from '@openai/agents'; interface Purchase { id: string; uid: string; deliveryStatus: string;}interface UserContext { uid: string; isProUser: boolean; // this function can be used within tools fetchPurchases(): Promise;} const agent = new Agent({ name: 'Personal shopper', instructions: 'Recommend products the user will love.',}); // Laterimport { run } from '@openai/agents'; const result = await run(agent, 'Find me a new pair of running shoes', { context: { uid: 'abc', isProUser: true, fetchPurchases: async () => [] },}); * * * Output types ------------ [Section titled “Output types”](https://openai.github.io/openai-agents-js/guides/agents/#output-types) By default, an Agent returns **plain text** (`string`). If you want the model to return a structured object you can specify the `outputType` property. The SDK accepts: 1. A [Zod](https://github.com/colinhacks/zod) schema (`z.object({...})`). 2. Any JSON‑schema‑compatible object. import { Agent } from '@openai/agents';import { z } from 'zod'; const CalendarEvent = z.object({ name: z.string(), date: z.string(), participants: z.array(z.string()),}); const extractor = new Agent({ name: 'Calendar extractor', instructions: 'Extract calendar events from the supplied text.', outputType: CalendarEvent,}); When `outputType` is provided, the SDK automatically uses [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) instead of plain text. * * * Multi-agent system design patterns ---------------------------------- [Section titled “Multi-agent system design patterns”](https://openai.github.io/openai-agents-js/guides/agents/#multi-agent-system-design-patterns) There are many ways to compose agents together. Two patterns we regularly see in production apps are: 1. **Manager (agents as tools)** – a central agent owns the conversation and invokes specialized agents that are exposed as tools. 2. **Handoffs** – the initial agent delegates the entire conversation to a specialist once it has identified the user’s request. These approaches are complementary. Managers give you a single place to enforce guardrails or rate limits, while handoffs let each agent focus on a single task without retaining control of the conversation. ### Manager (agents as tools) [Section titled “Manager (agents as tools)”](https://openai.github.io/openai-agents-js/guides/agents/#manager-agents-as-tools) In this pattern the manager never hands over control—the LLM uses the tools and the manager summarizes the final answer. Read more in the [tools guide](https://openai.github.io/openai-agents-js/guides/tools#agents-as-tools) . import { Agent } from '@openai/agents'; const bookingAgent = new Agent({ name: 'Booking expert', instructions: 'Answer booking questions and modify reservations.',}); const refundAgent = new Agent({ name: 'Refund expert', instructions: 'Help customers process refunds and credits.',}); const customerFacingAgent = new Agent({ name: 'Customer-facing agent', instructions: 'Talk to the user directly. When they need booking or refund help, call the matching tool.', tools: [ bookingAgent.asTool({ toolName: 'booking_expert', toolDescription: 'Handles booking questions and requests.', }), refundAgent.asTool({ toolName: 'refund_expert', toolDescription: 'Handles refund questions and requests.', }), ],}); ### Handoffs [Section titled “Handoffs”](https://openai.github.io/openai-agents-js/guides/agents/#handoffs) With handoffs the triage agent routes requests, but once a handoff occurs the specialist agent owns the conversation until it produces a final output. This keeps prompts short and lets you reason about each agent independently. Learn more in the [handoffs guide](https://openai.github.io/openai-agents-js/guides/handoffs) . import { Agent } from '@openai/agents'; const bookingAgent = new Agent({ name: 'Booking Agent', instructions: 'Help users with booking requests.',}); const refundAgent = new Agent({ name: 'Refund Agent', instructions: 'Process refund requests politely and efficiently.',}); // Use Agent.create method to ensure the finalOutput type considers handoffsconst triageAgent = Agent.create({ name: 'Triage Agent', instructions: `Help the user with their questions. If the user asks about booking, hand off to the booking agent. If the user asks about refunds, hand off to the refund agent.`.trimStart(), handoffs: [bookingAgent, refundAgent],}); * * * Dynamic instructions -------------------- [Section titled “Dynamic instructions”](https://openai.github.io/openai-agents-js/guides/agents/#dynamic-instructions) `instructions` can be a **function** instead of a string. The function receives the current `RunContext` and the Agent instance and can return a string _or_ a `Promise`. import { Agent, RunContext } from '@openai/agents'; interface UserContext { name: string;} function buildInstructions(runContext: RunContext) { return `The user's name is ${runContext.context.name}. Be extra friendly!`;} const agent = new Agent({ name: 'Personalized helper', instructions: buildInstructions,}); Both synchronous and `async` functions are supported. * * * Lifecycle hooks --------------- [Section titled “Lifecycle hooks”](https://openai.github.io/openai-agents-js/guides/agents/#lifecycle-hooks) For advanced use‑cases you can observe the Agent lifecycle by listening on events. To learn what’s available, refer to agent hook event names listed [here](https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/src/lifecycle.ts) . import { Agent } from '@openai/agents'; const agent = new Agent({ name: 'Verbose agent', instructions: 'Explain things thoroughly.',}); agent.on('agent_start', (ctx, agent) => { console.log(`[${agent.name}] started`);});agent.on('agent_end', (ctx, output) => { console.log(`[agent] produced:`, output);}); * * * Guardrails ---------- [Section titled “Guardrails”](https://openai.github.io/openai-agents-js/guides/agents/#guardrails) Guardrails allow you to validate or transform user input and agent output. They are configured via the `inputGuardrails` and `outputGuardrails` arrays. See the [guardrails guide](https://openai.github.io/openai-agents-js/guides/guardrails) for details. * * * Cloning / copying agents ------------------------ [Section titled “Cloning / copying agents”](https://openai.github.io/openai-agents-js/guides/agents/#cloning--copying-agents) Need a slightly modified version of an existing agent? Use the `clone()` method, which returns an entirely new `Agent` instance. import { Agent } from '@openai/agents'; const pirateAgent = new Agent({ name: 'Pirate', instructions: 'Respond like a pirate – lots of “Arrr!”', model: 'gpt-5-mini',}); const robotAgent = pirateAgent.clone({ name: 'Robot', instructions: 'Respond like a robot – be precise and factual.',}); * * * Forcing tool use ---------------- [Section titled “Forcing tool use”](https://openai.github.io/openai-agents-js/guides/agents/#forcing-tool-use) Supplying tools doesn’t guarantee the LLM will call one. You can **force** tool use with `modelSettings.tool_choice`: 1. `'auto'` (default) – the LLM decides whether to use a tool. 2. `'required'` – the LLM _must_ call a tool (it can choose which one). 3. `'none'` – the LLM must **not** call a tool. 4. A specific tool name, e.g. `'calculator'` – the LLM must call that particular tool. import { Agent, tool } from '@openai/agents';import { z } from 'zod'; const calculatorTool = tool({ name: 'Calculator', description: 'Use this tool to answer questions about math problems.', parameters: z.object({ question: z.string() }), execute: async (input) => { throw new Error('TODO: implement this'); },}); const agent = new Agent({ name: 'Strict tool user', instructions: 'Always answer using the calculator tool.', tools: [calculatorTool], modelSettings: { toolChoice: 'auto' },}); ### Preventing infinite loops [Section titled “Preventing infinite loops”](https://openai.github.io/openai-agents-js/guides/agents/#preventing-infinite-loops) After a tool call the SDK automatically resets `tool_choice` back to `'auto'`. This prevents the model from entering an infinite loop where it repeatedly tries to call the tool. You can override this behavior via the `resetToolChoice` flag or by configuring `toolUseBehavior`: * `'run_llm_again'` (default) – run the LLM again with the tool result. * `'stop_on_first_tool'` – treat the first tool result as the final answer. * `{ stopAtToolNames: ['my_tool'] }` – stop when any of the listed tools is called. * `(context, toolResults) => ...` – custom function returning whether the run should finish. const agent = new Agent({ ..., toolUseBehavior: 'stop_on_first_tool',}); * * * Next steps ---------- [Section titled “Next steps”](https://openai.github.io/openai-agents-js/guides/agents/#next-steps) * Learn how to [run agents](https://openai.github.io/openai-agents-js/guides/running-agents) . * Dive into [tools](https://openai.github.io/openai-agents-js/guides/tools) , [guardrails](https://openai.github.io/openai-agents-js/guides/guardrails) , and [models](https://openai.github.io/openai-agents-js/guides/models) . * Explore the full TypeDoc reference under **@openai/agents** in the sidebar. --- # Tools | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/tools/#_top) Tools ===== Tools let an Agent **take actions** – fetch data, call external APIs, execute code, or even use a computer. The JavaScript/TypeScript SDK supports four categories: 1. **Hosted tools** – run alongside the model on OpenAI servers. _(web search, file search, computer use, code interpreter, image generation)_ 2. **Function tools** – wrap any local function with a JSON schema so the LLM can call it. 3. **Agents as tools** – expose an entire Agent as a callable tool. 4. **Local MCP servers** – attach a Model Context Protocol server running on your machine. * * * 1\. Hosted tools ---------------- [Section titled “1. Hosted tools”](https://openai.github.io/openai-agents-js/guides/tools/#1-hosted-tools) When you use the `OpenAIResponsesModel` you can add the following built‑in tools: | Tool | Type string | Purpose | | --- | --- | --- | | Web search | `'web_search'` | Internet search. | | File / retrieval search | `'file_search'` | Query vector stores hosted on OpenAI. | | Computer use | `'computer'` | Automate GUI interactions. | | Shell | `'shell'` | Run shell commands on the host. | | Apply patch | `'apply_patch'` | Apply V4A diffs to local files. | | Code Interpreter | `'code_interpreter'` | Run code in a sandboxed environment. | | Image generation | `'image_generation'` | Generate images based on text. | import { Agent, webSearchTool, fileSearchTool } from '@openai/agents'; const agent = new Agent({ name: 'Travel assistant', tools: [webSearchTool(), fileSearchTool('VS_ID')],}); The exact parameter sets match the OpenAI Responses API – refer to the official documentation for advanced options like `rankingOptions` or semantic filters. * * * 2\. Function tools ------------------ [Section titled “2. Function tools”](https://openai.github.io/openai-agents-js/guides/tools/#2-function-tools) You can turn **any** function into a tool with the `tool()` helper. import { tool } from '@openai/agents';import { z } from 'zod'; const getWeatherTool = tool({ name: 'get_weather', description: 'Get the weather for a given city', parameters: z.object({ city: z.string() }), async execute({ city }) { return `The weather in ${city} is sunny.`; },}); ### Options reference [Section titled “Options reference”](https://openai.github.io/openai-agents-js/guides/tools/#options-reference) | Field | Required | Description | | --- | --- | --- | | `name` | No | Defaults to the function name (e.g., `get_weather`). | | `description` | Yes | Clear, human-readable description shown to the LLM. | | `parameters` | Yes | Either a Zod schema or a raw JSON schema object. Zod parameters automatically enable **strict** mode. | | `strict` | No | When `true` (default), the SDK returns a model error if the arguments don’t validate. Set to `false` for fuzzy matching. | | `execute` | Yes | `(args, context) => string \| Promise`– your business logic. The optional second parameter is the`RunContext`. | | `errorFunction` | No | Custom handler `(context, error) => string` for transforming internal errors into a user-visible string. | ### Non‑strict JSON‑schema tools [Section titled “Non‑strict JSON‑schema tools”](https://openai.github.io/openai-agents-js/guides/tools/#nonstrict-jsonschema-tools) If you need the model to _guess_ invalid or partial input you can disable strict mode when using raw JSON schema: import { tool } from '@openai/agents'; interface LooseToolInput { text: string;} const looseTool = tool({ description: 'Echo input; be forgiving about typos', strict: false, parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'], additionalProperties: true, }, execute: async (input) => { // because strict is false we need to do our own verification if (typeof input !== 'object' || input === null || !('text' in input)) { return 'Invalid input. Please try again'; } return (input as LooseToolInput).text; },}); * * * 3\. Agents as tools ------------------- [Section titled “3. Agents as tools”](https://openai.github.io/openai-agents-js/guides/tools/#3-agents-as-tools) Sometimes you want an Agent to _assist_ another Agent without fully handing off the conversation. Use `agent.asTool()`: import { Agent } from '@openai/agents'; const summarizer = new Agent({ name: 'Summarizer', instructions: 'Generate a concise summary of the supplied text.',}); const summarizerTool = summarizer.asTool({ toolName: 'summarize_text', toolDescription: 'Generate a concise summary of the supplied text.',}); const mainAgent = new Agent({ name: 'Research assistant', tools: [summarizerTool],}); Under the hood the SDK: * Creates a function tool with a single `input` parameter. * Runs the sub‑agent with that input when the tool is called. * Returns either the last message or the output extracted by `customOutputExtractor`. When you run an agent as a tool, Agents SDK creates a runner with the default settings and run the agent with it within the function execution. If you want to provide any properties of `runConfig` or `runOptions`, you can pass them to the `asTool()` method to customize the runner’s behavior. ### Streaming events from agent tools [Section titled “Streaming events from agent tools”](https://openai.github.io/openai-agents-js/guides/tools/#streaming-events-from-agent-tools) Agent tools can stream all nested run events back to your app. Choose the hook style that fits how you construct the tool: import { Agent } from '@openai/agents'; const billingAgent = new Agent({ name: 'Billing Agent', instructions: 'Answer billing questions and compute simple charges.',}); const billingTool = billingAgent.asTool({ toolName: 'billing_agent', toolDescription: 'Handles customer billing questions.', // onStream: simplest catch-all when you define the tool inline. onStream: (event) => { console.log(`[onStream] ${event.event.type}`, event); },}); // on(eventName) lets you subscribe selectively (or use '*' for all).billingTool.on('run_item_stream_event', (event) => { console.log('[on run_item_stream_event]', event);});billingTool.on('raw_model_stream_event', (event) => { console.log('[on raw_model_stream_event]', event);}); const orchestrator = new Agent({ name: 'Support Orchestrator', instructions: 'Delegate billing questions to the billing agent tool.', tools: [billingTool],}); * Event types match `RunStreamEvent['type']`: `raw_model_stream_event`, `run_item_stream_event`, `agent_updated_stream_event`. * `onStream` is the simplest “catch-all” and works well when you declare the tool inline (`tools: [agent.asTool({ onStream })]`). Use it if you do not need per-event routing. * `on(eventName, handler)` lets you subscribe selectively (or with `'*'`) and is best when you need finer-grained handling or want to attach listeners after creation. * If you provide either `onStream` or any `on(...)` handler, the agent-as-tool will run in streaming mode automatically; without them it stays on the non-streaming path. * Handlers are invoked in parallel so a slow `onStream` callback will not block `on(...)` handlers (and vice versa). * `toolCallId` is provided when the tool was invoked via a model tool call; direct `invoke()` calls or provider quirks may omit it. * * * 4\. MCP servers --------------- [Section titled “4. MCP servers”](https://openai.github.io/openai-agents-js/guides/tools/#4-mcp-servers) You can expose tools via [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers and attach them to an agent. For instance, you can use `MCPServerStdio` to spawn and connect to the stdio MCP server: import { Agent, MCPServerStdio } from '@openai/agents'; const server = new MCPServerStdio({ fullCommand: 'npx -y @modelcontextprotocol/server-filesystem ./sample_files',}); await server.connect(); const agent = new Agent({ name: 'Assistant', mcpServers: [server],}); See [`filesystem-example.ts`](https://github.com/openai/openai-agents-js/tree/main/examples/mcp/filesystem-example.ts) for a complete example. Also, if you’re looking for a comprehensitve guide for MCP server tool integration, refer to [MCP guide](https://openai.github.io/openai-agents-js/guides/mcp) for details. * * * Tool use behavior ----------------- [Section titled “Tool use behavior”](https://openai.github.io/openai-agents-js/guides/tools/#tool-use-behavior) Refer to the [Agents guide](https://openai.github.io/openai-agents-js/guides/agents#forcing-tool-use) for controlling when and how a model must use tools (`tool_choice`, `toolUseBehavior`, etc.). * * * Best practices -------------- [Section titled “Best practices”](https://openai.github.io/openai-agents-js/guides/tools/#best-practices) * **Short, explicit descriptions** – describe _what_ the tool does _and when to use it_. * **Validate inputs** – use Zod schemas for strict JSON validation where possible. * **Avoid side‑effects in error handlers** – `errorFunction` should return a helpful string, not throw. * **One responsibility per tool** – small, composable tools lead to better model reasoning. * * * Next steps ---------- [Section titled “Next steps”](https://openai.github.io/openai-agents-js/guides/tools/#next-steps) * Learn about [forcing tool use](https://openai.github.io/openai-agents-js/guides/agents#forcing-tool-use) . * Add [guardrails](https://openai.github.io/openai-agents-js/guides/guardrails) to validate tool inputs or outputs. * Dive into the TypeDoc reference for [`tool()`](https://openai.github.io/openai-agents-js/openai/agents/functions/tool) and the various hosted tool types. --- # Models | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/models/#_top) Models ====== Every Agent ultimately calls an LLM. The SDK abstracts models behind two lightweight interfaces: * [`Model`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/model) – knows how to make _one_ request against a specific API. * [`ModelProvider`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/modelprovider) – resolves human‑readable model **names** (e.g. `'gpt‑4o'`) to `Model` instances. In day‑to‑day work you normally only interact with model **names** and occasionally `ModelSettings`. import { Agent } from '@openai/agents'; const agent = new Agent({ name: 'Creative writer', model: 'gpt-5.2',}); Default model ------------- [Section titled “Default model”](https://openai.github.io/openai-agents-js/guides/models/#default-model) When you don’t specify a model when initializing an `Agent`, the default model will be used. The default is currently [`gpt-4.1`](https://platform.openai.com/docs/models/gpt-4.1) for compatibility and low latency. If you have access, we recommend setting your agents to [`gpt-5.2`](https://platform.openai.com/docs/models/gpt-5.2) for higher quality while keeping explicit `modelSettings`. If you want to switch to other models like [`gpt-5.2`](https://platform.openai.com/docs/models/gpt-5.2) , there are two ways to configure your agents. First, if you want to consistently use a specific model for all agents that do not set a custom model, set the `OPENAI_DEFAULT_MODEL` environment variable before running your agents. export OPENAI_DEFAULT_MODEL=gpt-5node my-awesome-agent.js Second, you can set a default model for a `Runner` instance. If you don’t set a model for an agent, this `Runner`’s default model will be used. import { Runner } from '@openai/agents'; const runner = new Runner({ model: 'gpt‑4.1-mini' }); ### GPT-5 models [Section titled “GPT-5 models”](https://openai.github.io/openai-agents-js/guides/models/#gpt-5-models) When you use any of GPT-5’s reasoning models ([`gpt-5`](https://platform.openai.com/docs/models/gpt-5) , [`gpt-5-mini`](https://platform.openai.com/docs/models/gpt-5-mini) , or [`gpt-5-nano`](https://platform.openai.com/docs/models/gpt-5-nano) ) this way, the SDK applies sensible `modelSettings` by default. Specifically, it sets both `reasoning.effort` and `verbosity` to `"low"`. To adjust the reasoning effort for the default model, pass your own `modelSettings`: import { Agent } from '@openai/agents'; const myAgent = new Agent({ name: 'My Agent', instructions: "You're a helpful agent.", modelSettings: { reasoning: { effort: 'minimal' }, text: { verbosity: 'low' }, }, // If OPENAI_DEFAULT_MODEL=gpt-5 is set, passing only modelSettings works. // It's also fine to pass a GPT-5 model name explicitly: // model: 'gpt-5',}); For lower latency, using either [`gpt-5-mini`](https://platform.openai.com/docs/models/gpt-5-mini) or [`gpt-5-nano`](https://platform.openai.com/docs/models/gpt-5-nano) with `reasoning.effort="minimal"` will often return responses faster than the default settings. However, some built-in tools (such as file search and image generation) in Responses API do not support `"minimal"` reasoning effort, which is why this Agents SDK defaults to `"low"`. ### Non-GPT-5 models [Section titled “Non-GPT-5 models”](https://openai.github.io/openai-agents-js/guides/models/#non-gpt-5-models) If you pass a non–GPT-5 model name without custom `modelSettings`, the SDK reverts to generic `modelSettings` compatible with any model. * * * The OpenAI provider ------------------- [Section titled “The OpenAI provider”](https://openai.github.io/openai-agents-js/guides/models/#the-openai-provider) The default `ModelProvider` resolves names using the OpenAI APIs. It supports two distinct endpoints: | API | Usage | Call `setOpenAIAPI()` | | --- | --- | --- | | Chat Completions | Standard chat & function calls | `setOpenAIAPI('chat_completions')` | | Responses | New streaming‑first generative API (tool calls, flexible outputs) | `setOpenAIAPI('responses')` _(default)_ | ### Authentication [Section titled “Authentication”](https://openai.github.io/openai-agents-js/guides/models/#authentication) import { setDefaultOpenAIKey } from '@openai/agents'; setDefaultOpenAIKey(process.env.OPENAI_API_KEY!); // sk-... You can also plug your own `OpenAI` client via `setDefaultOpenAIClient(client)` if you need custom networking settings. * * * ModelSettings ------------- [Section titled “ModelSettings”](https://openai.github.io/openai-agents-js/guides/models/#modelsettings) `ModelSettings` mirrors the OpenAI parameters but is provider‑agnostic. | Field | Type | Notes | | --- | --- | --- | | `temperature` | `number` | Creativity vs. determinism. | | `topP` | `number` | Nucleus sampling. | | `frequencyPenalty` | `number` | Penalise repeated tokens. | | `presencePenalty` | `number` | Encourage new tokens. | | `toolChoice` | `'auto' \| 'required' \| 'none' \| string` | See [forcing tool use](https://openai.github.io/openai-agents-js/guides/agents#forcing-tool-use)
. | | `parallelToolCalls` | `boolean` | Allow parallel function calls where supported. | | `truncation` | `'auto' \| 'disabled'` | Token truncation strategy. | | `maxTokens` | `number` | Maximum tokens in the response. | | `store` | `boolean` | Persist the response for retrieval / RAG workflows. | | `reasoning.effort` | `'minimal' \| 'low' \| 'medium' \| 'high'` | Reasoning effort for gpt-5 etc. | | `text.verbosity` | `'low' \| 'medium' \| 'high'` | Text verbosity for gpt-5 etc. | Attach settings at either level: import { Runner, Agent } from '@openai/agents'; const agent = new Agent({ name: 'Creative writer', // ... modelSettings: { temperature: 0.7, toolChoice: 'auto' },}); // or globallynew Runner({ modelSettings: { temperature: 0.3 } }); `Runner`‑level settings override any conflicting per‑agent settings. * * * Prompt ------ [Section titled “Prompt”](https://openai.github.io/openai-agents-js/guides/models/#prompt) Agents can be configured with a `prompt` parameter, indicating a server-stored prompt configuration that should be used to control the Agent’s behavior. Currently, this option is only supported when you use the OpenAI [Responses API](https://platform.openai.com/docs/api-reference/responses) . | Field | Type | Notes | | --- | --- | --- | | `promptId` | `string` | Unique identifier for a prompt. | | `version` | `string` | Version of the prompt you wish to use. | | `variables` | `object` | A key/value pair of variables to substitute into the prompt. Values can be strings or content input types like text, images, or files. | import { Agent, run } from '@openai/agents'; async function main() { const agent = new Agent({ name: 'Assistant', prompt: { promptId: 'pmpt_68d50b26524c81958c1425070180b5e10ab840669e470fc7', variables: { name: 'Kaz' }, }, }); const result = await run(agent, 'What is your name?'); console.log(result.finalOutput);} main().catch((error) => { console.error(error); process.exit(1);}); Any additional agent configuration, like tools or instructions, will override the values you may have configured in your stored prompt. * * * Custom model providers ---------------------- [Section titled “Custom model providers”](https://openai.github.io/openai-agents-js/guides/models/#custom-model-providers) Implementing your own provider is straightforward – implement `ModelProvider` and `Model` and pass the provider to the `Runner` constructor: import { ModelProvider, Model, ModelRequest, ModelResponse, ResponseStreamEvent,} from '@openai/agents-core'; import { Agent, Runner } from '@openai/agents'; class EchoModel implements Model { name: string; constructor() { this.name = 'Echo'; } async getResponse(request: ModelRequest): Promise { return { usage: {}, output: [{ role: 'assistant', content: request.input as string }], } as any; } async *getStreamedResponse( _request: ModelRequest, ): AsyncIterable { yield { type: 'response.completed', response: { output: [], usage: {} }, } as any; }} class EchoProvider implements ModelProvider { getModel(_modelName?: string): Promise | Model { return new EchoModel(); }} const runner = new Runner({ modelProvider: new EchoProvider() });console.log(runner.config.modelProvider.getModel());const agent = new Agent({ name: 'Test Agent', instructions: 'You are a helpful assistant.', model: new EchoModel(), modelSettings: { temperature: 0.7, toolChoice: 'auto' },});console.log(agent.model); * * * Tracing exporter ---------------- [Section titled “Tracing exporter”](https://openai.github.io/openai-agents-js/guides/models/#tracing-exporter) When using the OpenAI provider you can opt‑in to automatic trace export by providing your API key: import { setTracingExportApiKey } from '@openai/agents'; setTracingExportApiKey('sk-...'); This sends traces to the [OpenAI dashboard](https://platform.openai.com/traces) where you can inspect the complete execution graph of your workflow. * * * Next steps ---------- [Section titled “Next steps”](https://openai.github.io/openai-agents-js/guides/models/#next-steps) * Explore [running agents](https://openai.github.io/openai-agents-js/guides/running-agents) . * Give your models super‑powers with [tools](https://openai.github.io/openai-agents-js/guides/tools) . * Add [guardrails](https://openai.github.io/openai-agents-js/guides/guardrails) or [tracing](https://openai.github.io/openai-agents-js/guides/tracing) as needed. --- # Running agents | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/running-agents/#_top) Running agents ============== Agents do nothing by themselves – you **run** them with the `Runner` class or the `run()` utility. 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. When you don’t need a custom runner, you can also use the `run()` utility, which runs a singleton default `Runner` instance. Alternatively, you can create your own runner instance: import { Agent, Runner } from '@openai/agents'; const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant',}); // You can pass custom configuration to the runnerconst runner = new Runner(); const result = await runner.run( agent, 'Write a haiku about recursion in programming.',);console.log(result.finalOutput); // Code within the code,// Functions calling themselves,// Infinite loop's dance. After running your agent, you will receive a [result](https://openai.github.io/openai-agents-js/guides/results) object that contains the final output and the full history of the run. The agent loop -------------- [Section titled “The agent loop”](https://openai.github.io/openai-agents-js/guides/running-agents/#the-agent-loop) When you use the run method in Runner, you pass in a starting agent and input. The input can either be a string (which is considered a user message), or a list of input items, which are the items in the OpenAI Responses API. The runner then runs a loop: 1. Call the current agent’s model with the current input. 2. Inspect the LLM response. * **Final output** → return. * **Handoff** → switch to the new agent, keep the accumulated conversation history, go to 1. * **Tool calls** → execute tools, append their results to the conversation, go to 1. 3. Throw [`MaxTurnsExceededError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/maxturnsexceedederror) once `maxTurns` is reached. ### Runner lifecycle [Section titled “Runner lifecycle”](https://openai.github.io/openai-agents-js/guides/running-agents/#runner-lifecycle) Create a `Runner` when your app starts and reuse it across requests. The instance stores global configuration such as model provider and tracing options. Only create another `Runner` if you need a completely different setup. For simple scripts you can also call `run()` which uses a default runner internally. Run arguments ------------- [Section titled “Run arguments”](https://openai.github.io/openai-agents-js/guides/running-agents/#run-arguments) The input to the `run()` method is an initial agent to start the run on, input for the run and a set of options. The input can either be a string (which is considered a user message), or a list of [input items](https://openai.github.io/openai-agents-js/openai/agents-core/type-aliases/agentinputitem) , or a [`RunState`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/runstate) object in case you are building a [human-in-the-loop](https://openai.github.io/openai-agents-js/guides/human-in-the-loop) agent. The additional options are: | Option | Default | Description | | --- | --- | --- | | `stream` | `false` | If `true` the call returns a `StreamedRunResult` and emits events as they arrive from the model. | | `context` | – | Context object forwarded to every tool / guardrail / handoff. Learn more in the [context guide](https://openai.github.io/openai-agents-js/guides/context)
. | | `maxTurns` | `10` | Safety limit – throws [`MaxTurnsExceededError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/maxturnsexceedederror)
when reached. | | `signal` | – | `AbortSignal` for cancellation. | Streaming --------- [Section titled “Streaming”](https://openai.github.io/openai-agents-js/guides/running-agents/#streaming) Streaming allows you to additionally receive streaming events as the LLM runs. Once the stream is started, the `StreamedRunResult` will contain the complete information about the run, including all the new outputs produces. You can iterate over the streaming events using a `for await` loop. Read more in the [streaming guide](https://openai.github.io/openai-agents-js/guides/streaming) . Run config ---------- [Section titled “Run config”](https://openai.github.io/openai-agents-js/guides/running-agents/#run-config) If you are creating your own `Runner` instance, you can pass in a `RunConfig` object to configure the runner. | Field | Type | Purpose | | --- | --- | --- | | `model` | `string \| Model` | Force a specific model for **all** agents in the run. | | `modelProvider` | `ModelProvider` | Resolves model names – defaults to the OpenAI provider. | | `modelSettings` | `ModelSettings` | Global tuning parameters that override per‑agent settings. | | `handoffInputFilter` | `HandoffInputFilter` | Mutates input items when performing handoffs (if the handoff itself doesn’t already define one). | | `inputGuardrails` | `InputGuardrail[]` | Guardrails applied to the _initial_ user input. | | `outputGuardrails` | `OutputGuardrail[]` | Guardrails applied to the _final_ output. | | `tracingDisabled` | `boolean` | Disable OpenAI Tracing completely. | | `traceIncludeSensitiveData` | `boolean` | Exclude LLM/tool inputs & outputs from traces while still emitting spans. | | `workflowName` | `string` | Appears in the Traces dashboard – helps group related runs. | | `traceId` / `groupId` | `string` | Manually specify the trace or group ID instead of letting the SDK generate one. | | `traceMetadata` | `Record` | Arbitrary metadata to attach to every span. | Conversations / chat threads ---------------------------- [Section titled “Conversations / chat threads”](https://openai.github.io/openai-agents-js/guides/running-agents/#conversations--chat-threads) Each call to `runner.run()` (or `run()` utility) represents one **turn** in your application-level conversation. You choose how much of the `RunResult` you show the end‑user – sometimes only `finalOutput`, other times every generated item. import { Agent, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents'; let thread: AgentInputItem[] = []; const agent = new Agent({ name: 'Assistant',}); async function userSays(text: string) { const result = await run( agent, thread.concat({ role: 'user', content: text }), ); thread = result.history; // Carry over history + newly generated items return result.finalOutput;} await userSays('What city is the Golden Gate Bridge in?');// -> "San Francisco" await userSays('What state is it in?');// -> "California" See [the chat example](https://github.com/openai/openai-agents-js/tree/main/examples/basic/chat.ts) for an interactive version. ### Server-managed conversations [Section titled “Server-managed conversations”](https://openai.github.io/openai-agents-js/guides/running-agents/#server-managed-conversations) You can let the OpenAI Responses API persist conversation history for you instead of sending your entire local transcript on every turn. This is useful when you are coordinating long conversations or multiple services. See the [Conversation state guide](https://platform.openai.com/docs/guides/conversation-state?api-mode=responses) for details. OpenAI exposes two ways to reuse server-side state: #### 1\. `conversationId` for an entire conversation [Section titled “1. conversationId for an entire conversation”](https://openai.github.io/openai-agents-js/guides/running-agents/#1-conversationid-for-an-entire-conversation) You can create a conversation once using [Conversations API](https://platform.openai.com/docs/api-reference/conversations/create) and then reuse its ID for every turn. The SDK automatically includes only the newly generated items. import { Agent, run } from '@openai/agents';import { OpenAI } from 'openai'; const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',}); async function main() { // Create a server-managed conversation: const client = new OpenAI(); const { id: conversationId } = await client.conversations.create({}); const first = await run(agent, 'What city is the Golden Gate Bridge in?', { conversationId, }); console.log(first.finalOutput); // -> "San Francisco" const second = await run(agent, 'What state is it in?', { conversationId }); console.log(second.finalOutput); // -> "California"} main().catch(console.error); #### 2\. `previousResponseId` to continue from the last turn [Section titled “2. previousResponseId to continue from the last turn”](https://openai.github.io/openai-agents-js/guides/running-agents/#2-previousresponseid-to-continue-from-the-last-turn) If you want to start only with Responses API anyway, you can chain each request using the ID returned from the previous response. This keeps the context alive across turns without creating a full conversation resource. import { Agent, run } from '@openai/agents'; const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',}); async function main() { const first = await run(agent, 'What city is the Golden Gate Bridge in?'); console.log(first.finalOutput); // -> "San Francisco" const previousResponseId = first.lastResponseId; const second = await run(agent, 'What state is it in?', { previousResponseId, }); console.log(second.finalOutput); // -> "California"} main().catch(console.error); Exceptions ---------- [Section titled “Exceptions”](https://openai.github.io/openai-agents-js/guides/running-agents/#exceptions) The SDK throws a small set of errors you can catch: * [`MaxTurnsExceededError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/maxturnsexceedederror) – `maxTurns` reached. * [`ModelBehaviorError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/modelbehaviorerror) – model produced invalid output (e.g. malformed JSON, unknown tool). * [`InputGuardrailTripwireTriggered`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/inputguardrailtripwiretriggered) / [`OutputGuardrailTripwireTriggered`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/outputguardrailtripwiretriggered) – guardrail violations. * [`GuardrailExecutionError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/guardrailexecutionerror) – guardrails failed to complete. * [`ToolCallError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/toolcallerror) – any of function tool calls failed. * [`UserError`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/usererror) – any error thrown based on configuration or user input. All extend the base `AgentsError` class, which could provide the `state` property to access the current run state. Here is an example code that handles `GuardrailExecutionError`. Because input guardrails only run on the first user input, the example restarts the run with the original input and context. It also shows reusing the saved state to retry output guardrails without calling the model again: import { Agent, GuardrailExecutionError, InputGuardrail, InputGuardrailTripwireTriggered, OutputGuardrail, OutputGuardrailTripwireTriggered, run,} from '@openai/agents';import { z } from 'zod'; // Shared guardrail agent to avoid re-creating it on every fallback run.const guardrailAgent = new Agent({ name: 'Guardrail check', instructions: 'Check if the user is asking you to do their math homework.', outputType: z.object({ isMathHomework: z.boolean(), reasoning: z.string(), }),}); async function main() { const input = 'Hello, can you help me solve for x: 2x + 3 = 11?'; const context = { customerId: '12345' }; // Input guardrail example const unstableInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (unstable)', execute: async () => { throw new Error('Something is wrong!'); }, }; const fallbackInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (fallback)', execute: async ({ input, context }) => { const result = await run(guardrailAgent, input, { context }); const isMathHomework = result.finalOutput?.isMathHomework ?? /solve for x|math homework/i.test(JSON.stringify(input)); return { outputInfo: result.finalOutput, tripwireTriggered: isMathHomework, }; }, }; const agent = new Agent({ name: 'Customer support agent', instructions: 'You are a customer support agent. You help customers with their questions.', inputGuardrails: [unstableInputGuardrail], }); try { // Input guardrails only run on the first turn of a run, so retries must start a fresh run. await run(agent, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError) { console.error(`Guardrail execution failed (input): ${e}`); try { agent.inputGuardrails = [fallbackInputGuardrail]; // Retry from scratch with the original input and context. await run(agent, input, { context }); } catch (ee) { if (ee instanceof InputGuardrailTripwireTriggered) { console.log('Math homework input guardrail tripped on retry'); } else { throw ee; } } } else { throw e; } } // Output guardrail example const replyOutputSchema = z.object({ reply: z.string() }); const unstableOutputGuardrail: OutputGuardrail = { name: 'Answer review (unstable)', execute: async () => { throw new Error('Output guardrail crashed.'); }, }; const fallbackOutputGuardrail: OutputGuardrail = { name: 'Answer review (fallback)', execute: async ({ agentOutput }) => { const outputText = typeof agentOutput === 'string' ? agentOutput : (agentOutput?.reply ?? JSON.stringify(agentOutput)); const flagged = /math homework|solve for x|x =/i.test(outputText); return { outputInfo: { flaggedOutput: outputText }, tripwireTriggered: flagged, }; }, }; const agent2 = new Agent({ name: 'Customer support agent (output check)', instructions: 'You are a customer support agent. Answer briefly.', outputType: replyOutputSchema, outputGuardrails: [unstableOutputGuardrail], }); try { await run(agent2, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError && e.state) { console.error(`Guardrail execution failed (output): ${e}`); try { agent2.outputGuardrails = [fallbackOutputGuardrail]; // Output guardrails can be retried using the saved state without another model call. await run(agent2, e.state); } catch (ee) { if (ee instanceof OutputGuardrailTripwireTriggered) { console.log('Output guardrail tripped after retry with saved state'); } else { throw ee; } } } else { throw e; } }} main().catch(console.error); Input vs. output retries: * Input guardrails run only on the very first user input of a run, so you must start a fresh run with the same input/context to retry them—passing a saved `state` will not re-trigger input guardrails. * Output guardrails run after the model response, so you can reuse the saved `state` from a `GuardrailExecutionError` to rerun output guardrails without another model call. When you run the above example, you will see the following output: Guardrail execution failed (input): Error: Input guardrail failed to complete: Error: Something is wrong!Math homework input guardrail tripped on retryGuardrail execution failed (output): Error: Output guardrail failed to complete: Error: Output guardrail crashed.Output guardrail tripped after retry with saved state * * * Next steps ---------- [Section titled “Next steps”](https://openai.github.io/openai-agents-js/guides/running-agents/#next-steps) * Learn how to [configure models](https://openai.github.io/openai-agents-js/guides/models) . * Provide your agents with [tools](https://openai.github.io/openai-agents-js/guides/tools) . * Add [guardrails](https://openai.github.io/openai-agents-js/guides/guardrails) or [tracing](https://openai.github.io/openai-agents-js/guides/tracing) for production readiness. --- # Streaming | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/streaming/#_top) Streaming ========= The Agents SDK can deliver output from the model and other execution steps incrementally. Streaming keeps your UI responsive and avoids waiting for the entire final result before updating the user. Enabling streaming ------------------ [Section titled “Enabling streaming”](https://openai.github.io/openai-agents-js/guides/streaming/#enabling-streaming) Pass a `{ stream: true }` option to `Runner.run()` to obtain a streaming object rather than a full result: import { Agent, run } from '@openai/agents'; const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',}); const result = await run(agent, 'Tell me a story about a cat.', { stream: true,}); When streaming is enabled the returned `stream` implements the `AsyncIterable` interface. Each yielded event is an object describing what happened within the run. The stream yields one of three event types, each describing a different part of the agent’s execution. Most applications only want the model’s text though, so the stream provides helpers. ### Get the text output [Section titled “Get the text output”](https://openai.github.io/openai-agents-js/guides/streaming/#get-the-text-output) Call `stream.toTextStream()` to obtain a stream of the emitted text. When `compatibleWithNodeStreams` is `true` the return value is a regular Node.js `Readable`. We can pipe it directly into `process.stdout` or another destination. import { Agent, run } from '@openai/agents'; const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',}); const result = await run(agent, 'Tell me a story about a cat.', { stream: true,}); result .toTextStream({ compatibleWithNodeStreams: true, }) .pipe(process.stdout); The promise `stream.completed` resolves once the run and all pending callbacks are completed. Always await it if you want to ensure there is no more output. ### Listen to all events [Section titled “Listen to all events”](https://openai.github.io/openai-agents-js/guides/streaming/#listen-to-all-events) You can use a `for await` loop to inspect each event as it arrives. Useful information includes low level model events, any agent switches and SDK specific run information: import { Agent, run } from '@openai/agents'; const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',}); const result = await run(agent, 'Tell me a story about a cat.', { stream: true,}); for await (const event of result) { // these are the raw events from the model if (event.type === 'raw_model_stream_event') { console.log(`${event.type} %o`, event.data); } // agent updated events if (event.type === 'agent_updated_stream_event') { console.log(`${event.type} %s`, event.agent.name); } // Agent SDK specific events if (event.type === 'run_item_stream_event') { console.log(`${event.type} %o`, event.item); }} See [the streamed example](https://github.com/openai/openai-agents-js/tree/main/examples/agent-patterns/streamed.ts) for a fully worked script that prints both the plain text stream and the raw event stream. Event types ----------- [Section titled “Event types”](https://openai.github.io/openai-agents-js/guides/streaming/#event-types) The stream yields three different event types: ### raw\_model\_stream\_event [Section titled “raw\_model\_stream\_event”](https://openai.github.io/openai-agents-js/guides/streaming/#raw_model_stream_event) type RunRawModelStreamEvent = { type: 'raw_model_stream_event'; data: ResponseStreamEvent;}; Example: { "type": "raw_model_stream_event", "data": { "type": "output_text_delta", "delta": "Hello" }} ### run\_item\_stream\_event [Section titled “run\_item\_stream\_event”](https://openai.github.io/openai-agents-js/guides/streaming/#run_item_stream_event) type RunItemStreamEvent = { type: 'run_item_stream_event'; name: RunItemStreamEventName; item: RunItem;}; Example handoff payload: { "type": "run_item_stream_event", "name": "handoff_occurred", "item": { "type": "handoff_call", "id": "h1", "status": "completed", "name": "transfer_to_refund_agent" }} ### agent\_updated\_stream\_event [Section titled “agent\_updated\_stream\_event”](https://openai.github.io/openai-agents-js/guides/streaming/#agent_updated_stream_event) type RunAgentUpdatedStreamEvent = { type: 'agent_updated_stream_event'; agent: Agent;}; Example: { "type": "agent_updated_stream_event", "agent": { "name": "Refund Agent" }} Human in the loop while streaming --------------------------------- [Section titled “Human in the loop while streaming”](https://openai.github.io/openai-agents-js/guides/streaming/#human-in-the-loop-while-streaming) Streaming is compatible with handoffs that pause execution (for example when a tool requires approval). The `interruption` field on the stream object exposes the interruptions, and you can continue execution by calling `state.approve()` or `state.reject()` for each of them. Executing again with `{ stream: true }` resumes streaming output. import { Agent, run } from '@openai/agents'; const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',}); let stream = await run( agent, 'What is the weather in San Francisco and Oakland?', { stream: true },);stream.toTextStream({ compatibleWithNodeStreams: true }).pipe(process.stdout);await stream.completed; while (stream.interruptions?.length) { console.log( 'Human-in-the-loop: approval required for the following tool calls:', ); const state = stream.state; for (const interruption of stream.interruptions) { const approved = confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`, ); if (approved) { state.approve(interruption); } else { state.reject(interruption); } } // Resume execution with streaming output stream = await run(agent, state, { stream: true }); const textStream = stream.toTextStream({ compatibleWithNodeStreams: true }); textStream.pipe(process.stdout); await stream.completed;} A fuller example that interacts with the user is [`human-in-the-loop-stream.ts`](https://github.com/openai/openai-agents-js/tree/main/examples/agent-patterns/human-in-the-loop-stream.ts) . Tips ---- [Section titled “Tips”](https://openai.github.io/openai-agents-js/guides/streaming/#tips) * Remember to wait for `stream.completed` before exiting to ensure all output has been flushed. * The initial `{ stream: true }` option only applies to the call where it is provided. If you re-run with a `RunState` you must specify the option again. * If your application only cares about the textual result prefer `toTextStream()` to avoid dealing with individual event objects. With streaming and the event system you can integrate an agent into a chat interface, terminal application or any place where users benefit from incremental updates. --- # Configuring the SDK | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/config/#_top) Configuring the SDK =================== API keys and clients -------------------- [Section titled “API keys and clients”](https://openai.github.io/openai-agents-js/guides/config/#api-keys-and-clients) By default the SDK reads the `OPENAI_API_KEY` environment variable when first imported. If setting the variable is not possible you can call `setDefaultOpenAIKey()` manually. import { setDefaultOpenAIKey } from '@openai/agents'; setDefaultOpenAIKey(process.env.OPENAI_API_KEY!); // sk-... You may also pass your own `OpenAI` client instance. The SDK will otherwise create one automatically using the default key. import { OpenAI } from 'openai';import { setDefaultOpenAIClient } from '@openai/agents'; const customClient = new OpenAI({ baseURL: '...', apiKey: '...' });setDefaultOpenAIClient(customClient); Finally you can switch between the Responses API and the Chat Completions API. import { setOpenAIAPI } from '@openai/agents'; setOpenAIAPI('chat_completions'); Tracing ------- [Section titled “Tracing”](https://openai.github.io/openai-agents-js/guides/config/#tracing) Tracing is enabled by default and uses the OpenAI key from the section above. A separate key may be set via `setTracingExportApiKey()`: import { setTracingExportApiKey } from '@openai/agents'; setTracingExportApiKey('sk-...'); Tracing can also be disabled entirely: import { setTracingDisabled } from '@openai/agents'; setTracingDisabled(true); If you’d like to learn more about the tracing feature, please check out [Tracing guide](https://openai.github.io/openai-agents-js/guides/tracing) . Debug logging ------------- [Section titled “Debug logging”](https://openai.github.io/openai-agents-js/guides/config/#debug-logging) The SDK uses the [`debug`](https://www.npmjs.com/package/debug) package for debug logging. Set the `DEBUG` environment variable to `openai-agents*` to see verbose logs. export DEBUG=openai-agents* You can obtain a namespaced logger for your own modules using `getLogger(namespace)` from `@openai/agents`. import { getLogger } from '@openai/agents'; const logger = getLogger('my-app');logger.debug('something happened'); ### Sensitive data in logs [Section titled “Sensitive data in logs”](https://openai.github.io/openai-agents-js/guides/config/#sensitive-data-in-logs) Certain logs may contain user data. Disable them by setting these environment variables. To disable logging LLM inputs and outputs: export OPENAI_AGENTS_DONT_LOG_MODEL_DATA=1 To disable logging tool inputs and outputs: export OPENAI_AGENTS_DONT_LOG_TOOL_DATA=1 --- # Troubleshooting | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/troubleshooting/#_top) Troubleshooting =============== Supported environments ---------------------- [Section titled “Supported environments”](https://openai.github.io/openai-agents-js/guides/troubleshooting/#supported-environments) The OpenAI Agents SDK is supported on the following server environments: * Node.js 22+ * Deno 2.35+ * Bun 1.2.5+ ### Limited support [Section titled “Limited support”](https://openai.github.io/openai-agents-js/guides/troubleshooting/#limited-support) * **Cloudflare Workers**: The Agents SDK can be used in Cloudflare Workers, but currently comes with some limitations: * The SDK current requires `nodejs_compat` to be enabled * Traces need to be manually flushed at the end of the request. [See the tracing guide](https://openai.github.io/openai-agents-js/guides/tracing#export-loop-lifecycle) for more details. * Due to Cloudflare Workers’ limited support for `AsyncLocalStorage` some traces might not be accurate * Outbound WebSocket connections must use a fetch-based upgrade (not the global `WebSocket` constructor). For Realtime, use the Cloudflare transport in `@openai/agents-extensions` (`CloudflareRealtimeTransportLayer`). * **Browsers**: * Tracing is currently not supported in browsers * **v8 isolates**: * While you should be able to bundle the SDK for v8 isolates if you use a bundler with the right browser polyfills, tracing will not work * v8 isolates have not been extensively tested Debug logging ------------- [Section titled “Debug logging”](https://openai.github.io/openai-agents-js/guides/troubleshooting/#debug-logging) If you are running into problems with the SDK, you can enable debug logging to get more information about what is happening. Enable debug logging by setting the `DEBUG` environment variable to `openai-agents:*`. DEBUG=openai-agents:* Alternatively, you can scope the debugging to specific parts of the SDK: * `openai-agents:core` — for the main execution logic of the SDK * `openai-agents:openai` — for the OpenAI API calls * `openai-agents:realtime` — for the Realtime Agents components --- # Guardrails | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/guardrails/#_top) Guardrails ========== Guardrails can run alongside your agents or block execution until they complete, allowing you to perform checks and validations on user input or agent output. For example, you may run a lightweight model as a guardrail before invoking an expensive model. If the guardrail detects malicious usage, it can trigger an error and stop the costly model from running. There are two kinds of guardrails: 1. **Input guardrails** run on the initial user input. 2. **Output guardrails** run on the final agent output. Input guardrails ---------------- [Section titled “Input guardrails”](https://openai.github.io/openai-agents-js/guides/guardrails/#input-guardrails) Input guardrails run in three steps: 1. The guardrail receives the same input passed to the agent. 2. The guardrail function executes and returns a [`GuardrailFunctionOutput`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/guardrailfunctionoutput) wrapped inside an [`InputGuardrailResult`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/inputguardrailresult) . 3. If `tripwireTriggered` is `true`, an [`InputGuardrailTripwireTriggered`](https://openai.github.io/openai-agents-js/openai/agents/classes/inputguardrailtripwiretriggered) error is thrown. > **Note** Input guardrails are intended for user input, so they only run if the agent is the _first_ agent in the workflow. Guardrails are configured on the agent itself because different agents often require different guardrails. ### Execution modes [Section titled “Execution modes”](https://openai.github.io/openai-agents-js/guides/guardrails/#execution-modes) * `runInParallel: true` (default) starts guardrails alongside the LLM/tool calls. This minimizes latency but the model may already have consumed tokens or run tools if the guardrail later triggers. * `runInParallel: false` runs the guardrail **before** calling the model, preventing token spend and tool execution when the guardrail blocks the request. Use this when you prefer safety and cost over latency. Output guardrails ----------------- [Section titled “Output guardrails”](https://openai.github.io/openai-agents-js/guides/guardrails/#output-guardrails) Output guardrails run in 3 steps: 1. The guardrail receives the output produced by the agent. 2. The guardrail function executes and returns a [`GuardrailFunctionOutput`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/guardrailfunctionoutput) wrapped inside an [`OutputGuardrailResult`](https://openai.github.io/openai-agents-js/openai/agents/interfaces/outputguardrailresult) . 3. If `tripwireTriggered` is `true`, an [`OutputGuardrailTripwireTriggered`](https://openai.github.io/openai-agents-js/openai/agents/classes/outputguardrailtripwiretriggered) error is thrown. > **Note** Output guardrails only run if the agent is the _last_ agent in the workflow. For realtime voice interactions see [the voice agents guide](https://openai.github.io/openai-agents-js/guides/voice-agents/build#guardrails) > . Tripwires --------- [Section titled “Tripwires”](https://openai.github.io/openai-agents-js/guides/guardrails/#tripwires) When a guardrail fails, it signals this via a tripwire. As soon as a tripwire is triggered, the runner throws the corresponding error and halts execution. Implementing a guardrail ------------------------ [Section titled “Implementing a guardrail”](https://openai.github.io/openai-agents-js/guides/guardrails/#implementing-a-guardrail) A guardrail is simply a function that returns a `GuardrailFunctionOutput`. Below is a minimal example that checks whether the user is asking for math homework help by running another agent under the hood. import { Agent, run, InputGuardrailTripwireTriggered, InputGuardrail,} from '@openai/agents';import { z } from 'zod'; const guardrailAgent = new Agent({ name: 'Guardrail check', instructions: 'Check if the user is asking you to do their math homework.', outputType: z.object({ isMathHomework: z.boolean(), reasoning: z.string(), }),}); const mathGuardrail: InputGuardrail = { name: 'Math Homework Guardrail', // Set runInParallel to false to block the model until the guardrail completes. runInParallel: false, execute: async ({ input, context }) => { const result = await run(guardrailAgent, input, { context }); return { outputInfo: result.finalOutput, tripwireTriggered: result.finalOutput?.isMathHomework === false, }; },}; const agent = new Agent({ name: 'Customer support agent', instructions: 'You are a customer support agent. You help customers with their questions.', inputGuardrails: [mathGuardrail],}); async function main() { try { await run(agent, 'Hello, can you help me solve for x: 2x + 3 = 11?'); console.log("Guardrail didn't trip - this is unexpected"); } catch (e) { if (e instanceof InputGuardrailTripwireTriggered) { console.log('Math homework guardrail tripped'); } }} main().catch(console.error); Output guardrails work the same way. import { Agent, run, OutputGuardrailTripwireTriggered, OutputGuardrail,} from '@openai/agents';import { z } from 'zod'; // The output by the main agentconst MessageOutput = z.object({ response: z.string() });type MessageOutput = z.infer; // The output by the math guardrail agentconst MathOutput = z.object({ reasoning: z.string(), isMath: z.boolean() }); // The guardrail agentconst guardrailAgent = new Agent({ name: 'Guardrail check', instructions: 'Check if the output includes any math.', outputType: MathOutput,}); // An output guardrail using an agent internallyconst mathGuardrail: OutputGuardrail = { name: 'Math Guardrail', async execute({ agentOutput, context }) { const result = await run(guardrailAgent, agentOutput.response, { context, }); return { outputInfo: result.finalOutput, tripwireTriggered: result.finalOutput?.isMath ?? false, }; },}; const agent = new Agent({ name: 'Support agent', instructions: 'You are a user support agent. You help users with their questions.', outputGuardrails: [mathGuardrail], outputType: MessageOutput,}); async function main() { try { const input = 'Hello, can you help me solve for x: 2x + 3 = 11?'; await run(agent, input); console.log("Guardrail didn't trip - this is unexpected"); } catch (e) { if (e instanceof OutputGuardrailTripwireTriggered) { console.log('Math output guardrail tripped'); } }} main().catch(console.error); 1. `guardrailAgent` is used inside the guardrail functions. 2. The guardrail function receives the agent input or output and returns the result. 3. Extra information can be included in the guardrail result. 4. `agent` defines the actual workflow where guardrails are applied. --- # Release process | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/release/#_top) Release process =============== Versioning ---------- [Section titled “Versioning”](https://openai.github.io/openai-agents-js/guides/release/#versioning) The project follows a slightly modified version of semantic versioning using the form `0.Y.Z`. The leading `0` indicates the SDK is still evolving rapidly. Increment the components as follows: Minor (`Y`) versions -------------------- [Section titled “Minor (Y) versions”](https://openai.github.io/openai-agents-js/guides/release/#minor-y-versions) We will increase minor versions `Y` for **breaking changes** to any public interfaces that are not marked as beta. For example, going from `0.0.x` to `0.1.x` might include breaking changes. If you don’t want breaking changes, we recommend pinning to `0.0.x` versions in your project. Patch (`Z`) versions -------------------- [Section titled “Patch (Z) versions”](https://openai.github.io/openai-agents-js/guides/release/#patch-z-versions) We will increment `Z` for non-breaking changes: * Bug fixes * New features * Changes to private interfaces * Updates to beta features Versioning sub-packages ----------------------- [Section titled “Versioning sub-packages”](https://openai.github.io/openai-agents-js/guides/release/#versioning-sub-packages) The main `@openai/agents` package is comprised of multiple sub-packages that can be used independently. At the moment the versions of the packages are linked, meaning if one package receives a version increase, so do the others. We might change this strategy as we move to `1.0.0`. Changelogs ---------- [Section titled “Changelogs”](https://openai.github.io/openai-agents-js/guides/release/#changelogs) We generate changelogs for each of the sub-packages to help understand what has changed. As the changes might have happened in a sub-package, you might have to look in that respective changelog for details on the change. * [`@openai/agents`](https://github.com/openai/openai-agents-js/blob/main/packages/agents/CHANGELOG.md) * [`@openai/agents-core`](https://github.com/openai/openai-agents-js/blob/main/packages/agents-core/CHANGELOG.md) * [`@openai/agents-extensions`](https://github.com/openai/openai-agents-js/blob/main/packages/agents-extensions/CHANGELOG.md) * [`@openai/agents-openai`](https://github.com/openai/openai-agents-js/blob/main/packages/agents-openai/CHANGELOG.md) * [`@openai/agents-realtime`](https://github.com/openai/openai-agents-js/blob/main/packages/agents-realtime/CHANGELOG.md) --- # Tracing | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/tracing/#_top) Tracing ======= The Agents SDK includes built-in tracing, collecting a comprehensive record of events during an agent run: LLM generations, tool calls, handoffs, guardrails, and even custom events that occur. Using the [Traces dashboard](https://platform.openai.com/traces) , you can debug, visualize, and monitor your workflows during development and in production. Export loop lifecycle --------------------- [Section titled “Export loop lifecycle”](https://openai.github.io/openai-agents-js/guides/tracing/#export-loop-lifecycle) In most environments traces will automatically be exported on a regular interval. In the browser or in Cloudflare Workers, this functionality is disabled by default. Traces will still get exported if too many are queued up but they are not exported on a regular interval. Instead you should use `getGlobalTraceProvider().forceFlush()` to manually export the traces as part of your code’s lifecycle. For example, in a Cloudflare Worker, you should wrap your code into a `try/catch/finally` block and use force flush with `waitUntil` to ensure that traces are exported before the worker exits. import { getGlobalTraceProvider } from '@openai/agents'; export default { async fetch(request, env, ctx): Promise { try { // your agent code here return new Response(`success`); } catch (error) { console.error(error); return new Response(String(error), { status: 500 }); } finally { // make sure to flush any remaining traces before exiting ctx.waitUntil(getGlobalTraceProvider().forceFlush()); } },}; Traces and spans ---------------- [Section titled “Traces and spans”](https://openai.github.io/openai-agents-js/guides/tracing/#traces-and-spans) * **Traces** represent a single end-to-end operation of a “workflow”. They’re composed of Spans. Traces have the following properties: * `workflow_name`: This is the logical workflow or app. For example “Code generation” or “Customer service”. * `trace_id`: A unique ID for the trace. Automatically generated if you don’t pass one. Must have the format `trace_<32_alphanumeric>`. * `group_id`: Optional group ID, to link multiple traces from the same conversation. For example, you might use a chat thread ID. * `disabled`: If True, the trace will not be recorded. * `metadata`: Optional metadata for the trace. * **Spans** represent operations that have a start and end time. Spans have: * `started_at` and `ended_at` timestamps. * `trace_id`, to represent the trace they belong to * `parent_id`, which points to the parent Span of this Span (if any) * `span_data`, which is information about the Span. For example, `AgentSpanData` contains information about the Agent, `GenerationSpanData` contains information about the LLM generation, etc. Default tracing --------------- [Section titled “Default tracing”](https://openai.github.io/openai-agents-js/guides/tracing/#default-tracing) By default, the SDK traces the following: * The entire `run()` or `Runner.run()` is wrapped in a `Trace`. * Each time an agent runs, it is wrapped in `AgentSpan` * LLM generations are wrapped in `GenerationSpan` * Function tool calls are each wrapped in `FunctionSpan` * Guardrails are wrapped in `GuardrailSpan` * Handoffs are wrapped in `HandoffSpan` By default, the trace is named “Agent workflow”. You can set this name if you use `withTrace`, or you can can configure the name and other properties with the [`RunConfig.workflowName`](https://openai.github.io/openai-agents-js/openai/agents-core/type-aliases/runconfig/#workflowname) . In addition, you can set up [custom trace processors](https://openai.github.io/openai-agents-js/guides/tracing/#custom-tracing-processors) to push traces to other destinations (as a replacement, or secondary destination). ### Voice agent tracing [Section titled “Voice agent tracing”](https://openai.github.io/openai-agents-js/guides/tracing/#voice-agent-tracing) If you are using `RealtimeAgent` and `RealtimeSession` with the default OpenAI Realtime API, tracing will automatically happen on the Realtime API side unless you disable it on the `RealtimeSession` using `tracingDisabled: true` or using the `OPENAI_AGENTS_DISABLE_TRACING` environment variable. Check out the [Voice agents guide](https://openai.github.io/openai-agents-js/guides/voice-agents) for more details. Higher level traces ------------------- [Section titled “Higher level traces”](https://openai.github.io/openai-agents-js/guides/tracing/#higher-level-traces) Sometimes, you might want multiple calls to `run()` to be part of a single trace. You can do this by wrapping the entire code in a `withTrace()`. import { Agent, run, withTrace } from '@openai/agents'; const agent = new Agent({ name: 'Joke generator', instructions: 'Tell funny jokes.',}); await withTrace('Joke workflow', async () => { const result = await run(agent, 'Tell me a joke'); const secondResult = await run( agent, `Rate this joke: ${result.finalOutput}`, ); console.log(`Joke: ${result.finalOutput}`); console.log(`Rating: ${secondResult.finalOutput}`);}); 1. Because the two calls to `run` are wrapped in a `withTrace()`, the individual runs will be part of the overall trace rather than creating two traces. Creating traces --------------- [Section titled “Creating traces”](https://openai.github.io/openai-agents-js/guides/tracing/#creating-traces) You can use the [`withTrace()`](https://openai.github.io/openai-agents-js/openai/agents-core/functions/withtrace/) function to create a trace. Alternatively, you can use `getGlobalTraceProvider().createTrace()` to create a new trace manually and pass it into `withTrace()`. The current trace is tracked via a [Node.js `AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) or the respective environment polyfills. This means that it works with concurrency automatically. Creating spans -------------- [Section titled “Creating spans”](https://openai.github.io/openai-agents-js/guides/tracing/#creating-spans) You can use the various `create*Span()` (e.g. `createGenerationSpan()`, `createFunctionSpan()`, etc.) methods to create a span. In general, you don’t need to manually create spans. A [`createCustomSpan()`](https://openai.github.io/openai-agents-js/openai/agents-core/functions/createcustomspan/) function is available for tracking custom span information. Spans are automatically part of the current trace, and are nested under the nearest current span, which is tracked via a [Node.js `AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) or the respective environment polyfills. Sensitive data -------------- [Section titled “Sensitive data”](https://openai.github.io/openai-agents-js/guides/tracing/#sensitive-data) Certain spans may capture potentially sensitive data. The `createGenerationSpan()` stores the inputs/outputs of the LLM generation, and `createFunctionSpan()` stores the inputs/outputs of function calls. These may contain sensitive data, so you can disable capturing that data via [`RunConfig.traceIncludeSensitiveData`](https://openai.github.io/openai-agents-js/openai/agents-core/type-aliases/runconfig/#traceincludesensitivedata) . Custom tracing processors ------------------------- [Section titled “Custom tracing processors”](https://openai.github.io/openai-agents-js/guides/tracing/#custom-tracing-processors) The high level architecture for tracing is: * At initialization, we create a global [`TraceProvider`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/traceprovider) , which is responsible for creating traces and can be accessed through [`getGlobalTraceProvider()`](https://openai.github.io/openai-agents-js/openai/agents-core/functions/getglobaltraceprovider/) . * We configure the `TraceProvider` with a [`BatchTraceProcessor`](https://openai.github.io/openai-agents-js/openai/agents-core/classes/batchtraceprocessor/) that sends traces/spans in batches to a [`OpenAITracingExporter`](https://openai.github.io/openai-agents-js/openai/agents-openai/classes/openaitracingexporter/) , which exports the spans and traces to the OpenAI backend in batches. To customize this default setup, to send traces to alternative or additional backends or modifying exporter behavior, you have two options: 1. [`addTraceProcessor()`](https://openai.github.io/openai-agents-js/openai/agents-core/functions/addtraceprocessor) lets you add an **additional** trace processor that will receive traces and spans as they are ready. This lets you do your own processing in addition to sending traces to OpenAI’s backend. 2. [`setTraceProcessors()`](https://openai.github.io/openai-agents-js/openai/agents-core/functions/settraceprocessors) lets you **replace** the default processors with your own trace processors. This means traces will not be sent to the OpenAI backend unless you include a `TracingProcessor` that does so. External tracing processors list -------------------------------- [Section titled “External tracing processors list”](https://openai.github.io/openai-agents-js/guides/tracing/#external-tracing-processors-list) * [AgentOps](https://docs.agentops.ai/v2/usage/typescript-sdk#openai-agents-integration) * [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agents-sdk-js) --- # Voice Agents | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/voice-agents/#_top) Voice Agents ============ ![Realtime Agents](https://cdn.openai.com/API/docs/images/diagram-speech-to-speech.png) Voice Agents use OpenAI speech-to-speech models to provide realtime voice chat. These models support streaming audio, text, and tool calls and are great for applications like voice/phone customer support, mobile app experiences, and voice chat. The Voice Agents SDK provides a TypeScript client for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime) . [Voice Agents Quickstart](https://openai.github.io/openai-agents-js/guides/voice-agents/quickstart) Build your first realtime voice assistant using the OpenAI Agents SDK in minutes. ### Key features [Section titled “Key features”](https://openai.github.io/openai-agents-js/guides/voice-agents/#key-features) * Connect over WebSocket or WebRTC * Can be used both in the browser and for backend connections * Audio and interruption handling * Multi-agent orchestration through handoffs * Tool definition and calling * Custom guardrails to monitor model output * Callbacks for streamed events * Reuse the same components for both text and voice agents By using speech-to-speech models, we can leverage the model’s ability to process the audio in realtime without the need of transcribing and reconverting the text back to audio after the model acted. ![Speech-to-speech model](https://cdn.openai.com/API/docs/images/diagram-chained-agent.png) --- # Model Context Protocol (MCP) | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/mcp/#_top) Model Context Protocol (MCP) ============================ The [**Model Context Protocol (MCP)**](https://modelcontextprotocol.io/) is an open protocol that standardizes how applications provide tools and context to LLMs. From the MCP docs: > MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools. There are three types of MCP servers this SDK supports: 1. **Hosted MCP server tools** – remote MCP servers used as tools by the [OpenAI Responses API](https://platform.openai.com/docs/guides/tools-remote-mcp) 2. **Streamable HTTP MCP servers** – local or remote servers that implement the [Streamable HTTP transport](https://modelcontextprotocol.io/docs/concepts/transports#streamable-http) 3. **Stdio MCP servers** – servers accessed via standard input/output (the simplest option) Choose a server type based on your use‑case: | What you need | Recommended option | | --- | --- | | Call publicly accessible remote servers with default OpenAI responses models | **1\. Hosted MCP tools** | | Use publicly accessible remote servers but have the tool calls triggered locally | **2\. Streamable HTTP** | | Use locally running Streamable HTTP servers | **2\. Streamable HTTP** | | Use any Streamable HTTP servers with non-OpenAI-Responses models | **2\. Streamable HTTP** | | Work with local MCP servers that only support the standard-I/O protocol | **3\. Stdio** | 1\. Hosted MCP server tools --------------------------- [Section titled “1. Hosted MCP server tools”](https://openai.github.io/openai-agents-js/guides/mcp/#1-hosted-mcp-server-tools) Hosted tools push the entire round‑trip into the model. Instead of your code calling an MCP server, the OpenAI Responses API invokes the remote tool endpoint and streams the result back to the model. Here is the simplest example of using hosted MCP tools. You can pass the remote MCP server’s label and URL to the `hostedMcpTool` utility function, which is helpful for creating hosted MCP server tools. import { Agent, hostedMcpTool } from '@openai/agents'; export const agent = new Agent({ name: 'MCP Assistant', instructions: 'You must always use the MCP tools to answer questions.', tools: [ hostedMcpTool({ serverLabel: 'gitmcp', serverUrl: 'https://gitmcp.io/openai/codex', }), ],}); Then, you can run the Agent with the `run` function (or your own customized `Runner` instance’s `run` method): import { run } from '@openai/agents';import { agent } from './hostedAgent'; async function main() { const result = await run( agent, 'Which language is the repo I pointed in the MCP tool settings written in?', ); console.log(result.finalOutput);} main().catch(console.error); To stream incremental MCP results, pass `stream: true` when you run the `Agent`: import { run } from '@openai/agents';import { agent } from './hostedAgent'; async function main() { const result = await run( agent, 'Which language is the repo I pointed in the MCP tool settings written in?', { stream: true }, ); for await (const event of result) { if ( event.type === 'raw_model_stream_event' && event.data.type === 'model' && event.data.event.type !== 'response.mcp_call_arguments.delta' && event.data.event.type !== 'response.output_text.delta' ) { console.log(`Got event of type ${JSON.stringify(event.data)}`); } } console.log(`Done streaming; final result: ${result.finalOutput}`);} main().catch(console.error); #### Optional approval flow [Section titled “Optional approval flow”](https://openai.github.io/openai-agents-js/guides/mcp/#optional-approval-flow) For sensitive operations you can require human approval of individual tool calls. Pass either `requireApproval: 'always'` or a fine‑grained object mapping tool names to `'never'`/`'always'`. If you can programmatically determine whether a tool call is safe, you can use the [`onApproval` callback](https://github.com/openai/openai-agents-js/blob/main/examples/mcp/hosted-mcp-on-approval.ts) to approve or reject the tool call. If you require human approval, you can use the same [human-in-the-loop (HITL) approach](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/) using `interruptions` as for local function tools. import { Agent, run, hostedMcpTool, RunToolApprovalItem } from '@openai/agents'; async function main(): Promise { const agent = new Agent({ name: 'MCP Assistant', instructions: 'You must always use the MCP tools to answer questions.', tools: [ hostedMcpTool({ serverLabel: 'gitmcp', serverUrl: 'https://gitmcp.io/openai/codex', // 'always' | 'never' | { never, always } requireApproval: { never: { toolNames: ['search_codex_code', 'fetch_codex_documentation'], }, always: { toolNames: ['fetch_generic_url_content'], }, }, }), ], }); let result = await run(agent, 'Which language is this repo written in?'); while (result.interruptions && result.interruptions.length) { for (const interruption of result.interruptions) { // Human in the loop here const approval = await confirm(interruption); if (approval) { result.state.approve(interruption); } else { result.state.reject(interruption); } } result = await run(agent, result.state); } console.log(result.finalOutput);} import { stdin, stdout } from 'node:process';import * as readline from 'node:readline/promises'; async function confirm(item: RunToolApprovalItem): Promise { const rl = readline.createInterface({ input: stdin, output: stdout }); const name = item.name; const params = item.arguments; const answer = await rl.question( `Approve running tool (mcp: ${name}, params: ${params})? (y/n) `, ); rl.close(); return answer.toLowerCase().trim() === 'y';} main().catch(console.error); ### Connector-backed hosted servers [Section titled “Connector-backed hosted servers”](https://openai.github.io/openai-agents-js/guides/mcp/#connector-backed-hosted-servers) Hosted MCP also supports OpenAI connectors. Instead of providing a `serverUrl`, pass the connector’s `connectorId` and an `authorization` token. The Responses API then handles authentication and exposes the connector’s tools through the hosted MCP interface. import { Agent, hostedMcpTool } from '@openai/agents'; const authorization = process.env.GOOGLE_CALENDAR_AUTHORIZATION!; export const connectorAgent = new Agent({ name: 'Calendar Assistant', instructions: "You are a helpful assistant that can answer questions about the user's calendar.", tools: [ hostedMcpTool({ serverLabel: 'google_calendar', connectorId: 'connector_googlecalendar', authorization, requireApproval: 'never', }), ],}); In this example the `GOOGLE_CALENDAR_AUTHORIZATION` environment variable holds an OAuth token obtained from the Google OAuth Playground, which authorizes the connector-backed server to call the Calendar API. For a runnable sample that also demonstrates streaming, see [`examples/connectors`](https://github.com/openai/openai-agents-js/tree/main/examples/connectors) . Fully working samples (Hosted tools/Streamable HTTP/stdio + Streaming, HITL, onApproval) are [examples/mcp](https://github.com/openai/openai-agents-js/tree/main/examples/mcp) in our GitHub repository. 2\. Streamable HTTP MCP servers ------------------------------- [Section titled “2. Streamable HTTP MCP servers”](https://openai.github.io/openai-agents-js/guides/mcp/#2-streamable-http-mcp-servers) When your Agent talks directly to a Streamable HTTP MCP server—local or remote—instantiate `MCPServerStreamableHttp` with the server `url`, `name`, and any optional settings: import { Agent, run, MCPServerStreamableHttp } from '@openai/agents'; async function main() { const mcpServer = new MCPServerStreamableHttp({ url: 'https://gitmcp.io/openai/codex', name: 'GitMCP Documentation Server', }); const agent = new Agent({ name: 'GitMCP Assistant', instructions: 'Use the tools to respond to user requests.', mcpServers: [mcpServer], }); try { await mcpServer.connect(); const result = await run(agent, 'Which language is this repo written in?'); console.log(result.finalOutput); } finally { await mcpServer.close(); }} main().catch(console.error); The constructor also accepts additional MCP TypeScript‑SDK options such as `authProvider`, `requestInit`, `fetch`, `reconnectionOptions`, and `sessionId`. See the [MCP TypeScript SDK repository](https://github.com/modelcontextprotocol/typescript-sdk) and its documents for details. 3\. Stdio MCP servers --------------------- [Section titled “3. Stdio MCP servers”](https://openai.github.io/openai-agents-js/guides/mcp/#3-stdio-mcp-servers) For servers that expose only standard I/O, instantiate `MCPServerStdio` with a `fullCommand`: import { Agent, run, MCPServerStdio } from '@openai/agents';import * as path from 'node:path'; async function main() { const samplesDir = path.join(__dirname, 'sample_files'); const mcpServer = new MCPServerStdio({ name: 'Filesystem MCP Server, via npx', fullCommand: `npx -y @modelcontextprotocol/server-filesystem ${samplesDir}`, }); await mcpServer.connect(); try { const agent = new Agent({ name: 'FS MCP Assistant', instructions: 'Use the tools to read the filesystem and answer questions based on those files. If you are unable to find any files, you can say so instead of assuming they exist.', mcpServers: [mcpServer], }); const result = await run(agent, 'Read the files and list them.'); console.log(result.finalOutput); } finally { await mcpServer.close(); }} main().catch(console.error); Other things to know -------------------- [Section titled “Other things to know”](https://openai.github.io/openai-agents-js/guides/mcp/#other-things-to-know) For **Streamable HTTP** and **Stdio** servers, each time an `Agent` runs it may call `list_tools()` to discover available tools. Because that round‑trip can add latency—especially to remote servers—you can cache the results in memory by passing `cacheToolsList: true` to `MCPServerStdio` or `MCPServerStreamableHttp`. Only enable this if you’re confident the tool list won’t change. To invalidate the cache later, call `invalidateToolsCache()` on the server instance. ### Tool filtering [Section titled “Tool filtering”](https://openai.github.io/openai-agents-js/guides/mcp/#tool-filtering) You can restrict which tools are exposed from each server by passing either a static filter via `createMCPToolStaticFilter` or a custom function. Here’s a combined example showing both approaches: import { MCPServerStdio, MCPServerStreamableHttp, createMCPToolStaticFilter, MCPToolFilterContext,} from '@openai/agents'; interface ToolFilterContext { allowAll: boolean;} const server = new MCPServerStdio({ fullCommand: 'my-server', toolFilter: createMCPToolStaticFilter({ allowed: ['safe_tool'], blocked: ['danger_tool'], }),}); const dynamicServer = new MCPServerStreamableHttp({ url: 'http://localhost:3000', toolFilter: async ({ runContext }: MCPToolFilterContext, tool) => (runContext.context as ToolFilterContext).allowAll || tool.name !== 'admin',}); Further reading --------------- [Section titled “Further reading”](https://openai.github.io/openai-agents-js/guides/mcp/#further-reading) * [Model Context Protocol](https://modelcontextprotocol.io/) – official specification. * [examples/mcp](https://github.com/openai/openai-agents-js/tree/main/examples/mcp) – runnable demos referenced above. --- # Voice Agents Quickstart | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/voice-agents/quickstart/#_top) Voice Agents Quickstart ======================= 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) . npm create vite@latest my-project -- --template vanilla-ts 1. **Install the Agents SDK** 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. 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`](https://openai.github.io/openai-agents-js/openai/agents-realtime/classes/realtimeagent/) is very similar to creating a regular [`Agent`](https://openai.github.io/openai-agents-js/guides/agents) . 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. 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. 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](https://openai.github.io/openai-agents-js/guides/voice-agents/transport) guide. 6. **Putting it all together** import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; export async function setupCounter(element: HTMLButtonElement) { // .... // for quickly start, you can append the following code to the auto-generated TS code const agent = new RealtimeAgent({ name: 'Assistant', instructions: 'You are a helpful assistant.', }); const session = new RealtimeSession(agent); // Automatically connects your microphone and audio output in the browser via WebRTC. try { await session.connect({ // To get this ephemeral key string, you can run the following command or implement the equivalent on the server side: // curl -s -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"}}' | jq .value apiKey: 'ek_...(put your own key here)', }); console.log('You are connected!'); } catch (e) { console.error(e); }} 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. npm run dev Next Steps ---------- [Section titled “Next Steps”](https://openai.github.io/openai-agents-js/guides/voice-agents/quickstart/#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](https://openai.github.io/openai-agents-js/guides/voice-agents/build#tools) * [Handoffs](https://openai.github.io/openai-agents-js/guides/voice-agents/build#handoffs) * [Guardrails](https://openai.github.io/openai-agents-js/guides/voice-agents/build#guardrails) * [Handle audio interruptions](https://openai.github.io/openai-agents-js/guides/voice-agents/build#audio-interruptions) * [Manage session history](https://openai.github.io/openai-agents-js/guides/voice-agents/build#session-history) * Learn more about the different transport layers. * [WebRTC](https://openai.github.io/openai-agents-js/guides/voice-agents/transport#connecting-over-webrtc) * [WebSocket](https://openai.github.io/openai-agents-js/guides/voice-agents/transport#connecting-over-websocket) * [Building your own transport mechanism](https://openai.github.io/openai-agents-js/guides/voice-agents/transport#building-your-own-transport-mechanism) --- # Using any model with the Vercel's AI SDK | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/extensions/ai-sdk/#_top) Using any model with the Vercel's AI SDK ======================================== Out of the box the Agents SDK works with OpenAI models through the Responses API or Chat Completions API. However, if you would like to use another model, the [Vercel’s AI SDK](https://sdk.vercel.ai/) offers a range of supported models that can be brought into the Agents SDK through this adapter. Setup ----- [Section titled “Setup”](https://openai.github.io/openai-agents-js/extensions/ai-sdk/#setup) 1. Install the AI SDK adapter by installing the extensions package: npm install @openai/agents-extensions 2. Choose your desired model package from the [Vercel’s AI SDK](https://ai-sdk.dev/docs/foundations/providers-and-models) and install it: npm install @ai-sdk/openai 3. Import the adapter and model to connect to your agent: import { openai } from '@ai-sdk/openai';import { aisdk } from '@openai/agents-extensions'; 4. Initialize an instance of the model to be used by the agent: const model = aisdk(openai('gpt-5-mini')); Example ------- [Section titled “Example”](https://openai.github.io/openai-agents-js/extensions/ai-sdk/#example) import { Agent, run } from '@openai/agents'; // Import the model package you installedimport { openai } from '@ai-sdk/openai'; // Import the adapterimport { aisdk } from '@openai/agents-extensions'; // Create a model instance to be used by the agentconst model = aisdk(openai('gpt-5-mini')); // Create an agent with the modelconst agent = new Agent({ name: 'My Agent', instructions: 'You are a helpful assistant.', model,}); // Run the agent with the new modelrun(agent, 'What is the capital of Germany?'); Passing provider metadata ------------------------- [Section titled “Passing provider metadata”](https://openai.github.io/openai-agents-js/extensions/ai-sdk/#passing-provider-metadata) If you need to send provider-specific options with a message, pass them through `providerMetadata`. The values are forwarded directly to the underlying AI SDK model. For example, the following `providerData` in the Agents SDK providerData: { anthropic: { cacheControl: { type: 'ephemeral'; } }} would become providerMetadata: { anthropic: { cacheControl: { type: 'ephemeral'; } }} when using the AI SDK integration. --- # Realtime Transport Layer | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#_top) Realtime Transport Layer ======================== Default transport layers ------------------------ [Section titled “Default transport layers”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#default-transport-layers) ### Connecting over WebRTC [Section titled “Connecting over WebRTC”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#connecting-over-webrtc) The default transport layer uses WebRTC. Audio is recorded from the microphone and played back automatically. To use your own media stream or audio element, provide an `OpenAIRealtimeWebRTC` instance when creating the session. import { RealtimeAgent, RealtimeSession, OpenAIRealtimeWebRTC } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); async function main() { const transport = new OpenAIRealtimeWebRTC({ mediaStream: await navigator.mediaDevices.getUserMedia({ audio: true }), audioElement: document.createElement('audio'), }); const customSession = new RealtimeSession(agent, { transport });} ### Connecting over WebSocket [Section titled “Connecting over WebSocket”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#connecting-over-websocket) Pass `transport: 'websocket'` or an instance of `OpenAIRealtimeWebSocket` when creating the session to use a WebSocket connection instead of WebRTC. This works well for server-side use cases, for example building a phone agent with Twilio. import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); const myRecordedArrayBuffer = new ArrayBuffer(0); const wsSession = new RealtimeSession(agent, { transport: 'websocket', model: 'gpt-realtime',});await wsSession.connect({ apiKey: process.env.OPENAI_API_KEY! }); wsSession.on('audio', (event) => { // event.data is a chunk of PCM16 audio}); wsSession.sendAudio(myRecordedArrayBuffer); Use any recording/playback library to handle the raw PCM16 audio bytes. ### Connecting over SIP [Section titled “Connecting over SIP”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#connecting-over-sip) Bridge SIP calls from providers such as Twilio by using the `OpenAIRealtimeSIP` transport. The transport keeps the Realtime session synchronized with SIP events emitted by your telephony provider. 1. Accept the incoming call by generating an initial session configuration with `OpenAIRealtimeSIP.buildInitialConfig()`. This ensures the SIP invitation and Realtime session share identical defaults. 2. Attach a `RealtimeSession` that uses the `OpenAIRealtimeSIP` transport and connect with the `callId` issued by the provider webhook. 3. Listen for session events to drive call analytics, transcripts, or escalation logic. import OpenAI from 'openai';import { OpenAIRealtimeSIP, RealtimeAgent, RealtimeSession, type RealtimeSessionOptions,} from '@openai/agents/realtime'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, webhookSecret: process.env.OPENAI_WEBHOOK_SECRET!,}); const agent = new RealtimeAgent({ name: 'Receptionist', instructions: 'Welcome the caller, answer scheduling questions, and hand off if the caller requests a human.',}); const sessionOptions: Partial = { model: 'gpt-realtime', config: { audio: { input: { turnDetection: { type: 'semantic_vad', interruptResponse: true }, }, }, },}; export async function acceptIncomingCall(callId: string): Promise { const initialConfig = await OpenAIRealtimeSIP.buildInitialConfig( agent, sessionOptions, ); await openai.realtime.calls.accept(callId, initialConfig);} export async function attachRealtimeSession( callId: string,): Promise { const session = new RealtimeSession(agent, { transport: new OpenAIRealtimeSIP(), ...sessionOptions, }); session.on('history_added', (item) => { console.log('Realtime update:', item.type); }); await session.connect({ apiKey: process.env.OPENAI_API_KEY!, callId, }); return session;} #### Cloudflare Workers (workerd) note [Section titled “Cloudflare Workers (workerd) note”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#cloudflare-workers-workerd-note) Cloudflare Workers and other workerd runtimes cannot open outbound WebSockets using the global `WebSocket` constructor. Use the Cloudflare transport from the extensions package, which performs the `fetch()`\-based upgrade internally. import { CloudflareRealtimeTransportLayer } from '@openai/agents-extensions';import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'My Agent',}); // Create a transport that connects to OpenAI Realtime via Cloudflare/workerd's fetch-based upgrade.const cfTransport = new CloudflareRealtimeTransportLayer({ url: 'wss://api.openai.com/v1/realtime?model=gpt-realtime',}); const session = new RealtimeSession(agent, { // Set your own transport. transport: cfTransport,}); ### Building your own transport mechanism [Section titled “Building your own transport mechanism”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#building-your-own-transport-mechanism) If you want to use a different speech-to-speech API or have your own custom transport mechanism, you can create your own by implementing the `RealtimeTransportLayer` interface and emit the `RealtimeTransportEventTypes` events. Interacting with the Realtime API more directly ----------------------------------------------- [Section titled “Interacting with the Realtime API more directly”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#interacting-with-the-realtime-api-more-directly) If you want to use the OpenAI Realtime API but have more direct access to the Realtime API, you have two options: ### Option 1 - Accessing the transport layer [Section titled “Option 1 - Accessing the transport layer”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#option-1---accessing-the-transport-layer) If you still want to benefit from all of the capabilities of the `RealtimeSession` you can access your transport layer through `session.transport`. The transport layer will emit every event it receives under the `*` event and you can send raw events using the `sendEvent()` method. import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); const session = new RealtimeSession(agent, { model: 'gpt-realtime',}); session.transport.on('*', (event) => { // JSON parsed version of the event received on the connection}); // Send any valid event as JSON. For example triggering a new responsesession.transport.sendEvent({ type: 'response.create', // ...}); ### Option 2 — Only using the transport layer [Section titled “Option 2 — Only using the transport layer”](https://openai.github.io/openai-agents-js/guides/voice-agents/transport/#option-2--only-using-the-transport-layer) If you don’t need automatic tool execution, guardrails, etc. you can also use the transport layer as a “thin” client that just manages connection and interruptions. import { OpenAIRealtimeWebRTC } from '@openai/agents/realtime'; const client = new OpenAIRealtimeWebRTC();const audioBuffer = new ArrayBuffer(0); await client.connect({ apiKey: '', model: 'gpt-4o-mini-realtime-preview', initialSessionConfig: { instructions: 'Speak like a pirate', voice: 'ash', modalities: ['text', 'audio'], inputAudioFormat: 'pcm16', outputAudioFormat: 'pcm16', },}); // optionally for WebSocketsclient.on('audio', (newAudio) => {}); client.sendAudio(audioBuffer); --- # Human in the loop | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#_top) Human in the loop ================= This guide demonstrates how to use the built-in human-in-the-loop support in the SDK to pause and resume agent runs based on human intervention. The primary use case for this right now is asking for approval for sensitive tool executions. Approval requests ----------------- [Section titled “Approval requests”](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#approval-requests) You can define a tool that requires approval by setting the `needsApproval` option to `true` or to an async function that returns a boolean. import { tool } from '@openai/agents';import z from 'zod'; const sensitiveTool = tool({ name: 'cancelOrder', description: 'Cancel order', parameters: z.object({ orderId: z.number(), }), // always requires approval needsApproval: true, execute: async ({ orderId }, args) => { // prepare order return },}); const sendEmail = tool({ name: 'sendEmail', description: 'Send an email', parameters: z.object({ to: z.string(), subject: z.string(), body: z.string(), }), needsApproval: async (_context, { subject }) => { // check if the email is spam return subject.includes('spam'); }, execute: async ({ to, subject, body }, args) => { // send email },}); ### Flow [Section titled “Flow”](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#flow) 1. If the agent decides to call a tool (or many) it will check if this tool needs approval by evaluating `needsApproval`. 2. If the approval is required, the agent will check if approval is already granted or rejected. * If approval has not been granted or rejected, the tool will return a static message to the agent that the tool call cannot be executed. * If approval / rejection is missing it will trigger a tool approval request. 3. The agent will gather all tool approval requests and interrupt the execution. 4. If there are any interruptions, the [result](https://openai.github.io/openai-agents-js/guides/results) will contain an `interruptions` array describing pending steps. A `ToolApprovalItem` with `type: "tool_approval_item"` appears when a tool call requires confirmation. 5. You can call `result.state.approve(interruption)` or `result.state.reject(interruption)` to approve or reject the tool call. 6. After handling all interruptions, you can resume execution by passing the `result.state` back into `runner.run(agent, state)` where `agent` is the original agent that triggered the overall run. 7. The flow starts again from step 1. Example ------- [Section titled “Example”](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#example) Below is a more complete example of a human-in-the-loop flow that prompts for approval in the terminal and temporarily stores the state in a file. import { z } from 'zod';import readline from 'node:readline/promises';import fs from 'node:fs/promises';import { Agent, run, tool, RunState, RunResult } from '@openai/agents'; const getWeatherTool = tool({ name: 'get_weather', description: 'Get the weather for a given city', parameters: z.object({ location: z.string(), }), needsApproval: async (_context, { location }) => { // forces approval to look up the weather in San Francisco return location === 'San Francisco'; }, execute: async ({ location }) => { return `The weather in ${location} is sunny`; },}); const dataAgentTwo = new Agent({ name: 'Data agent', instructions: 'You are a data agent', handoffDescription: 'You know everything about the weather', tools: [getWeatherTool],}); const agent = new Agent({ name: 'Basic test agent', instructions: 'You are a basic agent', handoffs: [dataAgentTwo],}); async function confirm(question: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const answer = await rl.question(`${question} (y/n): `); const normalizedAnswer = answer.toLowerCase(); rl.close(); return normalizedAnswer === 'y' || normalizedAnswer === 'yes';} async function main() { let result: RunResult> = await run( agent, 'What is the weather in Oakland and San Francisco?', ); let hasInterruptions = result.interruptions?.length > 0; while (hasInterruptions) { // storing await fs.writeFile( 'result.json', JSON.stringify(result.state, null, 2), 'utf-8', ); // from here on you could run things on a different thread/process // reading later on const storedState = await fs.readFile('result.json', 'utf-8'); const state = await RunState.fromString(agent, storedState); for (const interruption of result.interruptions) { const confirmed = await confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`, ); if (confirmed) { state.approve(interruption); } else { state.reject(interruption); } } // resume execution of the current state result = await run(agent, state); hasInterruptions = result.interruptions?.length > 0; } console.log(result.finalOutput);} main().catch((error) => { console.dir(error, { depth: null });}); See [the full example script](https://github.com/openai/openai-agents-js/tree/main/examples/agent-patterns/human-in-the-loop.ts) for a working end-to-end version. Dealing with longer approval times ---------------------------------- [Section titled “Dealing with longer approval times”](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#dealing-with-longer-approval-times) The human-in-the-loop flow is designed to be interruptible for longer periods of time without keeping your server running. If you need to shut down the request and continue later on you can serialize the state and resume later. You can serialize the state using `JSON.stringify(result.state)` and resume later on by passing the serialized state into `RunState.fromString(agent, serializedState)` where `agent` is the instance of the agent that triggered the overall run. That way you can store your serialized state in a database, or along with your request. ### Versioning pending tasks [Section titled “Versioning pending tasks”](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#versioning-pending-tasks) If your approval requests take a longer time and you intend to version your agent definitions in a meaningful way or bump your Agents SDK version, we currently recommend for you to implement your own branching logic by installing two versions of the Agents SDK in parallel using package aliases. In practice this means assigning your own code a version number and storing it along with the serialized state and guiding the deserialization to the correct version of your code. --- # Connect Realtime Agents to Twilio | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/extensions/twilio/#_top) Connect Realtime Agents to Twilio ================================= Twilio offers a [Media Streams API](https://www.twilio.com/docs/voice/media-streams) that sends the raw audio from a phone call to a WebSocket server. This set up can be used to connect your [voice agents](https://openai.github.io/openai-agents-js/guides/voice-agents) to Twilio. You can use the default Realtime Session transport in `websocket` mode to connect the events coming from Twilio to your Realtime Session. However, this requires you to set the right audio format and adjust your own interruption timing as phone calls will naturally introduce more latency than a web-based conversation. To improve the set up experience, we’ve created a dedicated transport layer that handles the connection to Twilio for you, including handling interruptions and audio forwarding for you. Setup ----- [Section titled “Setup”](https://openai.github.io/openai-agents-js/extensions/twilio/#setup) 1. **Make sure you have a Twilio account and a Twilio phone number.** 2. **Set up a WebSocket server that can receive events from Twilio.** If you are developing locally, this will require you to configure a local tunnel like this will require you to configure a local tunnel like [`ngrok`](https://ngrok.io/) or [Cloudflare Tunnel](https://developers.cloudflare.com/pages/how-to/preview-with-cloudflare-tunnel/) to make your local server accessible to Twilio. You can use the `TwilioRealtimeTransportLayer` to connect to Twilio. 3. **Install the Twilio adapter by installing the extensions package:** npm install @openai/agents-extensions 4. **Import the adapter and model to connect to your `RealtimeSession`:** import { TwilioRealtimeTransportLayer } from '@openai/agents-extensions';import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'My Agent',}); // Create a new transport mechanism that will bridge the connection between Twilio and// the OpenAI Realtime API.const twilioTransport = new TwilioRealtimeTransportLayer({ twilioWebSocket: websocketConnection,}); const session = new RealtimeSession(agent, { // set your own transport transport: twilioTransport,}); 5. **Connect your `RealtimeSession` to Twilio:** session.connect({ apiKey: 'your-openai-api-key' }); Any event and behavior that you would expect from a `RealtimeSession` will work as expected including tool calls, guardrails, and more. Read the [voice agents guide](https://openai.github.io/openai-agents-js/guides/voice-agents) for more information on how to use the `RealtimeSession` with voice agents. Tips and Considerations ----------------------- [Section titled “Tips and Considerations”](https://openai.github.io/openai-agents-js/extensions/twilio/#tips-and-considerations) 1. **Speed is the name of the game.** In order to receive all the necessary events and audio from Twilio, you should create your `TwilioRealtimeTransportLayer` instance as soon as you have a reference to the WebSocket connection and immediately call `session.connect()` afterwards. 2. **Access the raw Twilio events.** If you want to access the raw events that are being sent by Twilio, you can listen to the `transport_event` event on your `RealtimeSession` instance. Every event from Twilio will have a type of `twilio_message` and a `message` property that contains the raw event data. 3. **Watch debug logs.** Sometimes you may run into issues where you want more information on what’s going on. Using a `DEBUG=openai-agents*` environment variable will show all the debug logs from the Agents SDK. Alternatively, you can enable just debug logs for the Twilio adapter using `DEBUG=openai-agents:extensions:twilio*`. Full example server ------------------- [Section titled “Full example server”](https://openai.github.io/openai-agents-js/extensions/twilio/#full-example-server) Below is an example of a full end-to-end example of a WebSocket server that receives requests from Twilio and forwards them to a `RealtimeSession`. import Fastify from 'fastify';import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';import dotenv from 'dotenv';import fastifyFormBody from '@fastify/formbody';import fastifyWs from '@fastify/websocket';import { RealtimeAgent, RealtimeSession, backgroundResult, tool,} from '@openai/agents/realtime';import { TwilioRealtimeTransportLayer } from '@openai/agents-extensions';import { hostedMcpTool } from '@openai/agents';import { z } from 'zod';import process from 'node:process'; // Load environment variables from .env filedotenv.config(); // Retrieve the OpenAI API key from environment variables. You must have OpenAI Realtime API access.const { OPENAI_API_KEY } = process.env;if (!OPENAI_API_KEY) { console.error('Missing OpenAI API key. Please set it in the .env file.'); process.exit(1);}const PORT = +(process.env.PORT || 5050); // Initialize Fastifyconst fastify = Fastify();fastify.register(fastifyFormBody);fastify.register(fastifyWs); const weatherTool = tool({ name: 'weather', description: 'Get the weather in a given location.', parameters: z.object({ location: z.string(), }), execute: async ({ location }: { location: string }) => { return backgroundResult(`The weather in ${location} is sunny.`); },}); const secretTool = tool({ name: 'secret', description: 'A secret tool to tell the special number.', parameters: z.object({ question: z .string() .describe( 'The question to ask the secret tool; mainly about the special number.', ), }), execute: async ({ question }: { question: string }) => { return `The answer to ${question} is 42.`; }, needsApproval: true,}); const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'You are a friendly assistant. When you use a tool always first say what you are about to do.', tools: [ hostedMcpTool({ serverLabel: 'deepwiki', serverUrl: 'https://mcp.deepwiki.com/sse', }), secretTool, weatherTool, ],}); // Root Routefastify.get('/', async (_request: FastifyRequest, reply: FastifyReply) => { reply.send({ message: 'Twilio Media Stream Server is running!' });}); // Route for Twilio to handle incoming and outgoing calls// punctuation to improve text-to-speech translationfastify.all( '/incoming-call', async (request: FastifyRequest, reply: FastifyReply) => { const twimlResponse = ` O.K. you can start talking! `.trim(); reply.type('text/xml').send(twimlResponse); },); // WebSocket route for media-streamfastify.register(async (scopedFastify: FastifyInstance) => { scopedFastify.get( '/media-stream', { websocket: true }, async (connection: any) => { const twilioTransportLayer = new TwilioRealtimeTransportLayer({ twilioWebSocket: connection, }); const session = new RealtimeSession(agent, { transport: twilioTransportLayer, model: 'gpt-realtime', config: { audio: { output: { voice: 'verse', }, }, }, }); session.on('mcp_tools_changed', (tools: { name: string }[]) => { const toolNames = tools.map((tool) => tool.name).join(', '); console.log(`Available MCP tools: ${toolNames || 'None'}`); }); session.on( 'tool_approval_requested', (_context: unknown, _agent: unknown, approvalRequest: any) => { console.log( `Approving tool call for ${approvalRequest.approvalItem.rawItem.name}.`, ); session .approve(approvalRequest.approvalItem) .catch((error: unknown) => console.error('Failed to approve tool call.', error), ); }, ); session.on( 'mcp_tool_call_completed', (_context: unknown, _agent: unknown, toolCall: unknown) => { console.log('MCP tool call completed.', toolCall); }, ); await session.connect({ apiKey: OPENAI_API_KEY, }); console.log('Connected to the OpenAI Realtime API'); }, );}); fastify.listen({ port: PORT }, (err: Error | null) => { if (err) { console.error(err); process.exit(1); } console.log(`Server is listening on port ${PORT}`);}); process.on('SIGINT', () => { fastify.close(); process.exit(0);}); --- # Building Voice Agents | OpenAI Agents SDK [Skip to content](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#_top) Building Voice Agents ===================== Audio handling -------------- [Section titled “Audio handling”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#audio-handling) Some transport layers like the default `OpenAIRealtimeWebRTC` will handle audio input and output automatically for you. For other transport mechanisms like `OpenAIRealtimeWebSocket` you will have to handle session audio yourself: import { RealtimeAgent, RealtimeSession, TransportLayerAudio,} from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'My agent' });const session = new RealtimeSession(agent);const newlyRecordedAudio = new ArrayBuffer(0); session.on('audio', (event: TransportLayerAudio) => { // play your audio}); // send new audio to the agentsession.sendAudio(newlyRecordedAudio); Session configuration --------------------- [Section titled “Session configuration”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#session-configuration) You can configure your session by passing additional options to either the [`RealtimeSession`](https://openai.github.io/openai-agents-js/openai/agents-realtime/classes/realtimesession/) during construction or when you call `connect(...)`. import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); const session = new RealtimeSession(agent, { model: 'gpt-realtime', config: { inputAudioFormat: 'pcm16', outputAudioFormat: 'pcm16', inputAudioTranscription: { model: 'gpt-4o-mini-transcribe', }, },}); These transport layers allow you to pass any parameter that matches [session](https://platform.openai.com/docs/api-reference/realtime-client-events/session/update) . For parameters that are new and don’t have a matching parameter in the [RealtimeSessionConfig](https://openai.github.io/openai-agents-js/openai/agents-realtime/type-aliases/realtimesessionconfig/) you can use `providerData`. Anything passed in `providerData` will be passed directly as part of the `session` object. Handoffs -------- [Section titled “Handoffs”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#handoffs) Similarly to regular agents, you can use handoffs to break your agent into multiple agents and orchestrate between them to improve the performance of your agents and better scope the problem. import { RealtimeAgent } from '@openai/agents/realtime'; const mathTutorAgent = new RealtimeAgent({ name: 'Math Tutor', handoffDescription: 'Specialist agent for math questions', instructions: 'You provide help with math problems. Explain your reasoning at each step and include examples',}); const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.', handoffs: [mathTutorAgent],}); Unlike regular agents, handoffs behave slightly differently for Realtime Agents. When a handoff is performed, the ongoing session will be updated with the new agent configuration. Because of this, the agent automatically has access to the ongoing conversation history and input filters are currently not applied. Additionally, this means that the `voice` or `model` cannot be changed as part of the handoff. You can also only connect to other Realtime Agents. If you need to use a different model, for example a reasoning model like `gpt-5-mini`, you can use [delegation through tools](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#delegation-through-tools) . Tools ----- [Section titled “Tools”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#tools) Just like regular agents, Realtime Agents can call tools to perform actions. You can define a tool using the same `tool()` function that you would use for a regular agent. import { tool, RealtimeAgent } from '@openai/agents/realtime';import { z } from 'zod'; const getWeather = tool({ name: 'get_weather', description: 'Return the weather for a city.', parameters: z.object({ city: z.string() }), async execute({ city }) { return `The weather in ${city} is sunny.`; },}); const weatherAgent = new RealtimeAgent({ name: 'Weather assistant', instructions: 'Answer weather questions.', tools: [getWeather],}); You can only use function tools with Realtime Agents and these tools will be executed in the same place as your Realtime Session. This means if you are running your Realtime Session in the browser, your tool will be executed in the browser. If you need to perform more sensitive actions, you can make an HTTP request within your tool to your backend server. While the tool is executing the agent will not be able to process new requests from the user. One way to improve the experience is by telling your agent to announce when it is about to execute a tool or say specific phrases to buy the agent some time to execute the tool. ### Accessing the conversation history [Section titled “Accessing the conversation history”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#accessing-the-conversation-history) Additionally to the arguments that the agent called a particular tool with, you can also access a snapshot of the current conversation history that is tracked by the Realtime Session. This can be useful if you need to perform a more complex action based on the current state of the conversation or are planning to use [tools for delegation](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#delegation-through-tools) . import { tool, RealtimeContextData, RealtimeItem,} from '@openai/agents/realtime';import { z } from 'zod'; const parameters = z.object({ request: z.string(),}); const refundTool = tool({ name: 'Refund Expert', description: 'Evaluate a refund', parameters, execute: async ({ request }, details) => { // The history might not be available const history: RealtimeItem[] = details?.context?.history ?? []; // making your call to process the refund request },}); ### Approval before tool execution [Section titled “Approval before tool execution”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#approval-before-tool-execution) If you define your tool with `needsApproval: true` the agent will emit a `tool_approval_requested` event before executing the tool. By listening to this event you can show a UI to the user to approve or reject the tool call. import { session } from './agent'; session.on('tool_approval_requested', (_context, _agent, request) => { // show a UI to the user to approve or reject the tool call // you can use the `session.approve(...)` or `session.reject(...)` methods to approve or reject the tool call session.approve(request.approvalItem); // or session.reject(request.rawItem);}); Guardrails ---------- [Section titled “Guardrails”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#guardrails) Guardrails offer a way to monitor whether what the agent has said violated a set of rules and immediately cut off the response. These guardrail checks will be performed based on the transcript of the agent’s response and therefore requires that the text output of your model is enabled (it is enabled by default). The guardrails that you provide will run asynchronously as a model response is returned, allowing you to cut off the response based a predefined classification trigger, for example “mentions a specific banned word”. When a guardrail trips the session emits a `guardrail_tripped` event. The event also provides a `details` object containing the `itemId` that triggered the guardrail. import { RealtimeOutputGuardrail, RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); const guardrails: RealtimeOutputGuardrail[] = [ { name: 'No mention of Dom', async execute({ agentOutput }) { const domInOutput = agentOutput.includes('Dom'); return { tripwireTriggered: domInOutput, outputInfo: { domInOutput }, }; }, },]; const guardedSession = new RealtimeSession(agent, { outputGuardrails: guardrails,}); By default guardrails are run every 100 characters or at the end of the response text has been generated. Since speaking out the text normally takes longer it means that in most cases the guardrail should catch the violation before the user can hear it. If you want to modify this behavior you can pass a `outputGuardrailSettings` object to the session. import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Greeter', instructions: 'Greet the user with cheer and answer questions.',}); const guardedSession = new RealtimeSession(agent, { outputGuardrails: [ /*...*/ ], outputGuardrailSettings: { debounceTextLength: 500, // run guardrail every 500 characters or set it to -1 to run it only at the end },}); Turn detection / voice activity detection ----------------------------------------- [Section titled “Turn detection / voice activity detection”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#turn-detection--voice-activity-detection) The Realtime Session will automatically detect when the user is speaking and trigger new turns using the built-in [voice activity detection modes of the Realtime API](https://platform.openai.com/docs/guides/realtime-vad) . You can change the voice activity detection mode by passing a `turnDetection` object to the session. import { RealtimeSession } from '@openai/agents/realtime';import { agent } from './agent'; const session = new RealtimeSession(agent, { model: 'gpt-realtime', config: { turnDetection: { type: 'semantic_vad', eagerness: 'medium', createResponse: true, interruptResponse: true, }, },}); Modifying the turn detection settings can help calibrate unwanted interruptions and dealing with silence. Check out the [Realtime API documentation for more details on the different settings](https://platform.openai.com/docs/guides/realtime-vad) Interruptions ------------- [Section titled “Interruptions”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#interruptions) When using the built-in voice activity detection, speaking over the agent automatically triggers the agent to detect and update its context based on what was said. It will also emit an `audio_interrupted` event. This can be used to immediately stop all audio playback (only applicable to WebSocket connections). import { session } from './agent'; session.on('audio_interrupted', () => { // handle local playback interruption}); If you want to perform a manual interruption, for example if you want to offer a “stop” button in your UI, you can call `interrupt()` manually: import { session } from './agent'; session.interrupt();// this will still trigger the `audio_interrupted` event for you// to cut off the audio playback when using WebSockets In either way, the Realtime Session will handle both interrupting the generation of the agent, truncate its knowledge of what was said to the user, and update the history. If you are using WebRTC to connect to your agent, it will also clear the audio output. If you are using WebSocket, you will need to handle this yourself by stopping audio playack of whatever has been queued up to be played. Text input ---------- [Section titled “Text input”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#text-input) If you want to send text input to your agent, you can use the `sendMessage` method on the `RealtimeSession`. This can be useful if you want to enable your user to interface in both modalities with the agent, or to provide additional context to the conversation. import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Assistant',}); const session = new RealtimeSession(agent, { model: 'gpt-realtime',}); session.sendMessage('Hello, how are you?'); Conversation history management ------------------------------- [Section titled “Conversation history management”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#conversation-history-management) The `RealtimeSession` automatically manages the conversation history in a `history` property: You can use this to render the history to the customer or perform additional actions on it. As this history will constantly change during the course of the conversation you can listen for the `history_updated` event. If you want to modify the history, like removing a message entirely or updating its transcript, you can use the `updateHistory` method. import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime'; const agent = new RealtimeAgent({ name: 'Assistant',}); const session = new RealtimeSession(agent, { model: 'gpt-realtime',}); await session.connect({ apiKey: '' }); // listening to the history_updated eventsession.on('history_updated', (history) => { // returns the full history of the session console.log(history);}); // Option 1: explicit settingsession.updateHistory([ /* specific history */]); // Option 2: override based on current state like removing all agent messagessession.updateHistory((currentHistory) => { return currentHistory.filter( (item) => !(item.type === 'message' && item.role === 'assistant'), );}); ### Limitations [Section titled “Limitations”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#limitations) 1. You can currently not update/change function tool calls after the fact 2. Text output in the history requires transcripts and text modalities to be enabled 3. Responses that were truncated due to an interruption do not have a transcript Delegation through tools ------------------------ [Section titled “Delegation through tools”](https://openai.github.io/openai-agents-js/guides/voice-agents/build/#delegation-through-tools) ![Delegation through tools](https://cdn.openai.com/API/docs/diagram-speech-to-speech-agent-tools.png) By combining the conversation history with a tool call, you can delegate the conversation to another backend agent to perform a more complex action and then pass it back as the result to the user. import { RealtimeAgent, RealtimeContextData, tool,} from '@openai/agents/realtime';import { handleRefundRequest } from './serverAgent';import z from 'zod'; const refundSupervisorParameters = z.object({ request: z.string(),}); const refundSupervisor = tool< typeof refundSupervisorParameters, RealtimeContextData>({ name: 'escalateToRefundSupervisor', description: 'Escalate a refund request to the refund supervisor', parameters: refundSupervisorParameters, execute: async ({ request }, details) => { // This will execute on the server return handleRefundRequest(request, details?.context?.history ?? []); },}); const agent = new RealtimeAgent({ name: 'Customer Support', instructions: 'You are a customer support agent. If you receive any requests for refunds, you need to delegate to your supervisor.', tools: [refundSupervisor],}); The code below will then be executed on the server. In this example through a server actions in Next.js. // This runs on the serverimport 'server-only'; import { Agent, run } from '@openai/agents';import type { RealtimeItem } from '@openai/agents/realtime';import z from 'zod'; const agent = new Agent({ name: 'Refund Expert', instructions: 'You are a refund expert. You are given a request to process a refund and you need to determine if the request is valid.', model: 'gpt-5-mini', outputType: z.object({ reasong: z.string(), refundApproved: z.boolean(), }),}); export async function handleRefundRequest( request: string, history: RealtimeItem[],) { const input = `The user has requested a refund. The request is: ${request} Current conversation history:${JSON.stringify(history, null, 2)}`.trim(); const result = await run(agent, input); return JSON.stringify(result.finalOutput, null, 2);} ---