OpenAI Agents SDK TypeScript vs Python: The Differences That Matter

The OpenAI Agents SDK ships as two separate libraries: @openai/agents on npm for TypeScript and JavaScript, and openai-agents on PyPI for Python. They are not line-for-line ports. Both give you the same agent loop, handoffs, guardrails, MCP tools, sessions, and sandboxes, but tool schemas are declared differently, the streaming event names diverge, Python ships far more session backends, and the shell environment types do not match. This guide lists each gap with the exact API names so a Python agent can be ported without guessing.

Key takeaways:

  • Tools: Python reads your function signature and docstring; TypeScript takes an explicit Zod schema, which automatically enables strict mode
  • Zod is a peer dependency in TypeScript (zod ^4.0.0) and must be installed yourself; Python's pydantic comes bundled
  • Streaming: run_item_stream_event and agent_updated_stream_event are identical in both, but the raw event is raw_response_event in Python and raw_model_stream_event in TypeScript
  • Sessions: Python ships SQLite, SQLAlchemy, Redis, MongoDB, and encrypted backends; TypeScript ships three
  • Shell: TypeScript's shellTool() accepts local, container_auto, or container_reference; there is no environment named hosted or container

OpenAI Agents SDK TypeScript vs Python at a glance

The two SDKs version independently. As of July 17, 2026 the TypeScript package @openai/agents is at 0.13.5 and the Python package openai-agents is at 0.18.3, so matching version numbers across the two is meaningless. Runtime floors also differ: the JS docs require Node.js 22 or higher, with Deno and Bun also supported, while the PyPI metadata for openai-agents declares requires_python >=3.10. (Source: npm registry @openai/agents)

CapabilityPython SDKTypeScript SDK
Packageopenai-agents (PyPI), 0.18.3@openai/agents (npm), 0.13.5
Runtime floorPython 3.10+Node.js 22+, plus Deno and Bun
Tool schema@function_tool on a plain function; inspect reads the signature, griffe parses the docstring, pydantic builds the schematool() with a Zod schema, or a raw JSON Schema, in parameters
Schema librarypydantic, bundledZod, a peer dependency you install (zod ^4.0.0)
Streaming entry pointRunner.run_streamed(), then .stream_events()run(agent, input, { stream: true }), then for await or toTextStream()
Raw stream event nameraw_response_eventraw_model_stream_event
Session backendsSQLite, async SQLite, advanced SQLite, SQLAlchemy, Redis, MongoDB, Dapr, encrypted, plus OpenAIConversationsSessionMemorySession, OpenAIConversationsSession, OpenAIResponsesCompactionSession
Local shell toolShellTool(executor=...)shellTool({ shell, environment: { type: 'local' } })

Everything above the session row is a syntax or packaging difference you can translate mechanically. The session and shell rows are the two places where a Python agent has capabilities with no drop-in TypeScript equivalent, which is where migrations actually stall. (Source: OpenAI Agents Python Docs)

Tool definition: Zod schemas vs signature inference

The most visible difference is how a tool's input schema gets built. Python turns any function into a tool with the @function_tool decorator and derives everything from the code: the tool name comes from the function name, the description from the docstring, and the JSON Schema from the arguments. The docs are explicit about the machinery: Python's inspect module extracts the signature, griffe parses docstrings, and pydantic creates the schema. (Source: OpenAI Agents Python Docs)

TypeScript takes the opposite approach. The tool() helper wants an explicit parameters value, and the tools guide notes that parameters accepts either a Zod schema or a raw JSON Schema object, with Zod parameters automatically enabling strict mode. (Source: OpenAI Agents JS Tools Guide)

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.;
},
});

Two consequences follow. First, text that lived in a Python docstring has to move into the description field and into .describe() calls on individual Zod fields; nothing reads your JSDoc. Second, strict mode is on by default in TypeScript, so the SDK returns a model error when arguments fail validation unless you set strict: false for fuzzy matching. A Python tool that quietly tolerated a loose argument will start erroring after the port. (Source: OpenAI Agents JS Tools Guide)

Operator note (first-hand): npm view @openai/agents-core peerDependencies on July 28, 2026 returns { "zod": "^4.0.0" }, and npm view @openai/agents version returns 0.13.5. Zod is a peer dependency, not a bundled one, so npm install @openai/agents on its own leaves you without it and every tool() call fails to resolve z. Install both: npm install @openai/agents zod@^4. Zod 3 does not satisfy the range. Python developers are the ones who trip on this, because pydantic arrives with the Python SDK and needs no equivalent step.

Streaming: same concepts, two of three event names match

Both SDKs stream the same three kinds of event, and two of the three names are identical, which makes the third one an easy silent bug. In Python you call Runner.run_streamed() and iterate .stream_events(); the raw model event is raw_response_event. (Source: OpenAI Agents Python Docs)

In TypeScript you pass { stream: true } to run() and iterate the returned object directly. The raw event is named raw_model_stream_event. (Source: OpenAI Agents JS Docs)

import { Agent, run } from "@openai/agents";

const agent = new Agent({
name: "Storyteller",
instructions: "You are a storyteller.",
});

const result = await run(agent, "Tell me a story about a cat.", {
stream: true,
});

for await (const event of result) {
if (event.type === "raw_model_stream_event") console.log(event.data);
if (event.type === "agent_updated_stream_event") console.log(event.agent.name);
if (event.type === "run_item_stream_event") console.log(event.item);
}

If you only want assistant text, TypeScript adds a helper: result.toTextStream({ compatibleWithNodeStreams: true }) returns a regular Node.js Readable you can pipe straight into process.stdout. The stream.completed promise resolves once the run and all pending callbacks finish, including session persistence and history compaction hooks that run after the last text token arrives. Await it before you tear the process down. (Source: OpenAI Agents JS Docs)

Sessions: TypeScript ships three backends, Python ships many

Sessions remove the need to call toInputList() or stitch history between turns yourself. The TypeScript SDK ships two general implementations, OpenAIConversationsSession for the Conversations API and MemorySession for local development, plus OpenAIResponsesCompactionSession, which wraps any session and shrinks stored history via responses.compact when you use an OpenAI Responses model. Because they share the Session interface, you can plug in your own storage backend. (Source: OpenAI Agents JS Docs)

Python's list is much longer, covering SQLite in three variants, SQLAlchemy, Redis, MongoDB, Dapr, and an encrypted wrapper alongside the same OpenAIConversationsSession. A Python agent persisting to Postgres through SQLAlchemySession has no drop-in TypeScript equivalent; you implement the Session interface against your own store. (Source: OpenAI Agents Python Docs)

Resuming a conversation is where ported code most often breaks. OpenAIConversationsSession is constructed with conversationId, not a thread id:

import { OpenAIConversationsSession } from "@openai/agents";

const session = new OpenAIConversationsSession({
conversationId: "conv_123", // resume an existing conversation
});

The other constructor options are client, apiKey, baseURL, organization, and project. To mint an id up front rather than lazily, call startOpenAIConversationsSession(client?) and pass the returned id as conversationId. (Source: OpenAI Agents JS Docs)

Shell and sandbox: the environment types do not match

The TypeScript shell tool is the function shellTool(), and it has two modes rather than the three the naming suggests. Local mode takes a shell implementation and optionally environment: { type: 'local', skills } plus needsApproval and onApproval. Hosted container mode takes environment with type: 'container_auto' to create a managed container for the run, or type: 'container_reference' to reuse an existing container by containerId. There is no type: 'hosted' and no type: 'container'. (Source: OpenAI Agents JS Tools Guide)

Modeenvironment.typeAccepts shell and approvalsNotable options
LocallocalYesskills mounted by name, description, and filesystem path
Hosted, new containercontainer_autoNonetworkPolicy with allowlists and domainSecrets, fileIds, memoryLimit, skills
Hosted, existing containercontainer_referenceNocontainerId of the container to reuse

Hosted shell environments reject shell, needsApproval, and onApproval, because execution happens in the hosted container rather than in your local process. Approval gates you relied on in a local Python ShellTool(executor=...) therefore do not carry over when you switch to a hosted container. (Source: OpenAI Agents JS Tools Guide)

