Skip to main content
This example is an agent written with the OpenAI Agents SDK for JavaScript (@openai/agents). Its pay tool has needsApproval: true. When the model calls it, the agent saves the paused run, asks a person through AgentOS, and on the resume approves or rejects the call and lets the run continue. The TypeScript SDK has no adapter for this framework, so the example maps the approval itself, in about twenty lines: the Agents SDK’s RunState goes into AgentOS run state, and its interruptions become one hold. The Python SDK has a ready-made adapter for the Python version; see Framework adapters.

The bundle

The folder also holds vendor/agentos-sdk-0.1.0.tgz, the TypeScript SDK (see SDKs: it isn’t on npm yet and ships with AgentOS). Set config.models.main to a model from your environment’s Models page, zip the folder with vendor/ in it and deploy it.

How the model is wired

The agent uses an OpenAI client pointed at the gateway: baseURL and apiKey from inv.llm (the gateway ignores the key), the model name from config.models with inv.llm.model("main"), and fetch: inv.fetch.bind(inv), which adds the run token to requests for the gateway and the platform only, and only while the invocation runs. Build the client and the agent inside the handler, once per invocation. OpenAIChatCompletionsModel makes the agent call /v1/chat/completions, which every provider behind the gateway serves. setTracingDisabled(true) keeps traces from being sent to OpenAI; AgentOS records each model call, its tokens and its cost on the run.

Approve a tool call

  1. The model calls pay. Because the tool has needsApproval: true, run() stops with result.interruptions.
  2. The agent saves result.state.toString() in run state and calls inv.ask() with one item per interrupted call, named by the call’s id. ask() creates the hold and ends the invocation as suspended.
  3. A person decides. AgentOS invokes the same run again with the decision in inv.resume.
  4. The agent restores the RunState, approves or rejects each interrupted call (a rejection passes the reviewer’s comment to the model), and runs again from that state. An approved call runs once.
If the model asks for another approval later in the run, the same code asks again on that resume.
1

Start the run

2

Decide

In the Review queue, or through the API with the item ids from the hold:
3

Read the result

Good to know

  • Approve or reject only. The Agents SDK approves or rejects a call as the model made it, so the example offers no edit. To let reviewers change the amount, ask before the agent runs, or give the tool its own inv.ask().
  • An expired hold rejects. When nobody decides before the hold’s expires_in, inv.resume.item(...) finds no decision and the call is rejected.
  • Run state is at most 1 MiB. The saved RunState grows with the run’s transcript, so keep long conversations out of a run that waits for approvals.
  • Guardrail blocks. A model call a guardrail blocks fails with HTTP 400 and the guardrail object in the error body; uncaught, the run fails. See Guardrails.
Tested with @openai/agents 0.21.0, openai 7.45.0 and Node 24, with the conformance kit. More frameworks: Framework examples.