---
title: 'SDKs'
description: 'The TypeScript and Python SDKs: Agents, upsert, run, stream, tools and the lower-level API'
---
[View as Markdown](https://camelai.com/docs/camelrun/sdks.md) · [Docs index for coding agents](https://run.camelai.com/llms.txt)

The two SDKs have the same features, named in each language's style; their
version numbers are their own and do not line up.

```bash TypeScript
npm install @camelai/run   # Node 22+, Bun, Deno, Cloudflare Workers
```

```bash Python
pip install camelai-run    # Python 3.11+
pip install "camelai-run[server]"   # adds serve_tools
pip install "camelai-run[pydantic]" # adds pydantic, for run(output=Model)
```

| TypeScript entry       |                                                                                                                  |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `@camelai/run`         | Everything portable: `Agents`, `tool`, `schema`, the lower-level `AgentRuntime` and `AgentClient`, and the types |
| `@camelai/run/node`    | The same for Node and Bun, plus local file paths as attachments and `nodeListener`                               |
| `@camelai/run/server`  | `serveTools`, `verifyRuntimeToken`, `runtimeAuth`, `runtimeIdentity`, `createAgentHandler`                       |
| `@camelai/run/watch`   | `watchAgent`, for reading an agent from a browser                                                                |
| `@camelai/run/chat`    | `createAgentChat`, the framework-free chat store                                                                 |
| `@camelai/run/ai-sdk`  | `AgentRuntimeChatTransport`, for the AI SDK's `useChat`                                                          |
| `@camelai/run/mcp`     | `fromMcpServer`, for attaching an MCP SDK server                                                                 |
| `@camelai/run/testing` | `testRuntime`, for signing identity tokens in tests                                                              |

## Agents

```ts TypeScript
const agents = new Agents({ apiKey, url }); // or `await using agents = new Agents()`
```

```python Python
async with Agents(api_key, url=url) as agents:
    ...
```

The Python SDK is async-only: every call is awaited inside `async def`. From
synchronous code (a script, a Django view, a Celery task), run it with
`asyncio.run(...)`. Its names are the TypeScript ones in snake\_case
(`throw_on_error`, `run.tool_errors`, `part.is_error`), as the tables below show.

* `apiKey` defaults to `CAMELAI_API_KEY`, and `url` to `CAMELAI_BASE_URL`, else
  `https://run.camelai.com`.
* `agents.close()` closes every agent's connection so the process can exit.
  Their runs go on in camelRun.
* `agents.runtime` is the lower-level `AgentRuntime`: definitions, volumes,
  `listAgents()`, `browserToken(agentId)`, `inbox()` and `toolSources(agentId)`.

### `agents.upsert(key, config)`

The agent for `key`, made if there's none and brought to `config` if it
differs. Returns a connected `Agent`.

| TypeScript                               | Python                                         |                                                                                         |
| ---------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------- |
| `model`                                  | `model=`                                       | `"provider/model-id"` from `GET /v1/models`                                             |
| `instructions`                           | `instructions=`                                | The system prompt                                                                       |
| `instructionsAppend`                     | `instructions_append=`                         | Text after the prompt, such as per-conversation context                                 |
| `tools`                                  | `tools=`                                       | Tools served from this process: `{ name: tool({...}) }`, or a list of `@tool` functions |
| `builtins`                               | `builtins=`                                    | `web_search`, `web_fetch`, `schedule`, `ask_user`                                       |
| `definition`                             | `definition=`                                  | Make it from a definition                                                               |
| `thinkingLevel`                          | `thinking_level=`                              | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`                               |
| `subject`, `context`                     | `subject=`, `context=`                         | Whom it acts for, and claims for its tools. Fixed at creation                           |
| `keyScope`, `spendLimit`, `modelHeaders` | `key_scope=`, `spend_limit=`, `model_headers=` | See [Models and keys](/docs/camelrun/models-and-keys)                                   |
| `mounts`, `fileTools`                    | `mounts=`, `file_tools=`                       | Its volumes (fixed at creation), and whether it has file tools                          |
| `name`                                   | `name=`                                        | A label shown in the console                                                            |
| `attach`                                 | `attach=`                                      | `false`: declare `tools` without serving them                                           |
| `takeover`                               | `takeover=`                                    | Replace the process serving the tools now                                               |
| `onEvent`, `onInput`, `onError`          | `on_event=`, `on_input=`, `on_error=`          | Callbacks for events, inputs as they're asked, and connection errors                    |

### `agents.get(keyOrId, config)`

The existing agent with this key (or id), as it is. `upsert` sets an agent to
the config it's given; `get` changes nothing, so use it where a process only
runs or reads agents that another process configures.

```ts
const agent = await agents.get("support-triage");      // or its id
```

```python
agent = await agents.get("support-triage")
```

It throws an `AgentError` with `status` 404 when no live agent has the key or
id, and 409 `AGENT_KEYLESS` for an agent made without a key. It takes `tools`,
the callbacks, `attach` and `takeover` as `upsert` does. Over REST,
`GET /v1/agents/{keyOrId}/credentials` returns `{id, token, expiresAt}`.
Available from `@camelai/run` 0.14.0 and `camelai-run` 0.10.0.

### `agent.fork(options)`

A new agent made from this one: its configuration, a copy of its history and a
fork of its workspace, each its own from then on.

```ts
const fork = await agent.fork({ key: "support-b", atMessage: 5 });
await fork.run("Try the other approach");
```

```python
fork = await agent.fork(key="support-b", at_message=5)
```

`atMessage` ends the copied history at a history index (that message and the
tool results that answer it) or a request id (that run's whole turn); by
default it ends with the last turn that finished, never mid-turn. Forking again
with the same `key` returns the same fork, and `agents.get(key)` finds it.
Without a key the fork lives a day (`ttlSeconds` changes that). `subject`,
`context`, `instructionsAppend` (`instructions_append=`) and `modelHeaders` (`model_headers=`) are the fork's own
instead of the source's. `fork.forkedFrom`
(`forked_from`) is `{agentId, atMessage}`, and `agents.fork(id, options)` forks
an agent by id. Available from `@camelai/run` 0.14.0 and `camelai-run` 0.10.0.

### `Agent`

| TypeScript                                                       | Python                           |                                                                                                                            |
| ---------------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `agent.id`                                                       | `agent.id`                       | `client_...`, safe to log and store                                                                                        |
| `agent.run(text, options)`                                       | `await agent.run(text, ...)`     | Send a message and wait for the `Run`                                                                                      |
| `agent.stream(text, options)`                                    | `agent.stream(text, ...)`        | The run as it happens: `for await` / `async for` over parts                                                                |
| `agent.pendingInputs()`                                          | `pending_inputs()`               | Inputs waiting on people, each with `answer()`                                                                             |
| `agent.history()`, `historyPage({ before, limit })`              | `history()`, `history_page(...)` | The whole history, or a page of whole turns                                                                                |
| `agent.steer(text)`                                              | `steer()`                        | Join the running turn, or start one                                                                                        |
| `agent.configure({ model, instructions, thinkingLevel, tools })` | `configure(...)`                 | Change it between runs                                                                                                     |
| `agent.schedule({ text, inSeconds, at, everySeconds })`          | `schedule(...)`                  | Wake it later; `schedules()`, `unschedule(id)`                                                                             |
| `agent.files`                                                    | `agent.files`                    | `list`, `download`, `upload`, `link` by the paths the agent sees                                                           |
| `agent.abort()`                                                  | `abort()`                        | Stop the running turn                                                                                                      |
| `agent.fork({ key, name, atMessage })`                           | `fork(key=, name=, at_message=)` | A new agent with its configuration, a copy of its history and of its files. See [Forking](/docs/camelrun/concepts#forking) |
| `agent.delete()`                                                 | `delete()`                       | Delete it, its history and files                                                                                           |
| `agent.close()`                                                  | `close()`                        | Close this process's connection                                                                                            |

Run options for `run` and `stream`:

| TypeScript          | Python                |                                                                                                                                                                                                               |
| ------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user`              | `user=`               | Who sent it: your user id, or `{ id, name, username }`                                                                                                                                                        |
| `files`             | `files=`              | Attachments. See [Files](/docs/camelrun/files)                                                                                                                                                                |
| `metadata`          | `metadata=`           | Your own key-value data (16 strings); the model never sees it                                                                                                                                                 |
| `idempotencyKey`    | `idempotency_key=`    | The run's id. The same key returns the same run                                                                                                                                                               |
| `signal`            | `timeout=`            | Stop waiting; the run goes on                                                                                                                                                                                 |
| `throwOnError`      | `throw_on_error=`     | `false` returns a failed run instead of throwing `RunError`                                                                                                                                                   |
| `whileRunning`      | `while_running=`      | `"queue"` (default) or `"steer"`                                                                                                                                                                              |
| `spendLimit`        | `spend_limit=`        | `{usd}`: this run's own budget                                                                                                                                                                                |
| `allowDisconnected` | `allow_disconnected=` | Run even with nobody serving the agent's tools                                                                                                                                                                |
| `output`            | `output=`             | Structured output: a zod, TypeBox or JSON Schema (Python: a pydantic model class, or a JSON Schema dict) for an object. The answer is `run.output`. See [Structured output](/docs/camelrun/structured-output) |
| `traceparent`       | `traceparent=`        | A W3C trace context (`00-<trace-id>-<span-id>-<flags>`): with [trace export](/docs/camelrun/observability) set, the run's spans continue your trace under that span. Not part of the run's idempotency        |

### `Run` and stream parts

A `Run` is `{ id, status, text, output?, inputs, error, usage, files, toolErrors, toolCalls, sourceErrors, raw }`
(Python: `tool_errors`, `tool_calls`, `source_errors`). `toolCalls` lists every
tool call the run made, with `ok` or an error `code`, not their arguments or
results (see [Run outcomes](/docs/camelrun/events#run-outcomes)). `output` is the
answer of a run sent with `output`, typed by its schema (Python: an instance of
the pydantic model). `status` is `completed`,
`input_required` or `failed`. Each of `run.inputs` has `answer(value, { from })`
and `decline({ from })` (Python: `answer(value, from_=)` and `decline(from_=)`),
which resolve with the resumed run.

| Part `type`      | Fields                                                |
| ---------------- | ----------------------------------------------------- |
| `text`           | `text`: reply text as it's written                    |
| `tool_call`      | `id`, `name`, `arguments`                             |
| `tool_result`    | `id`, `name`, `output`, `isError` (Python `is_error`) |
| `input_required` | `input`, with `answer()`                              |
| `done`           | `run`, always last                                    |

`run.toolCalls` names a call's tool `tool` and its id `toolCallId`. Stream parts
carry the same values as `name` and `id`, and, from `@camelai/run` 0.14.0 and
`camelai-run` 0.10.0, also as `tool` and `toolCallId` (Python `tool_call_id`).
`name` and `id` stay.

## Tools

```ts TypeScript
tool({ description, input: schema.Object({ ... }), execute: (args, context) => result,
       timeoutMs?, needsApproval?, exposure? })
```

```python Python
@tool(name=None, description=None, timeout=None, needs_approval=None)
def my_tool(arg: str, context: ToolContext): ...
```

| `context` (TypeScript)         | Python                                 |                                                                    |
| ------------------------------ | -------------------------------------- | ------------------------------------------------------------------ |
| `idempotencyKey`               | `idempotency_key`                      | The same for every attempt of this call                            |
| `callId`                       | `call_id`                              | This attempt's id                                                  |
| `identity`                     | `identity`                             | Who the call is for. See [Identity](/docs/camelrun/tools#identity) |
| `signal`                       | (task cancellation)                    | Aborted when camelRun cancels the call                             |
| `progress(message)`            | `progress(message, progress=, total=)` | Report progress; restarts the deadline                             |
| `confirm`, `ask`, `requireUrl` | `confirm`, `ask`, `require_url`        | Ask the user. See [Human input](/docs/camelrun/human-input)        |

With several processes (web workers, task queues, serverless), serve tools over
HTTP: see [Several processes, workers and deploys](/docs/camelrun/tools#several-processes-workers-and-deploys).

## Errors

`AgentError` has `status` (HTTP, or 0), `code` (a stable name where camelRun
gives one), `requestId`, `uncertain` and `retryAfterMs` (Python `request_id`,
and `retry_after` in seconds). `RunError` is an `AgentError` for a failed run,
with `run`.

## The lower-level API

`Agents` and `Agent` are built on `AgentRuntime` and `AgentClient`, which stay
available for code that needs the wire's shape: `agents.runtime`,
`agent.client`, or `new AgentRuntime({ url, apiKey })` directly.

| TypeScript                                                                                                  | Python                                                                                                                     |                                                                                                                                                      |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runtime.upsertAgent(key, options)`                                                                         | `upsert_agent(key, ...)`                                                                                                   | Upsert, returning credentials without connecting                                                                                                     |
| `runtime.connectAgent(session, { tools, attach, takeover })`                                                | `connect_agent(...)`                                                                                                       | Connect with stored credentials                                                                                                                      |
| `runtime.me()`                                                                                              | `me()`                                                                                                                     | Your `tenant` id and default model                                                                                                                   |
| `runtime.upsertDefinition(key, input)`                                                                      | `upsert_definition(key, ...)`                                                                                              | Definitions; also `createDefinition`, `updateDefinition`, `deleteDefinition`                                                                         |
| `runtime.setProvider(name, config)`                                                                         | `set_provider(name, ...)`                                                                                                  | A [custom provider](/docs/camelrun/custom-models)                                                                                                    |
| `runtime.createVolume`, `volume(id)`                                                                        | `create_volume`, `volume(id)`                                                                                              | Volumes                                                                                                                                              |
| `runtime.inbox(state)`                                                                                      | `inbox(state=)`                                                                                                            | Inputs across all agents                                                                                                                             |
| `client.execute(code)`                                                                                      | `execute(code)`                                                                                                            | Run code in the sandbox with the agent's tools, outside its history                                                                                  |
| `client.waitForRequest(id)`                                                                                 | `wait_for_request(id)`                                                                                                     | Wait for a request already sent, from any process                                                                                                    |
| `client.requestStatus(id, { wait })`                                                                        | `request_status(id, wait=)`                                                                                                | A request's record; `wait` (seconds, at most 25) waits for it to settle first, with no stream connected                                              |
| `runtime.telemetry.set({ endpoint, headers, protocol, sampleRate, include })`, `get()`, `test()`, `clear()` | `await runtime.telemetry.set(endpoint, headers=, protocol=, sample_rate=, include_content=)`, `get()`, `test()`, `clear()` | [Trace export](/docs/camelrun/observability): `set` changes only what it is given; `get` shows header names, never values; `test` sends one span now |

```ts
await agents.runtime.telemetry.set({ endpoint: "https://api.smith.langchain.com/otel/v1/traces", headers: { "x-api-key": process.env.LANGSMITH_API_KEY! } });
const { ok, traceId } = await agents.runtime.telemetry.test();
```

```python
await agents.runtime.telemetry.set("https://api.smith.langchain.com/otel/v1/traces", headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]})
result = await agents.runtime.telemetry.test()
```

## Examples

Both examples read `CAMELAI_API_KEY`:

### A tool that needs approval

```ts
import { Agents, schema, tool } from "@camelai/run";

const agents = new Agents();
const agent = await agents.upsert("approval-example", {
  model: "anthropic/claude-sonnet-5-5",
  instructions: "You manage preview environments. Delete the ones you are asked to, with the tool.",
  tools: {
    delete_environment: tool({
      description: "Delete a preview environment",
      input: schema.Object({ name: schema.String() }),
      needsApproval: true,
      execute: ({ name }, context) => previews.delete(name, { idempotencyKey: context.idempotencyKey }),
    }),
  },
});

let run = await agent.run("Delete the preview environment pr-4312.", { user: "alice" });
while (run.status === "input_required") {
  const input = run.inputs[0];
  console.log(`waiting for ${input.kind}: ${input.message}`);
  run = await input.answer(true, { from: "alice" });
}
console.log(run.text);
await agents.close();
```

### Files in and out

```ts
import { Agents } from "@camelai/run/node";

const agents = new Agents();
const agent = await agents.upsert("reporter", {
  model: "anthropic/claude-sonnet-5-5",
  instructions: "You are an analyst. Write reports to /workspace/out and present them with present_file.",
});
const run = await agent.run("Summarize this CSV into a report", { files: ["./sales.csv"] });
for (const file of run.files) {
  const { data } = await agent.files.download(file.path);
  console.log(file.path, data.byteLength);
}
await agents.close();
```