Separately, the TypeScript SDK has Sandbox Agents, a distinct API from shellTool(). A SandboxAgent gets a persistent workspace with a Manifest for staged files, Capabilities for sandbox-native tools, and a sandbox client chosen per run. UnixLocalSandboxClient is the starting point on macOS and Linux, DockerSandboxClient on Windows, and hosted clients are published under @openai/agents-extensions/sandbox/* for Blaxel, Cloudflare, Daytona, E2B, Modal, Runloop, and Vercel. The docs flag the surface as pre-general-availability and subject to change. (Source: OpenAI Agents JS Sandbox Guide)

Python-to-TypeScript migration checklist

Work through these in order; the first three cause build failures and the rest cause runtime surprises.

  1. Install zod@^4 alongside @openai/agents. Nothing compiles without it.
  2. Rewrite every @function_tool function as a tool({ name, description, parameters, execute }) call, moving docstring text into description and per-argument text into Zod .describe().
  3. Decide per tool whether strict mode is acceptable, and set strict: false where the Python version tolerated loose arguments.
  4. Rename raw_response_event to raw_model_stream_event in stream handlers, and leave run_item_stream_event and agent_updated_stream_event alone.
  5. Replace Runner.run_streamed(...) plus .stream_events() with run(agent, input, { stream: true }), and await stream.completed.
  6. Map your Python session backend. If it was OpenAIConversationsSession, pass the existing conversation id as conversationId. If it was SQLite, SQLAlchemy, Redis, or MongoDB, implement the Session interface against your own store.
  7. Convert shell configuration to local, container_auto, or container_reference, and re-home any approval callbacks, which hosted containers do not accept.

Handoffs, guardrails, MCP servers, and tracing keep the same concepts and near-identical names across both SDKs, so those parts of a port are usually mechanical. (Source: OpenAI Agents JS GitHub)

Frequently asked questions

Should I use Python or TypeScript for the OpenAI Agents SDK?

Pick the language your service already runs in. Both SDKs cover agents, handoffs, guardrails, MCP tools, sessions, sandboxes, and realtime. Python has more built-in session backends and a larger example set; TypeScript gives compile-time schema checking through Zod and fits an existing Node.js stack without standing up a second service.

Is OpenAI Agent SDK good?

It is OpenAI's own maintained framework, published on npm and PyPI with releases landing several times a month: @openai/agents shipped 0.13.0 through 0.13.5 between July 7 and July 17, 2026. Sandbox Agents are still labeled pre-general-availability in the docs, so treat that one surface as unstable.

Does the OpenAI Agents TypeScript SDK require the Python SDK to run?

No. @openai/agents is a standalone package with no dependency on the Python SDK; it calls the OpenAI API directly. Its runtime dependencies are openai, debug, and the three sibling packages @openai/agents-core, @openai/agents-openai, and @openai/agents-realtime. You need Node.js 22 or higher, Deno, or Bun.

What is the difference between OpenAIConversationsSession and MemorySession?

OpenAIConversationsSession syncs history with OpenAI's Conversations API and hands you a conversationId that survives process restarts. MemorySession keeps history in process memory only and is intended for local development. Use the first for production agents that resume, the second while iterating locally.

Can I use the TypeScript SDK with Cloudflare Workers?

The SDK publishes a Cloudflare realtime transport layer and a CloudflareSandboxClient under @openai/agents-extensions, so edge deployment is a supported path. Shell tools in local mode will not work on an edge runtime because there is no process to spawn, so use container_auto or a hosted sandbox client instead.

Do the two SDKs share version numbers?

No. They version independently. On July 28, 2026 the TypeScript package sits at 0.13.5 and the Python package at 0.18.3. Do not pin them to a matching number, and do not read the higher Python version as meaning Python leads on any specific feature.

Which SDK should you choose?

Choose by stack, not by feature list, because the feature lists overlap almost entirely. Take TypeScript when your product is already Node.js and you want tool schema errors caught at build time; the cost is writing Zod schemas by hand and giving up the wide set of prebuilt session backends. Take Python when you want SQLite, SQLAlchemy, Redis, or MongoDB session persistence without writing an adapter, or when your team follows the larger Python example corpus. (Source: OpenAI Agents JS GitHub)

Teams running both should keep the agent definitions symmetric and isolate the differences in three files: a tool module, a stream-handler module, and a session adapter. Those are the only three places where the SDKs genuinely diverge, and keeping that divergence contained is what makes a two-language agent fleet maintainable.

References