Skip to content

feat(sdk): onEvent observability callback on the chat transport#4187

Open
ericallam wants to merge 8 commits into
mainfrom
feature/tri-11786-transport-observability-hook-onevent-with-typed-send-stream
Open

feat(sdk): onEvent observability callback on the chat transport#4187
ericallam wants to merge 8 commits into
mainfrom
feature/tri-11786-transport-observability-hook-onevent-with-typed-send-stream

Conversation

@ericallam

@ericallam ericallam commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

sendMessage from useChat gives no feedback about whether a message actually reached the backend, and the fetch override is wire-level: it requires knowing endpoint semantics, cannot attribute requests to messages, and misses the headStart first-turn POST entirely. This adds a typed onEvent observability callback to TriggerChatTransport / useTriggerChatTransport so send-success metrics, time-to-first-token, and "sent but never answered" watchdogs become a few lines of client code.

Example

const transport = useTriggerChatTransport({
  task: "my-chat",
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
  onEvent: (event) => {
    switch (event.type) {
      case "message-sent":
        // Durably acknowledged by the session's input stream, not just "request accepted".
        metrics.increment("chat.message_sent", { source: event.source });
        metrics.timing("chat.send_duration_ms", event.durationMs);
        break;
      case "message-send-failed":
        metrics.increment("chat.message_send_failed", { status: event.status });
        break;
      case "first-chunk":
        metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0);
        break;
      case "turn-completed":
        metrics.timing("chat.turn_duration_ms", event.sinceSendMs ?? 0);
        break;
    }
  },
});

Design

One callback, one discriminated union (ChatTransportEvent):

  • message-sent / message-send-failed: terminal send outcomes with messageId, a source discriminator (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-sent means the append was durably acknowledged, after any internal token-refresh retries.
  • stream-connected (with a resumed flag and the cursor it connected from), first-chunk (chunk type plus sinceSendMs for time-to-first-token), turn-completed (sinceSendMs full-turn latency and the agent's committed input cursor), and stream-error follow the response side, so a send can be paired with the answer that should follow it. messageId on 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 fetch override 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 records message_send_failed with status 413; and killing the worker after a durable send fires both sent_but_no_stream and sent_but_unanswered, reproducing and detecting the "message disappeared" failure mode that motivated this feature.

@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2434ee0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 28 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@internal/sdk-compat-tests Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/plugins Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/rbac Patch
@trigger.dev/sso Patch
trigger.dev Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch

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

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds an onEvent observability callback to TriggerChatTransport and useTriggerChatTransport. New exported types ChatTransportSendSource and ChatTransportEvent define lifecycle events including message-sent, message-send-failed, stream-connected, first-chunk, turn-completed, and stream-error. The transport adds setOnEvent, emitEvent, and sendWithEvents, and emits events from send, steer, stop, action, and streaming paths. The React hook syncs onEvent to the transport. Tests cover event emission and failure handling, and docs plus a changeset describe the API and monitoring patterns.

Changes

Cohort / File(s) Summary
Event types and optionspackages/trigger-sdk/src/v3/chat.ts Adds ChatTransportSendSource, ChatTransportEvent, onEvent, and callback storage
Send-path event emissionpackages/trigger-sdk/src/v3/chat.ts Adds event helpers and wraps send, handover, steer, stop, and action flows
Streaming lifecycle eventspackages/trigger-sdk/src/v3/chat.ts Emits stream-connected, first-chunk, turn-completed, and stream-error
React hook wiringpackages/trigger-sdk/src/v3/chat-react.ts Syncs onEvent and re-exports new types
Testspackages/trigger-sdk/test/chat-transport-events.test.ts Adds coverage for send and stream event emission
Docs & changeset.changeset/chat-transport-onevent.md, docs/ai-chat/frontend.mdx, docs/ai-chat/reference.mdx Documents the callback, event payloads, and watchdog example

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
Loading

Related PRs: None identified.

Suggested labels: area: sdk, documentation, enhancement

Suggested reviewers: none identified

🐰 A callback hops through streams so slow,
to whisper when a message lands or fails to go.
First-chunk, turn-complete, stream-connected too,
a watchdog ticks to catch what slips from view.
Carrots counted, metrics grow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description omits required template sections like Closes #issue, checklist, Testing, Changelog, and Screenshots. Reformat the PR description to match the template and add Closes #, checklist items, testing steps, changelog, and screenshots or mark them N/A.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main change: a new onEvent observability callback for the chat transport.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/tri-11786-transport-observability-hook-onevent-with-typed-send-stream

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@43d4a9b

trigger.dev

npm i https://pkg.pr.new/trigger.dev@43d4a9b

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@43d4a9b

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@43d4a9b

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@43d4a9b

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@43d4a9b

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@43d4a9b

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@43d4a9b

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@43d4a9b

commit: 43d4a9b

ericallam added 5 commits July 7, 2026 23:15
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.
@ericallam ericallam force-pushed the feature/tri-11786-transport-observability-hook-onevent-with-typed-send-stream branch from e898da8 to 55ec228 Compare July 7, 2026 22:16
coderabbitai[bot]

This comment was marked as resolved.

@ericallam ericallam marked this pull request as ready for review July 8, 2026 08:36
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread packages/trigger-sdk/src/v3/chat.ts
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants