HTTP with Hono

Second BYO HTTP stack: agent SSE and interruptible graph resume on Hono, installing @monorch/ai from npm (not the monorepo workspace).

1. Install (published package)

Clone the repo example or copy the pattern into your app. The example lives outside the pnpm workspace so it cannot accidentally link packages/ai.

terminal
cd examples/hono-npm
npm install          # pulls @monorch/ai@0.2.0 from the registry
npm run smoke        # or: pnpm smoke:hono from the monorepo root

2. Agent + SSE

stream.ts
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { agent, mock, tool } from "@monorch/ai";
import { z } from "zod";

const add = tool({
  name: "add",
  input: z.object({ a: z.number(), b: z.number() }),
  permission: { type: "roles", roles: ["agent"] },
  execute: ({ a, b }) => ({ sum: a + b }),
});

const app = new Hono();

app.post("/support/stream", async (c) => {
  const bot = agent({
    name: "support",
    model: mock([
      { toolCalls: [{ id: "c1", name: "add", arguments: { a: 2, b: 3 } }] },
      { text: "2 + 3 = 5" },
    ]),
    tools: [add],
  });
  return streamSSE(c, async (stream) => {
    for await (const ev of bot.stream("2+3")) {
      await stream.writeSSE({ data: JSON.stringify(ev) });
    }
  });
});

3. Interrupt + resume

refund.ts
import { AiError, graph, memorySaver } from "@monorch/ai";

const refund = graph("refund")
  .node("lookup", async ({ input }) => ({
    output: `order:${input.orderId}`,
    state: { orderId: input.orderId },
  }))
  .interrupt("approve", { prompt: "Approve refund?" })
  .node("pay", async ({ outputs }) => `refunded:${outputs.lookup}`)
  .compile({ checkpointer: memorySaver() });

app.post("/refund", async (c) => {
  const { orderId, threadId } = await c.req.json();
  const run = await refund.start({ orderId }, { threadId });
  return c.json({ status: run.status, threadId });
});

app.post("/refund/:threadId/resume", async (c) => {
  try {
    const run = await refund.restore(c.req.param("threadId"));
    const done = await run.resume("approved");
    return c.json({ status: done.status, outputs: done.outputs });
  } catch (e) {
    if (e instanceof AiError && e.code === "CHECKPOINT_NOT_FOUND") {
      return c.json({ error: "unknown thread" }, 404);
    }
    throw e;
  }
});

Fastify variant: HTTP with Fastify. HITL details: HITL refund. Failure codes: Errors & failure modes.

FAQ