feat(sdk): onEvent observability callback on the chat transport#4187
feat(sdk): onEvent observability callback on the chat transport#4187ericallam wants to merge 8 commits into
Conversation
🦋 Changeset detectedLatest commit: 2434ee0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 28 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds an Changes
Sequence Diagram(s)sequenceDiagram
participant TriggerChatTransport
participant sendWithEvents
participant SSEStream
participant onEvent
TriggerChatTransport->>sendWithEvents: sendMessages / steer / stop / action
sendWithEvents->>onEvent: emit message-sent or message-send-failed
TriggerChatTransport->>SSEStream: subscribeToSessionStream(resumed)
SSEStream-->>onEvent: emit stream-connected
SSEStream-->>onEvent: emit first-chunk
SSEStream-->>onEvent: emit turn-completed
SSEStream-->>onEvent: emit stream-error
Related PRs: None identified. Suggested labels: area: sdk, documentation, enhancement Suggested reviewers: none identified 🐰 A callback hops through streams so slow, 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@trigger.dev/build
trigger.dev
@trigger.dev/core
@trigger.dev/python
@trigger.dev/react-hooks
@trigger.dev/redis-worker
@trigger.dev/rsc
@trigger.dev/schema-to-json
@trigger.dev/sdk
commit: |
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.
Send events gain partId (the append idempotency key, also on the server-side record) and bodyBytes. Response events gain client-side attribution: messageId of the last turn-producing send plus sinceSendMs, so time-to-first-token and full-turn latency need no consumer bookkeeping. first-chunk carries chunkType and its record id, turn-completed carries the agent's committed input cursor, and stream-error carries the HTTP status when one exists.
e898da8 to
55ec228
Compare
The handover response stream now emits stream-connected, first-chunk, and turn-completed like the session subscribe path, so headStart turns get time-to-first-token and turn latency instead of an event gap on the exact path that exists to improve first-turn TTFT.
Summary
sendMessagefromuseChatgives no feedback about whether a message actually reached the backend, and thefetchoverride is wire-level: it requires knowing endpoint semantics, cannot attribute requests to messages, and misses the headStart first-turn POST entirely. This adds a typedonEventobservability callback toTriggerChatTransport/useTriggerChatTransportso send-success metrics, time-to-first-token, and "sent but never answered" watchdogs become a few lines of client code.Example
Design
One callback, one discriminated union (
ChatTransportEvent):message-sent/message-send-failed: terminal send outcomes withmessageId, asourcediscriminator (submit, regenerate, steer, action, stop, head-start),durationMs,bodyBytes, the append's idempotency key (partId, also stored on the server-side record), and error + HTTP status on failure.message-sentmeans the append was durably acknowledged, after any internal token-refresh retries.stream-connected(with aresumedflag and the cursor it connected from),first-chunk(chunk type plussinceSendMsfor time-to-first-token),turn-completed(sinceSendMsfull-turn latency and the agent's committed input cursor), andstream-errorfollow the response side, so a send can be paired with the answer that should follow it.messageIdon response events is client-side attribution from the last turn-producing send on that chat.Emissions sit at the transport's existing choke points, covering every send path uniformly (including steering and headStart, which the fetch override cannot observe). Exceptions thrown by the callback are swallowed: observability can never break the chat. The React hook keeps the callback live across renders instead of freezing the first-render closure.
Verification
Unit tests drive the transport directly with the
fetchoverride as the network stub (send success/failure per source, stream lifecycle, resumed flag, field enrichment, callback exceptions swallowed). Verified end-to-end against a realistic metrics setup in the ai-chat reference app (counters, send-duration and TTFT histograms, and both watchdogs built purely on these events): a healthy two-turn chat produces exactly the expected event sequence and TTFT values; an oversized append recordsmessage_send_failedwith status 413; and killing the worker after a durable send fires bothsent_but_no_streamandsent_but_unanswered, reproducing and detecting the "message disappeared" failure mode that motivated this feature.