Skip to content

feat(sdk): browser-side chat client + transport (3/5)#3544

Open
ericallam wants to merge 1 commit into
feature/chat-agent-runtimefrom
feature/chat-client-transport
Open

feat(sdk): browser-side chat client + transport (3/5)#3544
ericallam wants to merge 1 commit into
feature/chat-agent-runtimefrom
feature/chat-client-transport

Conversation

@ericallam
Copy link
Copy Markdown
Member

Layer 3 of 5 in the chat.agent stack split

The browser-facing half of chat.agent: a TriggerChatTransport that
plugs into Vercel's ai-sdk useChat, plus AgentChat for direct
programmatic use. Lets a Next.js or React app drive a chat.agent
task in trigger.dev cloud over SSE.

Targets feature/chat-agent-runtime — merge after L2

Components

  • TriggerChatTransport (packages/trigger-sdk/src/v3/chat.ts):
    Vercel ai-sdk Transport implementation. Delta-only wire sends, SSE
    reconnection with lastEventId resume, stop/abort cleanup, dynamic
    accessToken refresh, X-Peek-Settled fast-close.
  • AgentChat (chat-client.ts): direct programmatic chat with the
    same underlying transport.
  • useTriggerChatTransport (chat-react.ts): React hook for the
    ai-sdk Transport.
  • chat-tab-coordinator: cross-tab leader election for shared SSE.
  • auth.ts: token plumbing for the transport.
  • packages/core/src/v3/chat-client.ts: shared envelope/wire types
    used by both browser and server runtime.

Stack

  • L1 → main: Sessions primitive
  • L2 → L1: chat.agent runtime
  • L3 → L2 (this PR)
  • L4 → L3: agent-view dashboard
  • L5 → L4: ai-chat reference + MCP tooling

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 10, 2026

⚠️ No Changeset found

Latest commit: 172b7c3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 29b6c45c-102b-45c5-9aae-3870ed8624ea

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/chat-client-transport

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 and usage tips.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +598 to +599
trigger: "preload",
...(this.triggerConfigDefault?.basePayload ?? {}),
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.

🔴 Spread order lets user's basePayload silently override required trigger: "preload"

In AgentChat.ensureStarted, the required trigger: "preload" value is set before spreading this.triggerConfigDefault?.basePayload. Because later spread keys override earlier ones in JavaScript, if a user's triggerConfig.basePayload happens to contain a trigger key, it silently overrides "preload". The comment at packages/trigger-sdk/src/v3/chat-client.ts:592-596 explicitly states this value is required: "Without this, AgentChat's first run skips both preload and start hooks, which is where customer apps typically upsert their Chat row." Compare how chatId is correctly placed after the spread (line 600) so it can't be overridden — trigger should follow the same pattern.

Suggested change
trigger: "preload",
...(this.triggerConfigDefault?.basePayload ?? {}),
...(this.triggerConfigDefault?.basePayload ?? {}),
trigger: "preload",
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +407 to +412
setPendingMsgs((prev) => {
const msg = prev.find((m) => m.id === id);
if (!msg || msg._mode !== "queued") return prev;
transport.sendPendingMessage(chatId, msg, metadata);
return prev.map((m) => (m.id === id ? { ...m, _mode: "steering" as const } : m));
});
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.

🟡 Side effect inside setPendingMsgs state updater causes double message send in React strict mode

transport.sendPendingMessage(chatId, msg, metadata) is called inside the setPendingMsgs state updater function at line 410. React strict mode (development) double-invokes state updater functions to detect impurities — the first invocation's result is discarded, but the network side effect has already fired. Because the discarded first call sees the original prev state (with _mode: "queued"), and the second call also sees the same original prev, the message is sent to the backend twice.

Fix approach

Move the sendPendingMessage call outside the updater:

const msg = pendingMsgs.find((m) => m.id === id && m._mode === "queued");
if (!msg) return;
transport.sendPendingMessage(chatId, msg, metadata);
setPendingMsgs((prev) =>
  prev.map((m) => (m.id === id ? { ...m, _mode: "steering" as const } : m))
);
Prompt for agents
In usePendingMessages hook in packages/trigger-sdk/src/v3/chat-react.ts, the promoteToSteering callback calls transport.sendPendingMessage inside the setPendingMsgs state updater function (line 410). React strict mode double-invokes state updater functions for purity checks, so this network side effect fires twice in development. The fix is to extract the sendPendingMessage call out of the updater: read the current pendingMsgs from a ref or from the state snapshot before calling setPendingMsgs, send the message, then call setPendingMsgs with a pure updater that only changes _mode. Alternatively, use a separate useEffect triggered by a state change. The key constraint is that the updater function passed to setPendingMsgs must be side-effect-free.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

The browser-facing half of chat.agent: a TriggerChatTransport that
plugs into ai-sdk's useChat, plus AgentChat for direct programmatic
use. Together they let a Next.js or React app drive a chat.agent
task in trigger.dev cloud over SSE.

- TriggerChatTransport (packages/trigger-sdk/src/v3/chat.ts):
  Vercel ai-sdk Transport implementation. Delta-only wire sends, SSE
  reconnection with lastEventId resume, stop/abort cleanup, dynamic
  accessToken refresh, X-Peek-Settled fast-close.
- AgentChat (chat-client.ts): direct programmatic chat with the same
  underlying transport.
- useTriggerChatTransport (chat-react.ts): React hook for the
  ai-sdk Transport.
- chat-tab-coordinator: cross-tab leader election for shared SSE.
- auth.ts: token plumbing for the transport.
- packages/core/src/v3/chat-client.ts: shared envelope/wire types
  used by both browser and server runtime.
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from dbc0034 to 64929b7 Compare May 11, 2026 19:01
@ericallam ericallam force-pushed the feature/chat-client-transport branch from a60187c to 172b7c3 Compare May 11, 2026 19:01
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.

1 participant