@stesura/ai-server
Route handler factories for the AI feature (Anthropic, through the AI SDK) and the request
guard that protects them. Each factory returns a (req: Request) => Promise<Response> handler
to mount as a POST route; see Server routes for setup.
Route handlers
createTextActionHandler
Route handler factory for the @stesura/ai text actions. Mount it as a
POST handler (Next.js app router: export const POST = createTextActionHandler({ model })).
Request: { actionId, target, before?, after?, params? } — an action id the
SERVER has a prompt template for, plus data fragments it fences into that
template. The prompt never travels: a client-authored system string turned
the route into a generic completion proxy on the operator’s key (AIS-2).
Response: { text: string }.
Auth and rate limiting are deliberately the host app’s responsibility —
wrap the returned handler in withRequestGuard or your middleware of choice.
| Name | Type | Default |
|---|---|---|
options | CreateTextActionHandlerOptions |
createCompletionHandler
Same contract as createTextActionHandler, tuned for tab autocomplete: a fast, cheap model and a tight output cap. Mount on its own route so completions and text actions can be metered separately.
| Name | Type | Default |
|---|---|---|
options | CreateTextActionHandlerOptions |
CreateTextActionHandlerOptions
Options for createTextActionHandler and createCompletionHandler.
createChatHandler
Streaming chat route with document tools. Editor tools carry no execute —
the client runs them against the editor (via executeAiTool) and sends the
results back with the next request, so the model can chain read → edit.
Server tools run here, inside the loop, under the serverTools policy.
Request: { messages: UIMessage[], docContext?, attachments?, model?, mode? }
— model must come from the configured models allowlist; mode: "ask"
restricts the declared tool set to read-only tools (the gate is server-side
on purpose — client-side filtering is UX, not enforcement). Messages may only
carry the user and assistant roles: the system prompt is the server’s.
Response: UI message stream (AI SDK).
Auth and rate limiting are the host app’s responsibility (withRequestGuard).
| Name | Type | Default |
|---|---|---|
options | CreateChatHandlerOptions |
CreateChatHandlerOptions
Options for createChatHandler. A model is required: pass model, or a
models allowlist the client picks from by id (the body’s model field;
the first entry is the default, an id outside it is a 400). Any AI SDK
provider: the host constructs it with its own key.
Custom actions
Extra prompt templates go in the text-action handler’s actions option, keyed by action id. See
Prompts live on the server.
AiActionTemplate
Builds the server-owned prompt for a built-in action.
| Name | Type | Default |
|---|---|---|
params | TextActionParams | |
payload | AiActionPayload |
Request guard
See Securing the routes.
withRequestGuard
Minimal protection wrapper for the AI route handlers. This is a floor, not a substitute for the host app’s real auth:
export const POST = withRequestGuard(createChatHandler(), {
bearerToken: process.env.AI_ROUTE_TOKEN,
rateLimit: { windowMs: 60_000, max: 20 },
});Throws when neither bearerToken nor allowAnonymous is set: an
unauthenticated LLM proxy is never the right default.
| Name | Type | Default |
|---|---|---|
handler | Handler | |
options | RequestGuardOptions |
RequestGuardOptions
Options for withRequestGuard: set bearerToken, or opt out with allowAnonymous.