@trigger.dev/sdk 4.5.6 → 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.
- package/dist/commonjs/v3/agentSkillsRuntime.js +17 -7
- package/dist/commonjs/v3/agentSkillsRuntime.js.map +1 -1
- package/dist/commonjs/v3/ai.d.ts +49 -8
- package/dist/commonjs/v3/ai.js +151 -27
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/aiAutoTelemetry.js +3 -3
- package/dist/commonjs/v3/aiAutoTelemetry.js.map +1 -1
- package/dist/commonjs/v3/index.js +17 -7
- package/dist/commonjs/v3/index.js.map +1 -1
- package/dist/commonjs/v3/runs.d.ts +10 -10
- package/dist/commonjs/v3/skill.js +17 -7
- package/dist/commonjs/v3/skill.js.map +1 -1
- package/dist/commonjs/v3/test/test-session-handle.js +20 -8
- package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
- package/dist/commonjs/v3/triggerClient.js +17 -7
- package/dist/commonjs/v3/triggerClient.js.map +1 -1
- package/dist/commonjs/v3/triggerClient.types.test.js +17 -7
- package/dist/commonjs/v3/triggerClient.types.test.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +49 -8
- package/dist/esm/v3/ai.js +151 -27
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/aiAutoTelemetry.js +3 -3
- package/dist/esm/v3/aiAutoTelemetry.js.map +1 -1
- package/dist/esm/v3/test/test-session-handle.js +20 -8
- package/dist/esm/v3/test/test-session-handle.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai-chat/changelog.mdx +2 -2
- package/docs/ai-chat/compaction.mdx +2 -2
- package/docs/ai-chat/custom-agents.mdx +15 -20
- package/docs/ai-chat/fast-starts.mdx +2 -2
- package/docs/ai-chat/pending-messages.mdx +6 -2
- package/docs/ai-chat/reference.mdx +2 -2
- 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
|
-
*
|
|
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
|
|
5629
|
-
* if (
|
|
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
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
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
|
|
5677
|
-
* if (
|
|
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
|
-
|
|
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
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
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
|
-
|
|
6192
|
-
|
|
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
|