@trigger.dev/sdk 4.5.5 → 4.5.7

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.
Files changed (37) hide show
  1. package/dist/commonjs/v3/agentSkillsRuntime.js +17 -7
  2. package/dist/commonjs/v3/agentSkillsRuntime.js.map +1 -1
  3. package/dist/commonjs/v3/ai.d.ts +49 -8
  4. package/dist/commonjs/v3/ai.js +151 -27
  5. package/dist/commonjs/v3/ai.js.map +1 -1
  6. package/dist/commonjs/v3/aiAutoTelemetry.js +3 -3
  7. package/dist/commonjs/v3/aiAutoTelemetry.js.map +1 -1
  8. package/dist/commonjs/v3/index.js +17 -7
  9. package/dist/commonjs/v3/index.js.map +1 -1
  10. package/dist/commonjs/v3/runs.d.ts +10 -10
  11. package/dist/commonjs/v3/skill.js +17 -7
  12. package/dist/commonjs/v3/skill.js.map +1 -1
  13. package/dist/commonjs/v3/test/test-session-handle.js +20 -8
  14. package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
  15. package/dist/commonjs/v3/triggerClient.js +17 -7
  16. package/dist/commonjs/v3/triggerClient.js.map +1 -1
  17. package/dist/commonjs/v3/triggerClient.types.test.js +17 -7
  18. package/dist/commonjs/v3/triggerClient.types.test.js.map +1 -1
  19. package/dist/commonjs/version.js +1 -1
  20. package/dist/esm/v3/ai.d.ts +49 -8
  21. package/dist/esm/v3/ai.js +151 -27
  22. package/dist/esm/v3/ai.js.map +1 -1
  23. package/dist/esm/v3/aiAutoTelemetry.js +3 -3
  24. package/dist/esm/v3/aiAutoTelemetry.js.map +1 -1
  25. package/dist/esm/v3/test/test-session-handle.js +20 -8
  26. package/dist/esm/v3/test/test-session-handle.js.map +1 -1
  27. package/dist/esm/version.js +1 -1
  28. package/docs/ai-chat/changelog.mdx +2 -2
  29. package/docs/ai-chat/compaction.mdx +2 -2
  30. package/docs/ai-chat/custom-agents.mdx +15 -20
  31. package/docs/ai-chat/fast-starts.mdx +2 -2
  32. package/docs/ai-chat/pending-messages.mdx +6 -2
  33. package/docs/ai-chat/reference.mdx +2 -2
  34. package/docs/self-hosting/docker.mdx +19 -1
  35. package/docs/self-hosting/env/webapp.mdx +2 -1
  36. package/docs/self-hosting/kubernetes.mdx +24 -2
  37. package/package.json +3 -3
