createChatHandler

createChatHandler(options) returns GET, POST, DELETE, and OPTIONS for a single catch-all route. It authenticates requests, binds persistence and storage to the verified user, streams the model response, and persists completed turns.

code
export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
  getUserId: getChatUserId,
  model: anthropic('claude-sonnet-4-5'),
  store: createDrizzleChatStore(),
});

Required identity boundary

getUserId(request: Request) => string | null | Promise<string | null>required

Return identity from a verified server session. null produces 401. Never use request-body, query-string, or browser-controlled header identity.

Conditionally required runtime dependencies

modelLanguageModel | ((ctx) => LanguageModel | Promise<LanguageModel>)

Required unless getHostedConfig returns a model. Precedence is code, then hosted config, then an error. There is no package-default model.

storeChatStoreFactory

Currently required. Pass a Drizzle, hosted, or custom factory. Omitting it throws on the first chat request; there is no silent persistence default.

storageStorageAdapterFactory

Optional. Omitting it disables uploads.

buildTools(ctx) => BuiltTools | Promise<BuiltTools>

Optional request-scoped tools. Return { tools, cleanup? }; cleanup runs once when the request settles.

Configuration and hooks

getHostedConfig(ctx) => HostedAgentConfig | null | Promise<HostedAgentConfig | null>

Best-effort hosted configuration. Code values win. The current hosted shape can supply model, system prompt, greeting, appearance, output cap, and follow-up settings.

buildSystemPrompt(ctx) => string | Promise<string>
Server-authoritative system prompt.
transformMessages(messages, ctx) => ModelMessage[] | Promise<ModelMessage[]>
Transform the pruned conversation payload.
getContext(ctx, clientContext) => Record<string, unknown> | null | Promise<...>
Validate or replace untrusted client context.
trustClientContextbooleandefault: false
Inject client context verbatim. Use only for non-sensitive hints.
followUpsboolean | ServerFollowUpConfig
Generate server-side follow-up chips. Default off.
onFeedback(feedback, ctx) => void | Promise<void>
Persist verified feedback submissions.
onChatFinish({ ctx, messages, usage, providerMetadata }) => void | Promise<void>
Post-persistence telemetry hook.
onError(error) => string
Map stream errors to safe user-facing text.
logErrorsbooleandefault: true
Keep server-side stream error logging enabled unless another sink replaces it.
stopWhenStopCondition<ToolSet>
Bound tool-call loops.
retrievalRetrievalConfig
Optional read-only RAG integration.
memoryMemoryConfig
Optional long-term memory.
summarizeHistoryHistorySummarizer
Optional summary of messages dropped by the sliding window.

Limits

maxOutputTokensnumber
Code cap, then hosted cap, then provider default.
maxHistoryMessagesnumberdefault: 30
Recent messages sent to the model.
maxMessageCharsnumberdefault: 4000
Per-message character cap; 0 disables it.
maxRequestBytesnumberdefault: 1 MiB
Hard cap on actual request-body bytes.
streamTimeoutMsnumber
Optional wall-clock stream timeout; off by default.
upload.allowedMediaTypesstring[]
Exact server MIME allow-list.
upload.maxBytesnumberdefault: 5 MiB
Per-file server limit.

Cross-origin embeds

Same-origin applications need no CORS configuration. A widget calling a handler on another origin needs an explicit cors policy, and credentialed requests also need requestCredentials set on the client.

code
export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
  getUserId: getChatUserId,
  model,
  store,
  cors: {
    allowOrigins: ['https://docs.example.com'],
    allowCredentials: true,
  },
});
code
<ChatWidget
  apiBase="https://app.example.com/api/chat"
  requestCredentials="include"
/>

Use credentials only when the verified session actually depends on a cross-origin cookie. Never reflect arbitrary origins with credentials.

BuiltTools

code
interface BuiltTools {
  tools: ToolSet;
  cleanup?: () => void | Promise<void>;
}

Cleanup runs exactly once on success, stream error, or abort. Use it for MCP clients and other request-scoped resources.