API Reference
SDKs
The TypeScript and Python SDKs: Agents, upsert, run, stream, tools and the lower-level API
View as Markdown · Docs index for coding agents
The two SDKs have the same features, named in each language's style; their version numbers are their own and do not line up.
npm install @camelai/run # Node 22+, Bun, Deno, Cloudflare Workers| 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
const agents = new Agents({ apiKey, url }); // or `await using agents = new 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.
apiKeydefaults toCAMELAI_API_KEY, andurltoCAMELAI_BASE_URL, elsehttps://run.camelai.com.agents.close()closes every agent's connection so the process can exit. Their runs go on in camelRun.agents.runtimeis the lower-levelAgentRuntime: definitions, volumes,listAgents(),browserToken(agentId),inbox()andtoolSources(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 |
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.
const agent = await agents.get("support-triage"); // or its idagent = 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.
const fork = await agent.fork({ key: "support-b", atMessage: 5 });
await fork.run("Try the other approach");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 |
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 |
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 |
traceparent | traceparent= | A W3C trace context (00-<trace-id>-<span-id>-<flags>): with trace export 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). 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
tool({ description, input: schema.Object({ ... }), execute: (args, context) => result,
timeoutMs?, needsApproval?, exposure? })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 |
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 |
With several processes (web workers, task queues, serverless), serve tools over HTTP: see 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 |
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: set changes only what it is given; get shows header names, never values; test sends one span now |
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();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: