@trigger.dev/sdk 0.0.0-chat-prerelease-20260414141814 → 0.0.0-chat-prerelease-20260414181032

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2104,6 +2104,14 @@ export declare const chat: {
2104
2104
  inject: typeof injectBackgroundContext;
2105
2105
  /** Typed chat output stream for writing custom chunks or piping from subtasks. */
2106
2106
  stream: import("@trigger.dev/core/v3").RealtimeDefinedStream<UIMessageChunk>;
2107
+ /** Write data parts that persist to the response message. See {@link chatResponse}. */
2108
+ response: {
2109
+ /**
2110
+ * Write a single chunk. Non-transient data parts are accumulated into the
2111
+ * response message; everything else is stream-only.
2112
+ */
2113
+ write(part: UIMessageChunk): void;
2114
+ };
2107
2115
  /** Pre-built input stream for receiving messages from the transport. */
2108
2116
  messages: import("@trigger.dev/core/v3").RealtimeDefinedInputStream<ChatTaskWirePayload<UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>, unknown>>;
2109
2117
  /** Create a managed stop signal wired to the stop input stream. See {@link createStopSignal}. */
@@ -243,6 +243,43 @@ exports.CHAT_STREAM_KEY = chat_constants_js_1.CHAT_STREAM_KEY;
243
243
  * ```
244
244
  */
245
245
  const chatStream = streams_js_1.streams.define({ id: chat_constants_js_1.CHAT_STREAM_KEY });
246
+ // ---------------------------------------------------------------------------
247
+ // chat.response — write data parts that persist to the response message
248
+ // ---------------------------------------------------------------------------
249
+ /**
250
+ * Write data parts that both stream to the frontend AND persist in
251
+ * `onTurnComplete`'s `responseMessage` and `uiMessages`.
252
+ *
253
+ * Non-transient data chunks (`type` starts with `data-`, no `transient: true`)
254
+ * are queued for accumulation into the assistant response message.
255
+ * Transient or non-data chunks are streamed only (same as `chat.stream`).
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * // Persists to responseMessage.parts
260
+ * chat.response.write({ type: "data-handover", data: { context: summary } });
261
+ *
262
+ * // Transient — streams only, not in responseMessage
263
+ * chat.response.write({ type: "data-progress", data: { percent: 50 }, transient: true });
264
+ * ```
265
+ */
266
+ const chatResponse = {
267
+ /**
268
+ * Write a single chunk. Non-transient data parts are accumulated into the
269
+ * response message; everything else is stream-only.
270
+ */
271
+ write(part) {
272
+ queueResponsePart(part);
273
+ const { waitUntilComplete } = streams_js_1.streams.writer(exports.CHAT_STREAM_KEY, {
274
+ spanName: "chat.response.write",
275
+ collapsed: true,
276
+ execute: ({ write }) => {
277
+ write(part);
278
+ },
279
+ });
280
+ waitUntilComplete().catch(() => { });
281
+ },
282
+ };
246
283
  /**
247
284
  * Creates a lazy ChatWriter that only opens a realtime stream on first use.
248
285
  * Call `flush()` after the callback returns to await stream completion.
@@ -274,6 +311,7 @@ function createLazyChatWriter() {
274
311
  writer: {
275
312
  write(part) {
276
313
  ensureInitialized();
314
+ queueResponsePart(part);
277
315
  writeImpl(part);
278
316
  },
279
317
  merge(stream) {
@@ -400,6 +438,30 @@ const chatPendingMessagesKey = locals_js_1.locals.create("chat.pendingMessages")
400
438
  const chatSteeringQueueKey = locals_js_1.locals.create("chat.steeringQueue");
401
439
  /** @internal — IDs of messages that were successfully injected via prepareStep */
402
440
  const chatInjectedMessageIdsKey = locals_js_1.locals.create("chat.injectedMessageIds");
441
+ /** @internal — non-transient data parts queued via chat.response or writer.write() for accumulation into the response message */
442
+ const chatResponsePartsKey = locals_js_1.locals.create("chat.responseParts");
443
+ /**
444
+ * Check if a chunk is a non-transient data part that should persist to the response message.
445
+ * @internal
446
+ */
447
+ function isNonTransientDataPart(part) {
448
+ if (typeof part !== "object" || part === null)
449
+ return false;
450
+ const p = part;
451
+ return typeof p.type === "string" && p.type.startsWith("data-") && p.transient !== true;
452
+ }
453
+ /**
454
+ * Queue a chunk for accumulation into the response message (if it's a non-transient data part).
455
+ * Called by `chat.response.write()` and `ChatWriter.write()`.
456
+ * @internal
457
+ */
458
+ function queueResponsePart(part) {
459
+ if (!isNonTransientDataPart(part))
460
+ return;
461
+ const parts = locals_js_1.locals.get(chatResponsePartsKey) ?? [];
462
+ parts.push(part);
463
+ locals_js_1.locals.set(chatResponsePartsKey, parts);
464
+ }
403
465
  /**
404
466
  * Check that no tool calls are in-flight in a step's content.
405
467
  * Used before compaction to avoid losing tool state mid-execution.
@@ -536,6 +598,7 @@ async function chatCompact(messages, steps, options) {
536
598
  type: "data-compaction",
537
599
  id: compactionId,
538
600
  data: { status: "compacting", totalTokens },
601
+ transient: true,
539
602
  });
540
603
  // Generate summary
541
604
  summary = await options.summarize(messages);
@@ -576,6 +639,7 @@ async function chatCompact(messages, steps, options) {
576
639
  type: "data-compaction",
577
640
  id: compactionId,
578
641
  data: { status: "complete", totalTokens },
642
+ transient: true,
579
643
  });
580
644
  write({ type: "finish-step" });
581
645
  },
@@ -1174,6 +1238,7 @@ function chatAgent(options) {
1174
1238
  locals_js_1.locals.set(chatDeferKey, new Set());
1175
1239
  locals_js_1.locals.set(chatCompactionStateKey, undefined);
1176
1240
  locals_js_1.locals.set(chatSteeringQueueKey, []);
1241
+ locals_js_1.locals.set(chatResponsePartsKey, []);
1177
1242
  // NOTE: chatBackgroundQueueKey is NOT reset here — messages injected
1178
1243
  // by deferred work from the previous turn's onTurnComplete need to
1179
1244
  // survive into the next turn. The queue is drained before run().
@@ -1592,6 +1657,15 @@ function chatAgent(options) {
1592
1657
  if (!capturedResponseMessage.id) {
1593
1658
  capturedResponseMessage = { ...capturedResponseMessage, id: (0, ai_1.generateId)() };
1594
1659
  }
1660
+ // Append any non-transient data parts queued via chat.response or writer.write()
1661
+ const queuedParts = locals_js_1.locals.get(chatResponsePartsKey);
1662
+ if (queuedParts && queuedParts.length > 0) {
1663
+ capturedResponseMessage = {
1664
+ ...capturedResponseMessage,
1665
+ parts: [...capturedResponseMessage.parts, ...queuedParts],
1666
+ };
1667
+ locals_js_1.locals.set(chatResponsePartsKey, []);
1668
+ }
1595
1669
  accumulatedUIMessages.push(capturedResponseMessage);
1596
1670
  turnNewUIMessages.push(capturedResponseMessage);
1597
1671
  try {
@@ -1605,10 +1679,21 @@ function chatAgent(options) {
1605
1679
  // Conversion failed — skip accumulation for this turn
1606
1680
  }
1607
1681
  }
1608
- // TODO: When the user calls `pipeChat` manually instead of returning a
1609
- // StreamTextResult, we don't have access to onFinish. A future iteration
1610
- // should let manual-mode users report back response messages for
1611
- // accumulation (e.g. via a `chat.addMessages()` helper).
1682
+ // If there's no captured response (manual pipe mode) but there are
1683
+ // queued data parts, create a minimal response message to hold them.
1684
+ if (!capturedResponseMessage) {
1685
+ const remainingParts = locals_js_1.locals.get(chatResponsePartsKey);
1686
+ if (remainingParts && remainingParts.length > 0) {
1687
+ capturedResponseMessage = {
1688
+ id: (0, ai_1.generateId)(),
1689
+ role: "assistant",
1690
+ parts: [...remainingParts],
1691
+ };
1692
+ locals_js_1.locals.set(chatResponsePartsKey, []);
1693
+ accumulatedUIMessages.push(capturedResponseMessage);
1694
+ turnNewUIMessages.push(capturedResponseMessage);
1695
+ }
1696
+ }
1612
1697
  if (runSignal.aborted)
1613
1698
  return "exit";
1614
1699
  // Await deferred background work (e.g. DB writes from onTurnStart)
@@ -1650,6 +1735,7 @@ function chatAgent(options) {
1650
1735
  type: "data-compaction",
1651
1736
  id: compactionId,
1652
1737
  data: { status: "compacting", totalTokens: turnUsage.totalTokens },
1738
+ transient: true,
1653
1739
  });
1654
1740
  const summary = await outerCompaction.summarize({
1655
1741
  messages: accumulatedMessages,
@@ -1711,6 +1797,7 @@ function chatAgent(options) {
1711
1797
  type: "data-compaction",
1712
1798
  id: compactionId,
1713
1799
  data: { status: "complete", totalTokens: turnUsage.totalTokens },
1800
+ transient: true,
1714
1801
  });
1715
1802
  },
1716
1803
  });
@@ -1784,6 +1871,22 @@ function chatAgent(options) {
1784
1871
  },
1785
1872
  });
1786
1873
  }
1874
+ // Drain any late response parts added during onBeforeTurnComplete
1875
+ const lateParts = locals_js_1.locals.get(chatResponsePartsKey);
1876
+ if (lateParts && lateParts.length > 0 && capturedResponseMessage) {
1877
+ const idx = accumulatedUIMessages.findIndex((m) => m.id === capturedResponseMessage.id);
1878
+ if (idx !== -1) {
1879
+ const msg = accumulatedUIMessages[idx];
1880
+ accumulatedUIMessages[idx] = {
1881
+ ...msg,
1882
+ parts: [...(msg.parts ?? []), ...lateParts],
1883
+ };
1884
+ capturedResponseMessage = accumulatedUIMessages[idx];
1885
+ turnCompleteEvent.responseMessage = capturedResponseMessage;
1886
+ turnCompleteEvent.uiMessages = accumulatedUIMessages;
1887
+ }
1888
+ locals_js_1.locals.set(chatResponsePartsKey, []);
1889
+ }
1787
1890
  // Write turn-complete control chunk — closes the frontend stream.
1788
1891
  const turnCompleteResult = await writeTurnCompleteChunk(currentWirePayload.chatId, turnAccessToken);
1789
1892
  // Fire onTurnComplete — stream is closed, use for persistence.
@@ -2791,6 +2894,8 @@ function createChatSession(payload, options) {
2791
2894
  }
2792
2895
  // Reset stop signal for this turn
2793
2896
  stop.reset();
2897
+ // Reset per-turn state
2898
+ locals_js_1.locals.set(chatResponsePartsKey, []);
2794
2899
  // Set up steering queue and pending messages config in locals
2795
2900
  // so toStreamTextOptions() auto-injects prepareStep for steering
2796
2901
  const turnSteeringQueue = [];
@@ -2887,8 +2992,26 @@ function createChatSession(payload, options) {
2887
2992
  const cleaned = stop.signal.aborted && !runSignal.aborted
2888
2993
  ? cleanupAbortedParts(response)
2889
2994
  : response;
2995
+ // Append any non-transient data parts queued via chat.response or writer.write()
2996
+ const queuedParts = locals_js_1.locals.get(chatResponsePartsKey);
2997
+ if (queuedParts && queuedParts.length > 0) {
2998
+ cleaned.parts = [...(cleaned.parts ?? []), ...queuedParts];
2999
+ locals_js_1.locals.set(chatResponsePartsKey, []);
3000
+ }
2890
3001
  await accumulator.addResponse(cleaned);
2891
3002
  }
3003
+ else {
3004
+ // No response (manual pipe mode) but there are queued data parts
3005
+ const queuedParts = locals_js_1.locals.get(chatResponsePartsKey);
3006
+ if (queuedParts && queuedParts.length > 0) {
3007
+ await accumulator.addResponse({
3008
+ id: (0, ai_1.generateId)(),
3009
+ role: "assistant",
3010
+ parts: queuedParts,
3011
+ });
3012
+ locals_js_1.locals.set(chatResponsePartsKey, []);
3013
+ }
3014
+ }
2892
3015
  // Capture token usage from the streamText result
2893
3016
  let turnUsage;
2894
3017
  if (typeof source.totalUsage?.then === "function") {
@@ -2955,6 +3078,12 @@ function createChatSession(payload, options) {
2955
3078
  return response;
2956
3079
  },
2957
3080
  async addResponse(response) {
3081
+ // Append any non-transient data parts queued via chat.response or writer.write()
3082
+ const queuedParts = locals_js_1.locals.get(chatResponsePartsKey);
3083
+ if (queuedParts && queuedParts.length > 0) {
3084
+ response = { ...response, parts: [...(response.parts ?? []), ...queuedParts] };
3085
+ locals_js_1.locals.set(chatResponsePartsKey, []);
3086
+ }
2958
3087
  await accumulator.addResponse(response);
2959
3088
  },
2960
3089
  async done() {
@@ -3257,6 +3386,8 @@ exports.chat = {
3257
3386
  inject: injectBackgroundContext,
3258
3387
  /** Typed chat output stream for writing custom chunks or piping from subtasks. */
3259
3388
  stream: chatStream,
3389
+ /** Write data parts that persist to the response message. See {@link chatResponse}. */
3390
+ response: chatResponse,
3260
3391
  /** Pre-built input stream for receiving messages from the transport. */
3261
3392
  messages: messagesInput,
3262
3393
  /** Create a managed stop signal wired to the stop input stream. See {@link createStopSignal}. */