You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(sdk): onEvent observability callback on the chat transport
Typed lifecycle events (message-sent, message-send-failed,
stream-connected, first-chunk, turn-completed, stream-error) emitted
from the transport's send and stream paths, including headStart and
steering sends the fetch override cannot see. Send events are terminal
and durable-ack semantics; callback exceptions are swallowed. The React
hook keeps the callback live across renders.
Add an `onEvent` observability callback to `TriggerChatTransport` / `useTriggerChatTransport` that emits typed lifecycle events: `message-sent` and `message-send-failed` (durable send outcomes with source and duration), `stream-connected`, `first-chunk`, `turn-completed`, and `stream-error`. Together these make send-success metrics, time-to-first-token, and "sent but never answered" watchdogs a few lines of client code.
Copy file name to clipboardExpand all lines: docs/ai-chat/frontend.mdx
+42Lines changed: 42 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -574,3 +574,45 @@ baseURL: ({ endpoint }) =>
574
574
```
575
575
576
576
For per-request control beyond URL routing (header injection, custom retries, tracing), pass a `fetch` override. See [Trusted edge signals](/ai-chat/patterns/trusted-edge-signals) for a full proxy walkthrough.
577
+
578
+
## Monitoring message delivery
579
+
580
+
`sendMessage` from `useChat` gives no feedback about whether the message actually reached the backend. The transport's `onEvent` callback closes that gap with typed lifecycle events (see the [event catalog](/ai-chat/reference#transport-events)) so you can record real metrics:
A `message-sent` event means the message is durably written to the session's input stream (the stream the agent consumes from), so it's a true "sent successfully" signal. Because send and response events share the same callback, "sent but never answered" becomes a small client-side watchdog:
if (event.type==="first-chunk"||event.type==="turn-completed"||event.type==="stream-error") {
612
+
clearTimeout(pending.get(event.chatId));
613
+
pending.delete(event.chatId);
614
+
}
615
+
};
616
+
```
617
+
618
+
Time to first token is the delta between a `message-sent` and the following `first-chunk` on the same chat. Exceptions thrown inside `onEvent` are swallowed, so a failing metrics pipeline can never break the chat.
Copy file name to clipboardExpand all lines: docs/ai-chat/reference.mdx
+18Lines changed: 18 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -630,10 +630,28 @@ Options for the frontend transport constructor and `useTriggerChatTransport` hoo
630
630
|`clientData`| Typed by `clientDataSchema`| — | Default client data merged into per-turn `metadata` and threaded through `startSession`'s params (so the first run's `payload.metadata` matches per-turn `metadata`). Live-updated when the option value changes. |
631
631
|`sessions`|`Record<string, ChatSession>`| — | Restore sessions from storage. See [ChatSession](#chatsession). |
632
632
|`onSessionChange`|`(chatId, session \| null) => void`| — | Fires when session state changes. `session` is the full `ChatSession` or `null` when the run ends. |
633
+
|`onEvent`|`(event: ChatTransportEvent) => void`| — | Observability callback for transport lifecycle events (send outcomes, stream connects, first chunk, turn completion). See [Transport events](#transport-events). Exceptions thrown by the callback are swallowed. |
633
634
|`multiTab`|`boolean`|`false`| Enable multi-tab claim coordination via `BroadcastChannel`. See [Frontend → multi-tab](/ai-chat/frontend#multi-tab-coordination). |
634
635
|`watch`|`boolean`|`false`| Read-only watcher mode — keep the SSE subscription open across `trigger:turn-complete` so a viewer sees turns 2, 3, … through one long-lived stream. |
635
636
|`headStart`|`string`| — | URL of a [`chat.headStart`](/ai-chat/fast-starts#head-start) route handler. When set, the FIRST message of a brand-new chat POSTs to this URL so step 1's LLM call runs in your warm process while the agent run boots in parallel. Subsequent turns bypass it. |
636
637
638
+
### Transport events
639
+
640
+
The `onEvent` callback receives a `ChatTransportEvent` (exported from `@trigger.dev/sdk/chat` and `@trigger.dev/sdk/chat/react`) for each lifecycle moment the transport observes. All events carry `chatId` and `timestamp`.
641
+
642
+
| Event | Extra fields | Fires when |
643
+
| --- | --- | --- |
644
+
|`message-sent`|`messageId?`, `source`, `durationMs`| A send was durably acknowledged — a 2xx from the session input stream append (or the `headStart` POST), after any internal token-refresh retries. This means the message is durably written to the stream the agent consumes from, not merely "request accepted". |
645
+
|`message-send-failed`|`messageId?`, `source`, `error`, `status?`, `durationMs`| A send definitively failed after internal retries. Fires in addition to `useChat`'s `onError`. |
646
+
|`stream-connected`|`resumed`| The SSE subscription to the session's output stream started delivering. `resumed: true` when reconnecting from a stored cursor (page reload) rather than following a fresh send. |
647
+
|`first-chunk`| — | The first response chunk of a turn arrived. Pair with `message-sent` for time-to-first-token. |
648
+
|`turn-completed`|`lastEventId?`| The agent's turn-complete control record arrived — the "finished answering" signal. |
649
+
|`stream-error`|`error`| The output stream failed unrecoverably. |
650
+
651
+
`source` identifies the send path: `"submit-message"`, `"regenerate-message"`, `"steer"` (`sendPendingMessage`), `"action"` (`sendAction`), `"stop"` (`stopGeneration`), or `"head-start"`.
652
+
653
+
See [Monitoring message delivery](/ai-chat/frontend#monitoring-message-delivery) for the metrics and watchdog patterns these enable.
654
+
637
655
### `accessToken` callback
638
656
639
657
The transport invokes `accessToken` whenever it needs a *fresh* session-scoped PAT — initial use after no PAT is cached, or after a 401/403 from any session-PAT-authed request. The callback's job is to **return a token, not to start a run.**
0 commit comments