/** * ONE mount: the whole agent over HTTP. * * A library cannot add a route to the host's server, so — like `door` and * `permissions` — this comes back as a fetch handler for the host to mount: * * const handle = agentHandler(support, { basePath: "/api/agent", resolveUser }); * // Next: export { handle as GET, handle as POST, handle as DELETE } * // Hono: app.all("/api/agent/*", (c) => handle(c.req.raw)) * * Three planes come off the one catch-all, in the order a request meets them: * the engine's dial-back door (always-on internal plumbing on its own fixed * path, answering a live turn's own credential and nothing else), the * approvals/grants wire under THIS mount, and then its own table — the chat * turn, the thread lifecycle, the durable resume. * * The permission wire is mounted HERE, on this basePath and behind this mount's * `resolveUser`, rather than forwarded to `agent.permissions` on its fixed * `/api/vendo`. Two reasons, and the browser found both: deciding an approval * is what UNBLOCKS a parked turn (the guard's decision resolves the waiter the * turn is sitting on — packages/vendo/src/harnesses/turn-tools.ts:122,382), so a * client that cannot reach this wire has a park it can never answer; and a * standalone host configures identity once, in `resolveUser`, so the asks a * person sees must be scoped to the same person the turns are. * * The table runs on `./http/router.js`, the SAME route runtime the umbrella's * wire runs on, so a standalone mount and the embed cannot drift into two * routers with two ideas of what `/threads/:id` means. * * MOUNTING NOTE: the door keeps its own absolute path (`DOOR_PATH`) because that * is the address the box dials. A deployment whose engine thinks outside this * process therefore routes that path here too — mounting this handler at * `/api/vendo` puts it and everything else under one catch-all. */ import { type Json } from "../core/index.js"; import { type VendoAgent } from "./agent.js"; /** Who the host says is asking. */ export interface HandlerUser { /** The subject every thread, grant and audit row on this request is scoped to. */ subject: string; /** Server-trust identity facts, model-visible (`[User]`). */ profile?: Record; /** Guard/tools context: functions run at check-time, data survives parking. */ context?: Record; } /** * Two options the v1 spec names are deliberately ABSENT rather than forgotten. * * `publicOrigin` could not take effect: the door's origin is fixed when * `agent()` composes, and `resolveDoor` already throws the boot error naming * both ways out for a sandboxed engine with no origin (door.ts:96-107) — long * before a mount exists. `mcp` governs a PUBLIC MCP/auth plane, which this * package does not serve; the spec defaults it OFF, so omitting it and * defaulting it off are the same behaviour. Both are additive the day either * has something to do. */ export interface HandlerOptions { /** Where the host mounted this handler. */ basePath: string; /** The host's own session, read per request. `null` is UNAUTHENTICATED and * answers 401 — a mount with nobody asking serves nobody's conversation. */ resolveUser: (request: Request) => Promise; /** What the turn's own tools forward as the caller's authority. Unset → this * request's own headers, which is what a same-origin host wants; `false` → * nothing. Per request either way: request-lifetime authority does not * outlive the request. */ headers?: Record | false; } export declare function agentHandler(agent: VendoAgent, options: HandlerOptions): (request: Request) => Promise;