Skip to content

Commit 4cdc911

Browse files
committed
Add clientData support
1 parent 56603be commit 4cdc911

File tree

10 files changed

+356
-74
lines changed

10 files changed

+356
-74
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
paths:
3+
- "**/package.json"
4+
---
5+
6+
# Installing Packages
7+
8+
When adding a new dependency to any package.json in the monorepo:
9+
10+
1. **Look up the latest version** on npm before adding:
11+
```bash
12+
pnpm view <package-name> version
13+
```
14+
If unsure which version to use (e.g. major version compatibility), confirm with the user.
15+
16+
2. **Edit the package.json directly** — do NOT use `pnpm add` as it can cause issues in the monorepo. Add the dependency with the correct version range (typically `^x.y.z`).
17+
18+
3. **Run `pnpm i` from the repo root** after editing to install and update the lockfile:
19+
```bash
20+
pnpm i
21+
```
22+
Always run from the repo root, not from the package directory.

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This file provides guidance to Claude Code when working with this repository. Su
66

77
This is a pnpm 10.23.0 monorepo using Turborepo. Run commands from root with `pnpm run`.
88

9+
**Adding dependencies:** Edit `package.json` directly instead of using `pnpm add`, then run `pnpm i` from the repo root. See `.claude/rules/package-installation.md` for the full process.
10+
911
```bash
1012
pnpm run docker # Start Docker services (PostgreSQL, Redis, Electric)
1113
pnpm run db:migrate # Run database migrations

packages/trigger-sdk/src/v3/ai.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,23 @@ export const CHAT_STREAM_KEY = _CHAT_STREAM_KEY;
167167
export { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID };
168168

169169
/**
170-
* The payload shape that the chat transport sends to the triggered task.
170+
* The wire payload shape sent by `TriggerChatTransport`.
171+
* Uses `metadata` to match the AI SDK's `ChatRequestOptions` field name.
172+
* @internal
173+
*/
174+
type ChatTaskWirePayload<TMessage extends UIMessage = UIMessage> = {
175+
messages: TMessage[];
176+
chatId: string;
177+
trigger: "submit-message" | "regenerate-message";
178+
messageId?: string;
179+
metadata?: unknown;
180+
};
181+
182+
/**
183+
* The payload shape passed to the `chatTask` run function.
171184
*
172-
* When using `chatTask()`, the payload is automatically typed — you don't need
173-
* to import this type. Use this type only if you're using `task()` directly
174-
* with `pipeChat()`.
185+
* The `metadata` field from the AI SDK transport is exposed as `clientData`
186+
* to avoid confusion with Trigger.dev's run metadata.
175187
*/
176188
export type ChatTaskPayload<TMessage extends UIMessage = UIMessage> = {
177189
/** The conversation messages */
@@ -190,8 +202,8 @@ export type ChatTaskPayload<TMessage extends UIMessage = UIMessage> = {
190202
/** The ID of the message to regenerate (only for `"regenerate-message"`) */
191203
messageId?: string;
192204

193-
/** Custom metadata from the frontend */
194-
metadata?: unknown;
205+
/** Custom data from the frontend (passed via `metadata` on `sendMessage()` or the transport). */
206+
clientData?: unknown;
195207
};
196208

197209
/**
@@ -213,7 +225,7 @@ export type ChatTaskSignals = {
213225
export type ChatTaskRunPayload = ChatTaskPayload & ChatTaskSignals;
214226

215227
// Input streams for bidirectional chat communication
216-
const messagesInput = streams.input<ChatTaskPayload>({ id: CHAT_MESSAGES_STREAM_ID });
228+
const messagesInput = streams.input<ChatTaskWirePayload>({ id: CHAT_MESSAGES_STREAM_ID });
217229
const stopInput = streams.input<{ stop: true; message?: string }>({ id: CHAT_STOP_STREAM_ID });
218230

219231
/**
@@ -449,7 +461,7 @@ export type ChatTaskOptions<TIdentifier extends string> = Omit<
449461
*/
450462
function chatTask<TIdentifier extends string>(
451463
options: ChatTaskOptions<TIdentifier>
452-
): Task<TIdentifier, ChatTaskPayload, unknown> {
464+
): Task<TIdentifier, ChatTaskWirePayload, unknown> {
453465
const {
454466
run: userRun,
455467
maxTurns = 100,
@@ -458,10 +470,10 @@ function chatTask<TIdentifier extends string>(
458470
...restOptions
459471
} = options;
460472

461-
return createTask<TIdentifier, ChatTaskPayload, unknown>({
473+
return createTask<TIdentifier, ChatTaskWirePayload, unknown>({
462474
...restOptions,
463-
run: async (payload: ChatTaskPayload, { signal: runSignal }) => {
464-
let currentPayload = payload;
475+
run: async (payload: ChatTaskWirePayload, { signal: runSignal }) => {
476+
let currentWirePayload = payload;
465477

466478
// Mutable reference to the current turn's stop controller so the
467479
// stop input stream listener (registered once) can abort the right turn.
@@ -486,15 +498,19 @@ function chatTask<TIdentifier extends string>(
486498
const combinedSignal = AbortSignal.any([runSignal, stopController.signal]);
487499

488500
// Buffer messages that arrive during streaming
489-
const pendingMessages: ChatTaskPayload[] = [];
501+
const pendingMessages: ChatTaskWirePayload[] = [];
490502
const msgSub = messagesInput.on((msg) => {
491-
pendingMessages.push(msg as ChatTaskPayload);
503+
pendingMessages.push(msg);
492504
});
493505

506+
// Remap wire payload to user-facing payload (metadata -> clientData)
507+
const { metadata: wireMetadata, ...restWire } = currentWirePayload;
508+
494509
try {
495510
const result = await userRun({
496-
...currentPayload,
497-
messages: sanitizeMessages(currentPayload.messages),
511+
...restWire,
512+
clientData: wireMetadata,
513+
messages: sanitizeMessages(currentWirePayload.messages),
498514
signal: combinedSignal,
499515
cancelSignal,
500516
stopSignal,
@@ -526,7 +542,7 @@ function chatTask<TIdentifier extends string>(
526542

527543
// If messages arrived during streaming, use the first one immediately
528544
if (pendingMessages.length > 0) {
529-
currentPayload = pendingMessages[0]!;
545+
currentWirePayload = pendingMessages[0]!;
530546
continue;
531547
}
532548

@@ -539,7 +555,7 @@ function chatTask<TIdentifier extends string>(
539555

540556
if (warm.ok) {
541557
// Message arrived while warm — respond instantly
542-
currentPayload = warm.output;
558+
currentWirePayload = warm.output;
543559
continue;
544560
}
545561
}
@@ -552,7 +568,7 @@ function chatTask<TIdentifier extends string>(
552568
return;
553569
}
554570

555-
currentPayload = next.output;
571+
currentWirePayload = next.output;
556572
}
557573
} finally {
558574
stopSub.off();

packages/trigger-sdk/src/v3/chat.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ export type TriggerChatTransportOptions = {
8282
* @default 120
8383
*/
8484
streamTimeoutSeconds?: number;
85+
86+
/**
87+
* Default metadata included in every request payload.
88+
* Merged with per-call `metadata` from `sendMessage()` — per-call values
89+
* take precedence over transport-level defaults.
90+
*
91+
* Useful for data that should accompany every message, like a user ID.
92+
*
93+
* @example
94+
* ```ts
95+
* new TriggerChatTransport({
96+
* task: "my-chat",
97+
* accessToken,
98+
* metadata: { userId: currentUser.id },
99+
* });
100+
* ```
101+
*/
102+
metadata?: Record<string, unknown>;
85103
};
86104

87105
/**
@@ -132,6 +150,7 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
132150
private readonly streamKey: string;
133151
private readonly extraHeaders: Record<string, string>;
134152
private readonly streamTimeoutSeconds: number;
153+
private readonly defaultMetadata: Record<string, unknown> | undefined;
135154

136155
private sessions: Map<string, ChatSessionState> = new Map();
137156

@@ -145,6 +164,7 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
145164
this.streamKey = options.streamKey ?? DEFAULT_STREAM_KEY;
146165
this.extraHeaders = options.headers ?? {};
147166
this.streamTimeoutSeconds = options.streamTimeoutSeconds ?? DEFAULT_STREAM_TIMEOUT_SECONDS;
167+
this.defaultMetadata = options.metadata;
148168
}
149169

150170
sendMessages = async (
@@ -158,13 +178,18 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
158178
): Promise<ReadableStream<UIMessageChunk>> => {
159179
const { trigger, chatId, messageId, messages, abortSignal, body, metadata } = options;
160180

181+
const mergedMetadata =
182+
this.defaultMetadata || metadata
183+
? { ...(this.defaultMetadata ?? {}), ...((metadata as Record<string, unknown>) ?? {}) }
184+
: undefined;
185+
161186
const payload = {
162187
...(body ?? {}),
163188
messages,
164189
chatId,
165190
trigger,
166191
messageId,
167-
metadata,
192+
metadata: mergedMetadata,
168193
};
169194

170195
const session = this.sessions.get(chatId);

0 commit comments

Comments
 (0)