package/dist/esm/v3/ai.js CHANGED
@@ -5606,31 +5606,117 @@ function createStopSignal() {
5606
5606
  * The `TriggerChatTransport` intercepts this to close the ReadableStream
5607
5607
  * for the current turn. Call after piping the response stream.
5608
5608
  *
5609
+ * Returns two resume cursors for the turn boundary, both saveable from the
5610
+ * task instead of round-tripping them back from the client:
5611
+ * - `lastEventId` — the turn-complete control record's seq_num on
5612
+ * `session.out`; where the next turn's output stream resumes.
5613
+ * - `sessionInEventId` — the committed-consume cursor on `session.in` as of
5614
+ * this turn-complete, letting a raw loop correlate the boundary with the
5615
+ * exact input record it acknowledged. Trigger owns input-cursor recovery,
5616
+ * so this is for correlation / out-of-sync detection, not required.
5617
+ *
5618
+ * Either is `undefined` when the corresponding cursor isn't available.
5619
+ *
5609
5620
  * @example
5610
5621
  * ```ts
5611
5622
  * await chat.pipe(result);
5612
- * await chat.writeTurnComplete();
5623
+ * const { lastEventId, sessionInEventId } = await chat.writeTurnComplete();
5624
+ * await db.chats.update(chatId, { lastEventId, sessionInEventId });
5613
5625
  * ```
5614
5626
  */
5615
5627
  async function chatWriteTurnComplete(options) {
5616
- await writeTurnCompleteChunk(undefined, options?.publicAccessToken);
5628
+ const result = await writeTurnCompleteChunk(undefined, options?.publicAccessToken);
5629
+ // Same cursor written to the `session-in-event-id` header inside
5630
+ // `writeTurnCompleteChunk`; surfaced here so the caller can persist it.
5631
+ const inCursor = getChatSession().in.lastDispatchedSeqNum();
5632
+ return {
5633
+ lastEventId: result?.lastEventId,
5634
+ ...(inCursor !== undefined ? { sessionInEventId: String(inCursor) } : {}),
5635
+ };
5636
+ }
5637
+ /**
5638
+ * Pass every chunk through untouched while recording it in `buffer`. Handles
5639
+ * both the `AsyncIterable` and `ReadableStream` shapes `toUIMessageStream()`
5640
+ * can return, and propagates a source error to the consumer after buffering
5641
+ * whatever streamed first. See {@link pipeChatAndCapture} for why.
5642
+ */
5643
+ async function* tapUIMessageChunks(source, buffer) {
5644
+ if (isReadableStream(source)) {
5645
+ const reader = source.getReader();
5646
+ try {
5647
+ while (true) {
5648
+ const { done, value } = await reader.read();
5649
+ if (done)
5650
+ break;
5651
+ buffer.push(value);
5652
+ yield value;
5653
+ }
5654
+ }
5655
+ finally {
5656
+ reader.releaseLock();
5657
+ }
5658
+ }
5659
+ else {
5660
+ for await (const chunk of source) {
5661
+ buffer.push(chunk);
5662
+ yield chunk;
5663
+ }
5664
+ }
5665
+ }
5666
+ /**
5667
+ * Reconstruct a partial assistant `UIMessage` from the raw chunks that
5668
+ * streamed before a failure — the fallback for {@link pipeChatAndCapture}
5669
+ * when a transport error abandons the stream before `onFinish` runs. Uses the
5670
+ * same `readUIMessageStream` reducer as the boot-time replay path. Returns
5671
+ * `undefined` if there's nothing to assemble or the reducer throws.
5672
+ */
5673
+ async function assemblePartialFromChunks(chunks) {
5674
+ const relevant = chunks.filter((c) => {
5675
+ const type = c.type;
5676
+ return typeof type === "string" && !type.startsWith("trigger:");
5677
+ });
5678
+ if (relevant.length === 0)
5679
+ return undefined;
5680
+ try {
5681
+ const stream = new ReadableStream({
5682
+ start(controller) {
5683
+ for (const chunk of relevant)
5684
+ controller.enqueue(chunk);
5685
+ controller.close();
5686
+ },
5687
+ });
5688
+ let last;
5689
+ for await (const message of readUIMessageStream({ stream })) {
5690
+ last = message;
5691
+ }
5692
+ return last;
5693
+ }
5694
+ catch {
5695
+ return undefined;
5696
+ }
5617
5697
  }
5618
5698
  /**
5619
5699
  * Pipe a `StreamTextResult` (or similar) to the chat stream and capture
5620
5700
  * the assistant's response message via `onFinish`.
5621
5701
  *
5622
5702
  * Combines `toUIMessageStream()` + `onFinish` callback + `chat.pipe()`.
5623
- * Returns the captured `UIMessage`, or `undefined` if capture failed.
5703
+ * Never throws on a stopped or failed stream: it returns a
5704
+ * {@link PipeAndCaptureResult} whose `message` holds any partial output
5705
+ * captured before the stop/failure, alongside a typed `status` (and `error`
5706
+ * on failure). Save the partial after a stop or error without separate
5707
+ * capture logic.
5624
5708
  *
5625
5709
  * @example
5626
5710
  * ```ts
5627
5711
  * const result = streamText({ model, messages, abortSignal: signal });
5628
- * const response = await chat.pipeAndCapture(result, { signal });
5629
- * if (response) conversation.addResponse(response);
5712
+ * const { message, status, error } = await chat.pipeAndCapture(result, { signal });
5713
+ * if (message) conversation.addResponse(message);
5714
+ * if (status === "error") logger.error("turn failed", { error });
5630
5715
  * ```
5631
5716
  */
5632
5717
  async function pipeChatAndCapture(source, options) {
5633
5718
  let captured;
5719
+ let capturedFinishReason;
5634
5720
  let resolveOnFinish;
5635
5721
  const onFinishPromise = new Promise((r) => {
5636
5722
  resolveOnFinish = r;
@@ -5648,17 +5734,57 @@ async function pipeChatAndCapture(source, options) {
5648
5734
  // the frontend replaces the partial message — wiping the
5649
5735
  // pre-injection text from the UI and the captured response.
5650
5736
  generateMessageId: resolvedOptions.generateMessageId ?? generateMessageId,
5651
- onFinish: ({ responseMessage }) => {
5737
+ onFinish: ({ responseMessage, finishReason, }) => {
5652
5738
  captured = responseMessage;
5739
+ capturedFinishReason = finishReason;
5653
5740
  resolveOnFinish();
5654
5741
  },
5655
5742
  });
5656
- await pipeChat(uiStream, {
5657
- signal: options?.signal,
5658
- spanName: options?.spanName ?? "stream response",
5659
- });
5660
- await onFinishPromise;
5661
- return captured;
5743
+ // Buffer chunks as they flow to the pipe so a transport failure that
5744
+ // abandons the UI stream before `onFinish` fires — the one termination path
5745
+ // that skips `onFinish` — can still reconstruct the partial. This retains
5746
+ // chunk references only; the reassembly runs solely in the failure fallback
5747
+ // below, so the happy path pays nothing extra.
5748
+ const bufferedChunks = [];
5749
+ const tappedStream = tapUIMessageChunks(uiStream, bufferedChunks);
5750
+ let status = "complete";
5751
+ let error;
5752
+ try {
5753
+ await pipeChat(tappedStream, {
5754
+ signal: options?.signal,
5755
+ spanName: options?.spanName ?? "stream response",
5756
+ });
5757
+ // The pipe can drain cleanly on a stop — the source stream just ends
5758
+ // early — so classify by the signal rather than relying on a throw.
5759
+ if (options?.signal?.aborted) {
5760
+ status = "aborted";
5761
+ }
5762
+ }
5763
+ catch (err) {
5764
+ if ((err instanceof Error && err.name === "AbortError") || options?.signal?.aborted) {
5765
+ status = "aborted";
5766
+ }
5767
+ else {
5768
+ status = "error";
5769
+ error = err;
5770
+ }
5771
+ }
5772
+ // `onFinish` fires even on abort, carrying the partial — but a hard stop can
5773
+ // prevent it from firing at all, so race it against a timeout to avoid
5774
+ // hanging the caller. Mirrors chat.agent's capture path.
5775
+ await Promise.race([onFinishPromise, new Promise((r) => setTimeout(r, 2_000))]);
5776
+ // A transport failure can abandon the UI stream before `onFinish` fires, so
5777
+ // reconstruct the partial from the buffered chunks rather than losing output
5778
+ // that already streamed. Only runs when `onFinish` produced nothing.
5779
+ if (!captured && bufferedChunks.length > 0) {
5780
+ captured = await assemblePartialFromChunks(bufferedChunks);
5781
+ }
5782
+ return {
5783
+ message: captured,
5784
+ status,
5785
+ finishReason: capturedFinishReason,
5786
+ ...(error !== undefined ? { error } : {}),
5787
+ };
5662
5788
  }
5663
5789
  /**
5664
5790
  * Accumulates conversation messages across turns.
@@ -5673,8 +5799,8 @@ async function pipeChatAndCapture(source, options) {
5673
5799
  * for (let turn = 0; turn < 100; turn++) {
5674
5800
  * const messages = await conversation.addIncoming(payload.messages, payload.trigger, turn);
5675
5801
  * const result = streamText({ model, messages });
5676
- * const response = await chat.pipeAndCapture(result);
5677
- * if (response) await conversation.addResponse(response);
5802
+ * const { message } = await chat.pipeAndCapture(result);
5803
+ * if (message) await conversation.addResponse(message);
5678
5804
  * }
5679
5805
  * ```
5680
5806
  */
@@ -6168,7 +6294,7 @@ function createChatSession(payload, options) {
6168
6294
  }
6169
6295
  let response;
6170
6296
  try {
6171
- response = await pipeChatAndCapture(source, {
6297
+ const captured = await pipeChatAndCapture(source, {
6172
6298
  signal: combinedSignal,
6173
6299
  // On a non-final handover turn, thread the spliced partial so a
6174
6300
  // resumed tool round's tool-output chunks merge into the
@@ -6177,20 +6303,18 @@ function createChatSession(payload, options) {
6177
6303
  // fresh response into the prior assistant message).
6178
6304
  ...(handoverThisTurn ? { originalMessages: accumulator.uiMessages } : {}),
6179
6305
  });
6180
- }
6181
- catch (error) {
6182
- if (error instanceof Error && error.name === "AbortError") {
6183
- if (runSignal.aborted) {
6184
- // Full cancel — don't accumulate
6185
- sessionMsgSub.off();
6186
- await chatWriteTurnComplete();
6187
- return undefined;
6188
- }
6189
- // Stop — fall through to accumulate partial response
6306
+ if (runSignal.aborted) {
6307
+ // Full cancel — don't accumulate
6308
+ sessionMsgSub.off();
6309
+ await chatWriteTurnComplete();
6310
+ return undefined;
6190
6311
  }
6191
- else {
6192
- throw error;
6312
+ // Surface a genuine stream failure to the caller. A user stop
6313
+ // (status "aborted") falls through so the partial is accumulated.
6314
+ if (captured.status === "error") {
6315
+ throw captured.error;
6193
6316
  }
6317
+ response = captured.message;
6194
6318
  }
6195
6319
  finally {
6196
6320
  // Detach at stream end (like the agent loop): the steering queue