@pai/hono
Hono adapter for mounting a PAI app receiver.
@pai/hono mounts every agent in a createPai({ agents }) registry under a
single Hono route.
pnpm add @pai/hono honocreatePaiHonoReceiver
function createPaiHonoReceiver<
TPai extends PaiLike,
THostContext = undefined,
TEnv extends Env = Env,
>(
input: CreatePaiHonoReceiverOptions<TPai, THostContext, TEnv>,
): PaiHonoReceiver<TEnv>;import { createPai, type InferPaiContract } from "@pai/core";
import { createPaiHonoReceiver } from "@pai/hono";
import { Hono } from "hono";
import { assistantAgent } from "./assistant-agent";
export const pai = createPai({
agents: {
main: assistantAgent,
},
scopeKey: (identity) => identity.workspaceId,
});
export type AssistantPai = InferPaiContract<typeof pai>;
const app = new Hono();
app.route(
"/api/pai",
createPaiHonoReceiver({
pai,
resolveIdentity: async ({ request }) => ({
identity: {
userId: request.headers.get("x-user-id") ?? "anonymous",
workspaceId: request.headers.get("x-workspace-id") ?? "default",
},
}),
}),
);If the receiver is mounted at /api/pai, the main agent is served under
/api/pai/main. App clients should point at the app route:
const paiClient = createPaiHttpClient<AssistantPai>({
url: "/api/pai",
});
const assistant = paiClient.agent("main");Options
type CreatePaiHonoReceiverOptions<
TPai extends PaiLike,
THostContext = undefined,
TEnv extends Env = Env,
> = {
pai: TPai;
resolveIdentity:
| ResolveHttpAgentRequest<any, THostContext>
| {
[agentId: string]: ResolveHttpAgentRequest<any, THostContext>;
};
access?: Partial<Record<string, "full" | "read" | "none">>;
getContext?: (context: Context<TEnv>) => THostContext | Promise<THostContext>;
httpTelemetry?: HttpAgentTelemetryOptions;
requestLimits?: HttpAgentRequestLimits;
deriveFileUrlFallback?: boolean;
mapHttpError?: HttpAgentErrorMapper;
onHttpError?: false | HttpAgentErrorHandler;
};Use one resolveIdentity function when all agents share the same auth and scope
policy. Use the object form when each agent needs a different resolver.
createPaiHonoReceiver({
pai,
resolveIdentity: {
support: resolveSupportIdentity,
reviewer: resolveReviewerIdentity,
},
access: {
reviewer: "read",
},
});access: "read" allows manifest, thread list, thread state, watch, run read,
and file read routes while rejecting mutating operations. access: "none" skips
mounting that agent.
mapHttpError converts recognized receiver exceptions into safe
PaiErrorDetails. onHttpError receives the original trusted exception plus
the same safe error and diagnostic reference sent to the client. Omit the
reporter for PAI's default server console reporting, replace it with your
logger, or pass false to suppress reporting.
See Error Handling for mapping, correlation, and redaction.
Lifecycle
createPaiHonoReceiver() returns a Hono app with close(). Call it from your
server cleanup path when the receiver owns runtimes, storage, realtime, files,
or telemetry resources through the createPai app.
const routes = createPaiHonoReceiver({ pai, resolveIdentity });
process.on("SIGTERM", () => {
void routes.close();
});