Documentation
Tools
Built-in tools and the full long-poll callback protocol for custom function tools. Your handler, your process — Relay just orchestrates.
Two kinds of tools
Built-in tools execute server-side in the Go runtime. Custom function tools execute in your process — the runtime calls back to your SDK over an open stream.
import { createAgent, builtin, tool } from "@relayhq/sdk";
const agent = createAgent({
model: "claude-sonnet-4-6",
tools: [
builtin.calculator, // runs in the runtime
myCustomTool, // runs in your process
],
});Built-in tools
Today the registry has one tool. More land as the platform grows. Built-ins are useful when the operation is fast, common, and shouldn't cost a round-trip.
builtin.calculator
Performs a single arithmetic operation on two numbers. Useful as a control for testing tool calls without writing your own.
{
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" },
"op": { "type": "string", "enum": ["+", "-", "*", "/"] }
},
"required": ["a", "b", "op"]
}Custom function tools
The pattern is: schema + handler. The schema goes to the LLM. The handler runs locally.
import { tool } from "@relayhq/sdk";
export const getUser = tool({
name: "get_user",
description: "Look up a user by id. Returns name, tier, balance.",
inputSchema: {
type: "object",
properties: {
id: { type: "string", description: "user id like u_001" },
},
required: ["id"],
additionalProperties: false,
},
async handler({ id }: { id: string }) {
const user = await db.users.findById(id);
if (!user) return { error: `no user with id ${id}` };
return user;
},
});Naming & descriptions
Tool names should be snake_case, 1–64 chars. Descriptions are part of the prompt — write them like you're briefing a colleague: be specific about inputs, outputs, and side effects.
Input schemas
Plain JSON Schema. Use additionalProperties: false to keep the model honest. The runtime forwards the schema verbatim to both Anthropic and OpenAI; provider quirks are normalized for you.
Return values & errors
Whatever your handler returns gets JSON-serialized as the tool result. Throwing turns into "error: <message>"; the model sees it and almost always self-corrects on the next iteration.
How the callback works
Custom tools work through a long-poll callback — the runtime orchestrates, but execution stays in your process. End-to-end:
The runtime stays stateless. The SDK never talks to the runtime directly. Every event is persisted in run_events on the way through.
Limits & timeouts
- Tool result timeout: 30 seconds by default. Configure with
RELAY_TOOL_RESULT_TIMEOUT_MSon the control plane. - Max iterations per run: 8. The agent loop bails after 8 tool round-trips to prevent infinite cycles.
- Parallel tool calls: supported. The SDK dispatches all custom tool handlers concurrently when the model fires more than one in a turn.
- Tool input size: bounded by provider — Anthropic and OpenAI both cap at ~100K characters per call.