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

@ericallam ericallam commented May 10, 2026

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 #3543

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: #3542 Sessions dashboard, task_kind, and chat-ready hardening
  • L2 → L1: #3543 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: a4bd26a

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: d4bcd9dc-ca29-493e-8a99-d8756903be52

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.

@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
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 64929b7 to 9d8b67b Compare May 12, 2026 08:23
@ericallam ericallam force-pushed the feature/chat-client-transport branch from 172b7c3 to a63e60a Compare May 12, 2026 08:23
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 9d8b67b to f83a0e2 Compare May 12, 2026 08:35
@ericallam ericallam force-pushed the feature/chat-client-transport branch from a63e60a to ce861fb Compare May 12, 2026 08:35
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from f83a0e2 to 4cedf7c Compare May 12, 2026 08:40
@ericallam ericallam force-pushed the feature/chat-client-transport branch from ce861fb to dc686d8 Compare May 12, 2026 08:40
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 4cedf7c to 63cb53e Compare May 12, 2026 08:46
@ericallam ericallam force-pushed the feature/chat-client-transport branch from dc686d8 to 6c3490c Compare May 12, 2026 08:46
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 63cb53e to b850847 Compare May 12, 2026 08:52
@ericallam ericallam force-pushed the feature/chat-client-transport branch from 6c3490c to 9d26e60 Compare May 12, 2026 08:52
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from b850847 to e570acb Compare May 12, 2026 09:44
@ericallam ericallam force-pushed the feature/chat-client-transport branch from 9d26e60 to b971910 Compare May 12, 2026 09:44
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from e570acb to dd7d928 Compare May 12, 2026 09:46
@ericallam ericallam force-pushed the feature/chat-client-transport branch from b971910 to fa95095 Compare May 12, 2026 09:46
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from dd7d928 to 46dd26a Compare May 12, 2026 09:48
@ericallam ericallam force-pushed the feature/chat-client-transport branch from fa95095 to dc95b98 Compare May 12, 2026 09:48
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 46dd26a to 4703774 Compare May 12, 2026 09:52
@ericallam ericallam force-pushed the feature/chat-client-transport branch from dc95b98 to abb2cab Compare May 12, 2026 09:52
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 4703774 to e7f6819 Compare May 12, 2026 10:02
@ericallam ericallam force-pushed the feature/chat-client-transport branch from abb2cab to 3233503 Compare May 12, 2026 10:02
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from e7f6819 to c3486fe Compare May 12, 2026 10:07
@ericallam ericallam force-pushed the feature/chat-client-transport branch from 3233503 to 3b68877 Compare May 12, 2026 10:07
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from c3486fe to 9d67168 Compare May 12, 2026 10:16
@ericallam ericallam force-pushed the feature/chat-client-transport branch from 3b68877 to cac4fc5 Compare May 12, 2026 10:16
@ericallam ericallam force-pushed the feature/chat-agent-runtime branch from 9d67168 to 914a08f Compare May 12, 2026 10:27
@ericallam ericallam force-pushed the feature/chat-client-transport branch from cac4fc5 to a4bd26a Compare May 12, 2026 10:27
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 914a08f to f694134 Compare May 12, 2026 10:31
@ericallam ericallam force-pushed the feature/chat-client-transport branch from a4bd26a to 76e3cec Compare May 12, 2026 10:31
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