Codegen
Generate a typed HTTP client and React bindings from the server app.
The usual TypeScript path is import type from the server PAI module. That is what React chat does.
Use codegen when the frontend cannot safely type-import from the server package, or when you want a generated folder that stays in lockstep with the app contract.
pnpm add -D @pai/cliExport an inferred app contract from the server:
import { createPai, type InferPaiContract } from "@pai/core";
export const pai = createPai({
agents: { assistant: assistantAgent, sales: salesAgent },
scopeKey: (identity) => identity.workspaceId,
});
export type AppPai = InferPaiContract<typeof pai>;Keep the agents (and tools / commands) maps as object literals. Widening them to Record<string, ...> discards the keys, and the CLI rejects the contract instead of generating an incomplete one.
pnpm exec pai generate client \
--source server/pai.ts \
--type AppPai \
--out-dir web/src/generated/pai \
--name Pai--type is the exported server alias. --name is the generated API stem (Pai, PaiContract, createPaiClient, …).
| File | Contents |
|---|---|
types.d.ts | Flattened runtime-free contract types |
contract.ts | Runtime contract metadata |
client.ts | HTTP client factory and React bindings |
index.ts | Barrel |
import {
AssistantAgent,
Pai,
createPaiClient,
} from "./generated/pai";
export const paiClient = createPaiClient({
url: "http://localhost:3001/api/pai",
headers: () => ({
"x-user-id": "dev-user",
"x-workspace-id": "dev",
}),
});
export { AssistantAgent, Pai };Add a check script so CI fails when the generated folder is stale:
{
"scripts": {
"pai:generate": "pai generate client --source server/pai.ts --type AppPai --out-dir web/src/generated/pai --name Pai",
"pai:check": "pai generate client --source server/pai.ts --type AppPai --out-dir web/src/generated/pai --name Pai --check"
}
}Keep the server typecheck as a separate CI step; codegen validates generated contracts and file freshness.