Skip to content

Getting started

Status: Implemented and published (v0.1). Install the packages from npm (pnpm add @orpc-agent/core @orpc/server); inside this repository the walkthrough below also works as written against the workspace packages, and the customer-support example runs it end to end.

Goal: from an existing oRPC app to a governed AI-SDK tool call in five steps — then the one-line paths to everything else.

Prerequisites

  • An oRPC application (@orpc/server) with typed procedures and a context factory
  • Vercel AI SDK v5 for the model loop (any provider)
  • Node 20+, pnpm
bash
pnpm add @orpc-agent/core @orpc-agent/ai-sdk

1. Type your base for agents

ts
// src/orpc.ts
import { os } from "@orpc/server";
import { agentProcedure } from "@orpc-agent/core";

export const base = os.$context<AppContext>().use(requireSession);   // you already have this
export const agentBase = agentProcedure(base);                       // adds typing only

2. Annotate a procedure → it becomes a capability

ts
// src/capabilities/orders.ts
import * as z from "zod";
import { agentBase } from "../orpc";

export const searchOrders = agentBase
  .meta({
    agent: {
      description: "Search orders by customer email or order number.",
      expose: { aiSdk: true },        // deny-by-default: only what you name (SI-1)
      sideEffect: "read",
      risk: "low",
    },
  })
  .input(z.strictObject({ query: z.string().min(2), limit: z.number().int().max(50).default(10) }))
  .output(z.object({ orders: z.array(OrderSummary) }))
  .handler(async ({ input, context, signal }) => ({
    orders: await context.orders.search(input, { signal }),
  }));

Existing routers, HTTP handlers, and OpenAPI output are untouched — the agent block is inert outside the agent runtime.

3. Register and create the runtime

ts
// src/agent.ts
import { createCapabilityRegistry, createAgentRuntime } from "@orpc-agent/core";
import { searchOrders } from "./capabilities/orders";

export const capabilities = createCapabilityRegistry({
  orders: { search: searchOrders },
});

export const runtime = createAgentRuntime({
  registry: capabilities,
  // policies, approvals, audit, tracing — all optional to start; add as you grow
});

Startup validates every capability's metadata and fails loudly on problems.

4. Hand tools to your model loop

ts
// src/api/chat.ts
import { streamText, stepCountIs } from "ai";
import { toAISDKTools } from "@orpc-agent/ai-sdk";
import { runtime } from "../agent";

export async function POST(req: Request) {
  const session = await requireSession(req);
  const tools = await toAISDKTools(runtime, {
    actor: { id: session.userId, kind: "user" },     // authenticated identity — never the model (SI-3)
    context: await createAppContext(session),
  });

  return streamText({ model, messages: await req.json(), tools, stopWhen: stepCountIs(5) })
    .toUIMessageStreamResponse();
}

The model can now call orders_search. Input is validated by your schema, your middleware runs, errors come back model-safe, and every call is traceable.

5. Verify without a model

bash
pnpm add -D @orpc-agent/testing
ts
import { createAgentTestRuntime } from "@orpc-agent/testing";
import { capabilities } from "../src/agent";

test("search is exposed to aiSdk and validates input", async () => {
  const t = createAgentTestRuntime({ registry: capabilities, context: testContext });
  expect((await t.describe("aiSdk")).map(d => d.id)).toContain("orders.search");

  const bad = await t.invoke("orders.search", { query: "" }, { surface: "aiSdk" });
  expect(bad.error.code).toBe("INPUT_INVALID");
});

Growing up from here

Each next need is one addition, not a rewrite — the simple path and the governed path are the same abstractions:

NeedAddGuide
A write operationsideEffect: "write", strict schema, declared errorsdefining-capabilities
"Over $500 needs a manager"a policy + approvals configadding-policies, human-approval
Compliance trailaudit: sink (a table + one insert)auditing
Traces in your APMtracing: createOpenTelemetryTracing()adapters/opentelemetry
External MCP clients@orpc-agent/mcp + per-session identityadapters/mcp
Hide fields from modelsredact.outputsensitive-data
Migrate hand-written toolsone tool at a timemigrating-existing-tools

Recommended reading order after this page: concepts/capabilitiesconcepts/lifecyclesecurity/security-model → the customer-support example.

Independent community project — not affiliated with or endorsed by the oRPC maintainers.