Skip to content
+

Chat - Events & Callbacks

Respond to streaming lifecycle events, tool calls, data chunks, and errors using callback props on ChatProvider and ChatBox.

ChatProvider (and by extension ChatBox) exposes four callback props that fire at key moments in the chat lifecycle. Use them for logging, analytics, side effects, and error handling without modifying the adapter.

Callback overview

Prop When it fires Typical use case
onFinish When a stream reaches a terminal state Analytics, persistence, follow-up UI
onToolCall When a tool invocation state changes Logging, triggering external workflows
onData When a data-* chunk arrives during streaming Transient data, app-level side effects
onError When any runtime error surfaces Error reporting, toast notifications

onFinish

Fires when a stream finishes, aborts, disconnects, or errors. This is the primary callback for post-stream side effects.

interface ChatOnFinishPayload {
  message: ChatMessage; // the assistant message
  messages: ChatMessage[]; // all messages after the stream
  isAbort: boolean; // user stopped the stream
  isDisconnect: boolean; // stream disconnected unexpectedly
  isError: boolean; // stream ended with an error
  finishReason?: string; // backend-provided reason
}
<ChatBox
  adapter={adapter}
  onFinish={({ message, messages, isAbort }) => {
    if (!isAbort) {
      // Persist the completed conversation
      saveConversation(messages);
    }
    // Log completion analytics
    analytics.track('chat_response_complete', {
      messageId: message.id,
      partCount: message.parts.length,
    });
  }}
/>

Terminal states

The onFinish callback fires in four scenarios:

Scenario isAbort isDisconnect isError Description
Success false false false Stream completed normally
User abort true false false User clicked the stop button
Disconnect false true false Connection dropped mid-stream
Error false false true Stream ended with an error

onToolCall

Fires when a tool invocation state changes during streaming. Use it for side effects outside the message list — logging, analytics, or triggering external workflows.

interface ChatOnToolCallPayload {
  toolCall: ChatToolInvocation | ChatDynamicToolInvocation;
}
<ChatBox
  adapter={adapter}
  onToolCall={({ toolCall }) => {
    console.log(`Tool ${toolCall.toolName}: ${toolCall.state}`);

    if (toolCall.state === 'output-available') {
      // Tool execution completed — trigger follow-up
      analytics.track('tool_executed', { tool: toolCall.toolName });
    }
  }}
/>

Tool invocation states

State Description
input-streaming Tool input is being streamed
input-available Tool input is fully available
approval-requested Waiting for human-in-the-loop approval
approval-responded User has approved or denied
output-available Tool execution completed with output
output-error Tool execution failed
output-denied User denied the tool execution

onData

Fires when a data-* chunk arrives during streaming. Use it for transient data that should trigger app-level side effects without being persisted in the message.

type ChatOnData = (part: ChatDataMessagePart) => void;
<ChatBox
  adapter={adapter}
  onData={(part) => {
    if (part.type === 'progress') {
      setProgressPercent(part.data.percent);
    }
  }}
/>

This callback is useful for backend-driven UI updates that are transient — progress bars, status indicators, or notifications that should not be stored as message parts.

onError

Fires when any runtime error surfaces — from adapter methods, stream processing, or rendering.

type ChatOnError = (error: ChatError) => void;
<ChatBox
  adapter={adapter}
  onError={(error) => {
    console.error('[Chat error]', error.source, error.message);

    // Show a toast notification
    toast.error(error.message);

    // Report to error tracking
    Sentry.captureException(new Error(error.message), {
      tags: { source: error.source },
    });
  }}
/>

The ChatError type

type ChatErrorCode =
  | 'HISTORY_ERROR'
  | 'SEND_ERROR'
  | 'STREAM_ERROR'
  | 'REALTIME_ERROR';

interface ChatError {
  code: ChatErrorCode; // machine-readable error code
  message: string; // human-readable description
  source: ChatErrorSource; // where the error originated
  recoverable: boolean; // whether the runtime can continue
  retryable?: boolean; // whether the failed operation can be retried
  details?: Record<string, unknown>; // additional context
}

type ChatErrorSource = 'send' | 'stream' | 'history' | 'render' | 'adapter';

Error sources

Source When it fires
send sendMessage() rejected
stream Stream processing encountered an error
history listMessages() or listConversations() rejected
render A rendering error occurred in the message list
adapter A generic adapter method threw

Reading errors in components

Errors also surface through hooks, so you can display them in custom UI:

// Via useChat()
const { error } = useChat();

// Via useChatStatus() — lighter weight, no message subscriptions
const { error } = useChatStatus();

Registration

All callbacks are registered as props on ChatBox or ChatProvider:

// On ChatBox (all-in-one)
<ChatBox
  adapter={adapter}
  onFinish={handleFinish}
  onToolCall={handleToolCall}
  onData={handleData}
  onError={handleError}
/>

// On ChatProvider (custom layout)
<ChatProvider
  adapter={adapter}
  onFinish={handleFinish}
  onToolCall={handleToolCall}
  onData={handleData}
  onError={handleError}
>
  <MyCustomLayout />
</ChatProvider>

See also

  • Adapters for the adapter interface that produces these events.
  • Controlled State for the full ChatProvider props reference.
  • Hooks Reference for useChatStatus() and reading error state in components.

API

See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.