Streaming
How the handler streams, bounds tool loops, maps errors, and exposes post-turn telemetry.
createChatHandler streams the model response through the Vercel AI SDK UI
message protocol. The widget currently commits partial updates at a fixed 50 ms
throttle.
Bound tool loops
import { stepCountIs } from 'ai';
createChatHandler({
getUserId: getChatUserId,
model,
store,
stopWhen: stepCountIs(5),
});
stopWhen accepts an AI SDK stop condition. The default is bounded; use
stepCountIs(1) to prevent chaining.
Error mapping
createChatHandler({
getUserId: getChatUserId,
model,
store,
onError: (error) => isAbortError(error)
? 'Response stopped.'
: 'Something went wrong. Please try again.',
});
onError controls user-facing text. Server error logging remains enabled unless
logErrors: false is set. Disable it only when another sink captures the same
failures.
Post-turn telemetry
onChatFinish runs after persistence. It can inspect AI SDK usage and provider
metadata; its own failures are logged and swallowed.
createChatHandler({
getUserId: getChatUserId,
model,
store,
onChatFinish: async ({ ctx, usage, providerMetadata }) => {
await analytics.track({
userId: ctx.userId,
conversationId: ctx.conversationId,
usage,
providerMetadata,
});
},
});
Do not save the turn again in this hook. See Streaming reliability for proxy buffering, timeouts, and deployment probes.