/** * Durable Object helper for hosting the agent pairing + LAP surface * on Cloudflare Workers. One DO instance owns one `tid` — its * in-memory `PairingRegistry` survives across Worker isolate * invocations because the DO IS persistent. * * This file exports a class designed to be composed into a real * Durable Object in the user's Worker project. We intentionally * don't subclass `DurableObject` from `@cloudflare/workers-types` — * that dependency belongs to the user's project, not ours. Users * wrap an instance of `AgentPairingDurableObject` in their own DO * class and forward `fetch` to it. * * Usage in a Worker project: * * ```ts * // worker.ts * import { AgentPairingDurableObject } from '@llui/agent/server/cloudflare' * * export class AgentDO { * private agent: AgentPairingDurableObject * constructor(_state: DurableObjectState, env: Env) { * // Tokens are opaque (see token.ts) — no signing key needed. * this.agent = new AgentPairingDurableObject({}) * } * fetch(req: Request): Promise { * return this.agent.fetch(req) * } * } * * export default { * async fetch(req: Request, env: Env): Promise { * // routeToAgentDO now takes a `resolveTid` callback — typically * // a fetch to the root DO's token-resolution endpoint, or a * // const stub when you don't shard by tid. * return routeToAgentDO(req, env.AGENT_DO, async (token) => { * const stub = env.AGENT_DO.get(env.AGENT_DO.idFromName('__root')) * // Send the bearer in a header (and POST), never the query * // string — a token in the URL lands in Workers request logs and * // traces. `__resolve` must be reachable ONLY via this internal * // DO stub, never wired to a public route. * const r = await stub.fetch('http://internal/__resolve', { * method: 'POST', * headers: { authorization: `Bearer ${token}` }, * }) * const body = (await r.json()) as { tid: string | null } * return body.tid * }) * }, * } * ``` * * See `./worker.ts` for `routeToAgentDO` and the full wiring. */ import type { CoreOptions, AgentCoreHandle } from '../core.js'; import type { McpRouter, McpRouterOptions } from '../mcp/router.js'; export type DurableObjectOptions = Omit & { /** * Enable the server-side MCP endpoint at `/agent/mcp` (or a custom * path). Pass `true` for all defaults, or an `McpRouterOptions` * object to customise path, server name, and connect_session * description. */ mcp?: boolean | McpRouterOptions; }; /** * Agent server instance scoped to a single Durable Object. All * pairing state lives in the DO's in-process memory — which is safe * here because the DO is a persistent addressable entity, not a * one-shot Worker isolate. * * Users instantiate one of these inside their DO class's constructor * and delegate `fetch` to `agent.fetch(req)`. LAP HTTP routes, * WebSocket upgrades, the optional MCP endpoint, and the internal * `/__resolve` token-resolution endpoint all flow through this single * entry. * * ── SHARDED-DEPLOYMENT REQUIREMENT ──────────────────────────────── * `routeToAgentDO` shards by `tid`: the root DO (`__root`) owns * `/agent/mint` and friends, while LAP/WS calls route to a per-tid DO. * Because each DO defaults to its own `InMemoryTokenStore`, a token * minted on the root DO is INVISIBLE to a per-tid DO — `/__resolve` * (and every LAP auth check) would 401. So the sharded recipe REQUIRES * a single shared external `TokenStore` (a KV/D1-backed implementation * of the `TokenStore` interface) injected into EVERY DO: * * ```ts * const tokenStore = new KvTokenStore(env.AGENT_KV) // your adapter * this.agent = new AgentPairingDurableObject({ tokenStore }) * ``` * * The only no-shared-store option is to NOT shard — route everything * through the root DO by passing `() => Promise.resolve('__root')` as * `resolveTid`. Then all state (tokens + pairings) lives in one DO and * the default `InMemoryTokenStore` is sufficient, at the cost of a * single-DO bottleneck. */ export declare class AgentPairingDurableObject { readonly agent: AgentCoreHandle; /** * The MCP router when `mcp` is enabled, else `null`. Public because a * DO's 128 MB ceiling makes its session bound operationally * interesting — `mcpRouter.liveSessionCount()` is how a deployment (or * a test) reads it. This is the SAME `createMcpRouter` the Node * factory builds, so both surfaces are bounded by one implementation. */ readonly mcpRouter: McpRouter | null; constructor(opts: DurableObjectOptions); fetch(req: Request): Promise; /** * Resolve `Authorization: Bearer ` to its `tid` via the shared * token store. Returns `{ tid: string | null }` — `null` for an unknown * / malformed / expired-hash token (uniform, no probe surface). POST * only; the bearer travels in the header (never the URL) so it stays * out of Workers request logs. */ private resolveToken; } //# sourceMappingURL=durable-object.d.ts.map