Skip to content

Commit 8d5d466

Browse files
committed
fix(chat): merge an action's metadata over the transport's clientData
sendAction with per-action metadata replaced the transport defaults instead of merging them, so required default fields vanished for direct callers. sendMessages already merged before routing; sendAction now merges itself, and its docstring describes the chat.turn() model.
1 parent 0cf59a2 commit 8d5d466

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,35 @@ describe("TriggerChatTransport", () => {
10211021
expect(actionBody.payload.metadata).toEqual({ tenant: "t-1" });
10221022
});
10231023

1024+
it("merges per-action metadata over the transport's clientData", async () => {
1025+
let actionBody: any;
1026+
global.fetch = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
1027+
const urlStr = typeof url === "string" ? url : url.toString();
1028+
if (isSessionStreamAppendUrl(urlStr)) {
1029+
actionBody = JSON.parse(init!.body as string);
1030+
return defaultAppendResponse();
1031+
}
1032+
if (isSessionOutSubscribeUrl(urlStr)) return defaultSseResponse();
1033+
throw new Error(`Unexpected URL: ${urlStr}`);
1034+
});
1035+
1036+
const transport = new TriggerChatTransport({
1037+
task: "my-chat-task",
1038+
accessToken: () => "pat",
1039+
sessions: { "chat-act-meta": { publicAccessToken: "p" } },
1040+
clientData: { userId: "u1", scope: "default" } as Record<string, unknown>,
1041+
});
1042+
1043+
const stream = await transport.sendAction(
1044+
"chat-act-meta",
1045+
{ type: "undo" },
1046+
{ metadata: { scope: "action" } }
1047+
);
1048+
await drainChunks(stream);
1049+
1050+
expect(actionBody.payload.metadata).toEqual({ userId: "u1", scope: "action" });
1051+
});
1052+
10241053
it("marks the session streaming and notifies before subscribing", async () => {
10251054
global.fetch = vi.fn().mockImplementation(async (url: string | URL) => {
10261055
const urlStr = typeof url === "string" ? url : url.toString();

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,11 +1260,11 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
12601260

12611261
/**
12621262
* Send a custom action chunk (for `chat.agent`'s `actionSchema` /
1263-
* `onAction` hook). Actions are not turns — only `hydrateMessages`
1264-
* and `onAction` fire on the agent side. The returned stream
1265-
* carries any model response `onAction` produced (when it returns a
1266-
* `StreamTextResult`); for `void`-returning side-effect-only actions
1267-
* the stream completes immediately with `trigger:turn-complete`.
1263+
* `onAction` hook). An action is an edit: only `hydrateMessages` and
1264+
* `onAction` fire on the agent side, and the returned stream completes
1265+
* with `trigger:turn-complete` once the edit is persisted. When `onAction`
1266+
* returns `chat.turn()` the turn's response follows on the same stream.
1267+
* Per-action `metadata` is merged over the transport's `clientData`.
12681268
*/
12691269
sendAction = async (
12701270
chatId: string,
@@ -1284,7 +1284,10 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
12841284
chatId,
12851285
trigger: "action" as const,
12861286
action,
1287-
metadata: options?.metadata ?? this.defaultMetadata ?? undefined,
1287+
metadata:
1288+
this.defaultMetadata || options?.metadata
1289+
? { ...(this.defaultMetadata ?? {}), ...(options?.metadata ?? {}) }
1290+
: undefined,
12881291
};
12891292

12901293
const body = this.serializeInputChunk({ kind: "message", payload: wirePayload });

0 commit comments

Comments
 (0)