@trigger.dev/sdk 4.5.6 → 4.5.8
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 +258 -42
- 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/auth.d.ts +7 -3
- package/dist/commonjs/v3/auth.js +68 -13
- package/dist/commonjs/v3/auth.js.map +1 -1
- package/dist/commonjs/v3/auth.test.d.ts +1 -0
- package/dist/commonjs/v3/auth.test.js +149 -0
- package/dist/commonjs/v3/auth.test.js.map +1 -0
- package/dist/commonjs/v3/createStartSessionAction.test.js +33 -0
- package/dist/commonjs/v3/createStartSessionAction.test.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 +15 -15
- 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 +259 -43
- 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/auth.d.ts +7 -3
- package/dist/esm/v3/auth.js +69 -14
- package/dist/esm/v3/auth.js.map +1 -1
- package/dist/esm/v3/auth.test.d.ts +1 -0
- package/dist/esm/v3/auth.test.js +147 -0
- package/dist/esm/v3/auth.test.js.map +1 -0
- package/dist/esm/v3/createStartSessionAction.test.js +33 -0
- package/dist/esm/v3/createStartSessionAction.test.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessoryAttributes, apiClientManager, controlSubtype, generateJWT, getSchemaParseFn, headerValue, InputStreamOncePromise, isSchemaZodEsque, logger, ManualWaitpointPromise, OutOfMemoryError, resourceCatalog, SemanticInternalAttributes, SESSION_IN_EVENT_ID_HEADER, sessionStreams, taskContext, TRIGGER_CONTROL_SUBTYPE, } from "@trigger.dev/core/v3";
|
|
1
|
+
import { accessoryAttributes, apiClientManager, controlSubtype, generateJWT, getSchemaParseFn, headerValue, InputStreamOncePromise, isAdditionalApiKey, isSchemaZodEsque, logger, ManualWaitpointPromise, OutOfMemoryError, resourceCatalog, SemanticInternalAttributes, SESSION_IN_EVENT_ID_HEADER, sessionStreams, taskContext, TRIGGER_CONTROL_SUBTYPE, } from "@trigger.dev/core/v3";
|
|
2
2
|
// Runtime VALUES go through the ESM/CJS shim so the CJS build can `require`
|
|
3
3
|
// ESM-only `ai@7` (see ../imports/ai-runtime.ts).
|
|
4
4
|
import { trace } from "@opentelemetry/api";
|
|
@@ -3630,6 +3630,9 @@ function chatAgent(options) {
|
|
|
3630
3630
|
// Declared here so the finally can detach it — a handler leaked past
|
|
3631
3631
|
// its turn duplicates every mid-stream message into the shared buffer.
|
|
3632
3632
|
let turnMsgSub;
|
|
3633
|
+
let capturedPartialResponse;
|
|
3634
|
+
let responseCommitted = false;
|
|
3635
|
+
const turnBufferedChunks = [];
|
|
3633
3636
|
try {
|
|
3634
3637
|
// Extract turn-level context before entering the span. Slim
|
|
3635
3638
|
// wire: at most one delta message per record. `headStartMessages`
|
|
@@ -4307,11 +4310,12 @@ function chatAgent(options) {
|
|
|
4307
4310
|
generateMessageId: resolvedOptions.generateMessageId ?? generateMessageId,
|
|
4308
4311
|
onFinish: ({ responseMessage, finishReason, }) => {
|
|
4309
4312
|
capturedResponseMessage = responseMessage;
|
|
4313
|
+
capturedPartialResponse = responseMessage;
|
|
4310
4314
|
capturedFinishReason = finishReason;
|
|
4311
4315
|
resolveOnFinish();
|
|
4312
4316
|
},
|
|
4313
4317
|
});
|
|
4314
|
-
await pipeChat(uiStream, {
|
|
4318
|
+
await pipeChat(tapUIMessageChunks(uiStream, turnBufferedChunks), {
|
|
4315
4319
|
signal: combinedSignal,
|
|
4316
4320
|
spanName: "stream response",
|
|
4317
4321
|
});
|
|
@@ -4505,6 +4509,11 @@ function chatAgent(options) {
|
|
|
4505
4509
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
4506
4510
|
}
|
|
4507
4511
|
}
|
|
4512
|
+
if (capturedResponseMessage) {
|
|
4513
|
+
responseCommitted = true;
|
|
4514
|
+
capturedPartialResponse = capturedResponseMessage;
|
|
4515
|
+
turnBufferedChunks.length = 0;
|
|
4516
|
+
}
|
|
4508
4517
|
if (runSignal.aborted)
|
|
4509
4518
|
return "exit";
|
|
4510
4519
|
// Await deferred background work (e.g. DB writes from onTurnStart)
|
|
@@ -4695,6 +4704,7 @@ function chatAgent(options) {
|
|
|
4695
4704
|
parts: [...(msg.parts ?? []), ...lateParts],
|
|
4696
4705
|
};
|
|
4697
4706
|
capturedResponseMessage = accumulatedUIMessages[idx];
|
|
4707
|
+
capturedPartialResponse = capturedResponseMessage;
|
|
4698
4708
|
turnCompleteEvent.responseMessage = capturedResponseMessage;
|
|
4699
4709
|
turnCompleteEvent.uiMessages = accumulatedUIMessages;
|
|
4700
4710
|
}
|
|
@@ -4937,10 +4947,65 @@ function chatAgent(options) {
|
|
|
4937
4947
|
!accumulatedUIMessages.some((m) => m.id === erroredWireMessage.id)
|
|
4938
4948
|
? [...accumulatedUIMessages, erroredWireMessage]
|
|
4939
4949
|
: accumulatedUIMessages;
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4950
|
+
let partialResponse = capturedPartialResponse ??
|
|
4951
|
+
(await assemblePartialFromChunks(turnBufferedChunks));
|
|
4952
|
+
if (partialResponse) {
|
|
4953
|
+
partialResponse = cleanupAbortedParts(partialResponse);
|
|
4954
|
+
}
|
|
4955
|
+
let partialIdx = partialResponse?.id
|
|
4956
|
+
? erroredUIMessages.findIndex((m) => m.id === partialResponse.id)
|
|
4957
|
+
: -1;
|
|
4958
|
+
if (partialResponse && capturedPartialResponse === undefined && partialIdx !== -1) {
|
|
4959
|
+
partialResponse = undefined;
|
|
4960
|
+
partialIdx = -1;
|
|
4961
|
+
}
|
|
4962
|
+
if (partialResponse && !partialResponse.id) {
|
|
4963
|
+
partialResponse = { ...partialResponse, id: generateMessageId() };
|
|
4964
|
+
}
|
|
4965
|
+
if (partialResponse && !responseCommitted) {
|
|
4966
|
+
const queuedParts = locals.get(chatResponsePartsKey);
|
|
4967
|
+
if (queuedParts && queuedParts.length > 0) {
|
|
4968
|
+
partialResponse = {
|
|
4969
|
+
...partialResponse,
|
|
4970
|
+
parts: [...partialResponse.parts, ...queuedParts],
|
|
4971
|
+
};
|
|
4972
|
+
locals.set(chatResponsePartsKey, []);
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
const includePartial = partialResponse != null && !responseCommitted;
|
|
4976
|
+
let erroredUIMessagesWithPartial = !includePartial
|
|
4977
|
+
? erroredUIMessages
|
|
4978
|
+
: partialIdx === -1
|
|
4979
|
+
? [...erroredUIMessages, partialResponse]
|
|
4980
|
+
: erroredUIMessages.map((m, i) => i === partialIdx ? partialResponse : m);
|
|
4981
|
+
let erroredNewUIMessages = erroredWireMessage ? [erroredWireMessage] : [];
|
|
4982
|
+
if (includePartial) {
|
|
4983
|
+
erroredNewUIMessages.push(partialResponse);
|
|
4984
|
+
}
|
|
4985
|
+
let erroredNewModelMessages = [];
|
|
4986
|
+
if (!responseCommitted) {
|
|
4987
|
+
try {
|
|
4988
|
+
if (erroredNewUIMessages.length > 0) {
|
|
4989
|
+
erroredNewModelMessages = await toModelMessages(erroredNewUIMessages.map((m) => stripProviderMetadata(m)));
|
|
4990
|
+
}
|
|
4991
|
+
if (erroredUIMessagesWithPartial !== accumulatedUIMessages) {
|
|
4992
|
+
if (partialIdx === -1) {
|
|
4993
|
+
const appended = erroredUIMessagesWithPartial.slice(accumulatedUIMessages.length);
|
|
4994
|
+
accumulatedMessages.push(...(await toModelMessages(appended.map((m) => stripProviderMetadata(m)))));
|
|
4995
|
+
}
|
|
4996
|
+
else {
|
|
4997
|
+
accumulatedMessages = await toModelMessages(erroredUIMessagesWithPartial);
|
|
4998
|
+
}
|
|
4999
|
+
accumulatedUIMessages = erroredUIMessagesWithPartial;
|
|
5000
|
+
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
catch {
|
|
5004
|
+
erroredNewModelMessages = [];
|
|
5005
|
+
erroredUIMessagesWithPartial = erroredUIMessages;
|
|
5006
|
+
erroredNewUIMessages = erroredWireMessage ? [erroredWireMessage] : [];
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
4944
5009
|
if (onTurnComplete) {
|
|
4945
5010
|
try {
|
|
4946
5011
|
await tracer.startActiveSpan("onTurnComplete()", async () => {
|
|
@@ -4948,11 +5013,11 @@ function chatAgent(options) {
|
|
|
4948
5013
|
ctx,
|
|
4949
5014
|
chatId: currentWirePayload.chatId,
|
|
4950
5015
|
messages: accumulatedMessages,
|
|
4951
|
-
uiMessages:
|
|
4952
|
-
newMessages:
|
|
4953
|
-
newUIMessages:
|
|
4954
|
-
responseMessage:
|
|
4955
|
-
rawResponseMessage:
|
|
5016
|
+
uiMessages: erroredUIMessagesWithPartial,
|
|
5017
|
+
newMessages: erroredNewModelMessages,
|
|
5018
|
+
newUIMessages: erroredNewUIMessages,
|
|
5019
|
+
responseMessage: partialResponse,
|
|
5020
|
+
rawResponseMessage: partialResponse,
|
|
4956
5021
|
turn,
|
|
4957
5022
|
runId: ctx.run.id,
|
|
4958
5023
|
chatAccessToken: "",
|
|
@@ -4996,7 +5061,7 @@ function chatAgent(options) {
|
|
|
4996
5061
|
await writeChatSnapshot(sessionIdForSnapshot, {
|
|
4997
5062
|
version: 1,
|
|
4998
5063
|
savedAt: Date.now(),
|
|
4999
|
-
messages:
|
|
5064
|
+
messages: erroredUIMessagesWithPartial,
|
|
5000
5065
|
lastOutEventId: errorTurnCompleteResult?.lastEventId,
|
|
5001
5066
|
lastInEventId: errorSnapshotInCursor !== undefined ? String(errorSnapshotInCursor) : undefined,
|
|
5002
5067
|
});
|
|
@@ -5606,31 +5671,110 @@ function createStopSignal() {
|
|
|
5606
5671
|
* The `TriggerChatTransport` intercepts this to close the ReadableStream
|
|
5607
5672
|
* for the current turn. Call after piping the response stream.
|
|
5608
5673
|
*
|
|
5674
|
+
* Returns two resume cursors for the turn boundary, both saveable from the
|
|
5675
|
+
* task instead of round-tripping them back from the client:
|
|
5676
|
+
* - `lastEventId` — the turn-complete control record's seq_num on
|
|
5677
|
+
* `session.out`; where the next turn's output stream resumes.
|
|
5678
|
+
* - `sessionInEventId` — the committed-consume cursor on `session.in` as of
|
|
5679
|
+
* this turn-complete, letting a raw loop correlate the boundary with the
|
|
5680
|
+
* exact input record it acknowledged. Trigger owns input-cursor recovery,
|
|
5681
|
+
* so this is for correlation / out-of-sync detection, not required.
|
|
5682
|
+
*
|
|
5683
|
+
* Either is `undefined` when the corresponding cursor isn't available.
|
|
5684
|
+
*
|
|
5609
5685
|
* @example
|
|
5610
5686
|
* ```ts
|
|
5611
5687
|
* await chat.pipe(result);
|
|
5612
|
-
* await chat.writeTurnComplete();
|
|
5688
|
+
* const { lastEventId, sessionInEventId } = await chat.writeTurnComplete();
|
|
5689
|
+
* await db.chats.update(chatId, { lastEventId, sessionInEventId });
|
|
5613
5690
|
* ```
|
|
5614
5691
|
*/
|
|
5615
5692
|
async function chatWriteTurnComplete(options) {
|
|
5616
|
-
await writeTurnCompleteChunk(undefined, options?.publicAccessToken);
|
|
5693
|
+
const result = await writeTurnCompleteChunk(undefined, options?.publicAccessToken);
|
|
5694
|
+
// Same cursor written to the `session-in-event-id` header inside
|
|
5695
|
+
// `writeTurnCompleteChunk`; surfaced here so the caller can persist it.
|
|
5696
|
+
const inCursor = getChatSession().in.lastDispatchedSeqNum();
|
|
5697
|
+
return {
|
|
5698
|
+
lastEventId: result?.lastEventId,
|
|
5699
|
+
...(inCursor !== undefined ? { sessionInEventId: String(inCursor) } : {}),
|
|
5700
|
+
};
|
|
5701
|
+
}
|
|
5702
|
+
/**
|
|
5703
|
+
* Pass every chunk through untouched while recording it in `buffer`. Handles
|
|
5704
|
+
* both the `AsyncIterable` and `ReadableStream` shapes `toUIMessageStream()`
|
|
5705
|
+
* can return, and propagates a source error to the consumer after buffering
|
|
5706
|
+
* whatever streamed first. See {@link pipeChatAndCapture} for why.
|
|
5707
|
+
*/
|
|
5708
|
+
function tapUIMessageChunks(source, buffer) {
|
|
5709
|
+
if (isReadableStream(source)) {
|
|
5710
|
+
return source.pipeThrough(new TransformStream({
|
|
5711
|
+
transform(chunk, controller) {
|
|
5712
|
+
buffer.push(chunk);
|
|
5713
|
+
controller.enqueue(chunk);
|
|
5714
|
+
},
|
|
5715
|
+
}));
|
|
5716
|
+
}
|
|
5717
|
+
return (async function* () {
|
|
5718
|
+
for await (const chunk of source) {
|
|
5719
|
+
buffer.push(chunk);
|
|
5720
|
+
yield chunk;
|
|
5721
|
+
}
|
|
5722
|
+
})();
|
|
5723
|
+
}
|
|
5724
|
+
/**
|
|
5725
|
+
* Reconstruct a partial assistant `UIMessage` from the raw chunks that
|
|
5726
|
+
* streamed before a failure — the fallback for {@link pipeChatAndCapture}
|
|
5727
|
+
* when a transport error abandons the stream before `onFinish` runs. Uses the
|
|
5728
|
+
* same `readUIMessageStream` reducer as the boot-time replay path. Returns
|
|
5729
|
+
* `undefined` if there's nothing to assemble or the reducer throws.
|
|
5730
|
+
*/
|
|
5731
|
+
async function assemblePartialFromChunks(chunks) {
|
|
5732
|
+
const relevant = chunks.filter((c) => {
|
|
5733
|
+
const type = c.type;
|
|
5734
|
+
return typeof type === "string" && !type.startsWith("trigger:");
|
|
5735
|
+
});
|
|
5736
|
+
if (relevant.length === 0)
|
|
5737
|
+
return undefined;
|
|
5738
|
+
try {
|
|
5739
|
+
const stream = new ReadableStream({
|
|
5740
|
+
start(controller) {
|
|
5741
|
+
for (const chunk of relevant)
|
|
5742
|
+
controller.enqueue(chunk);
|
|
5743
|
+
controller.close();
|
|
5744
|
+
},
|
|
5745
|
+
});
|
|
5746
|
+
let last;
|
|
5747
|
+
for await (const message of readUIMessageStream({ stream })) {
|
|
5748
|
+
last = message;
|
|
5749
|
+
}
|
|
5750
|
+
return last;
|
|
5751
|
+
}
|
|
5752
|
+
catch {
|
|
5753
|
+
return undefined;
|
|
5754
|
+
}
|
|
5617
5755
|
}
|
|
5618
5756
|
/**
|
|
5619
5757
|
* Pipe a `StreamTextResult` (or similar) to the chat stream and capture
|
|
5620
5758
|
* the assistant's response message via `onFinish`.
|
|
5621
5759
|
*
|
|
5622
5760
|
* Combines `toUIMessageStream()` + `onFinish` callback + `chat.pipe()`.
|
|
5623
|
-
*
|
|
5761
|
+
* Never throws on a stopped or failed stream: it returns a
|
|
5762
|
+
* {@link PipeAndCaptureResult} whose `message` holds any partial output
|
|
5763
|
+
* captured before the stop/failure, alongside a typed `status` (and `error`
|
|
5764
|
+
* on failure). Save the partial after a stop or error without separate
|
|
5765
|
+
* capture logic.
|
|
5624
5766
|
*
|
|
5625
5767
|
* @example
|
|
5626
5768
|
* ```ts
|
|
5627
5769
|
* const result = streamText({ model, messages, abortSignal: signal });
|
|
5628
|
-
* const
|
|
5629
|
-
* if (
|
|
5770
|
+
* const { message, status, error } = await chat.pipeAndCapture(result, { signal });
|
|
5771
|
+
* if (message) conversation.addResponse(message);
|
|
5772
|
+
* if (status === "error") logger.error("turn failed", { error });
|
|
5630
5773
|
* ```
|
|
5631
5774
|
*/
|
|
5632
5775
|
async function pipeChatAndCapture(source, options) {
|
|
5633
5776
|
let captured;
|
|
5777
|
+
let capturedFinishReason;
|
|
5634
5778
|
let resolveOnFinish;
|
|
5635
5779
|
const onFinishPromise = new Promise((r) => {
|
|
5636
5780
|
resolveOnFinish = r;
|
|
@@ -5648,17 +5792,57 @@ async function pipeChatAndCapture(source, options) {
|
|
|
5648
5792
|
// the frontend replaces the partial message — wiping the
|
|
5649
5793
|
// pre-injection text from the UI and the captured response.
|
|
5650
5794
|
generateMessageId: resolvedOptions.generateMessageId ?? generateMessageId,
|
|
5651
|
-
onFinish: ({ responseMessage }) => {
|
|
5795
|
+
onFinish: ({ responseMessage, finishReason, }) => {
|
|
5652
5796
|
captured = responseMessage;
|
|
5797
|
+
capturedFinishReason = finishReason;
|
|
5653
5798
|
resolveOnFinish();
|
|
5654
5799
|
},
|
|
5655
5800
|
});
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5801
|
+
// Buffer chunks as they flow to the pipe so a transport failure that
|
|
5802
|
+
// abandons the UI stream before `onFinish` fires — the one termination path
|
|
5803
|
+
// that skips `onFinish` — can still reconstruct the partial. This retains
|
|
5804
|
+
// chunk references only; the reassembly runs solely in the failure fallback
|
|
5805
|
+
// below, so the happy path pays nothing extra.
|
|
5806
|
+
const bufferedChunks = [];
|
|
5807
|
+
const tappedStream = tapUIMessageChunks(uiStream, bufferedChunks);
|
|
5808
|
+
let status = "complete";
|
|
5809
|
+
let error;
|
|
5810
|
+
try {
|
|
5811
|
+
await pipeChat(tappedStream, {
|
|
5812
|
+
signal: options?.signal,
|
|
5813
|
+
spanName: options?.spanName ?? "stream response",
|
|
5814
|
+
});
|
|
5815
|
+
// The pipe can drain cleanly on a stop — the source stream just ends
|
|
5816
|
+
// early — so classify by the signal rather than relying on a throw.
|
|
5817
|
+
if (options?.signal?.aborted) {
|
|
5818
|
+
status = "aborted";
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
catch (err) {
|
|
5822
|
+
if ((err instanceof Error && err.name === "AbortError") || options?.signal?.aborted) {
|
|
5823
|
+
status = "aborted";
|
|
5824
|
+
}
|
|
5825
|
+
else {
|
|
5826
|
+
status = "error";
|
|
5827
|
+
error = err;
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
// `onFinish` fires even on abort, carrying the partial — but a hard stop can
|
|
5831
|
+
// prevent it from firing at all, so race it against a timeout to avoid
|
|
5832
|
+
// hanging the caller. Mirrors chat.agent's capture path.
|
|
5833
|
+
await Promise.race([onFinishPromise, new Promise((r) => setTimeout(r, 2_000))]);
|
|
5834
|
+
// A transport failure can abandon the UI stream before `onFinish` fires, so
|
|
5835
|
+
// reconstruct the partial from the buffered chunks rather than losing output
|
|
5836
|
+
// that already streamed. Only runs when `onFinish` produced nothing.
|
|
5837
|
+
if (!captured && bufferedChunks.length > 0) {
|
|
5838
|
+
captured = await assemblePartialFromChunks(bufferedChunks);
|
|
5839
|
+
}
|
|
5840
|
+
return {
|
|
5841
|
+
message: captured,
|
|
5842
|
+
status,
|
|
5843
|
+
finishReason: capturedFinishReason,
|
|
5844
|
+
...(error !== undefined ? { error } : {}),
|
|
5845
|
+
};
|
|
5662
5846
|
}
|
|
5663
5847
|
/**
|
|
5664
5848
|
* Accumulates conversation messages across turns.
|
|
@@ -5673,8 +5857,8 @@ async function pipeChatAndCapture(source, options) {
|
|
|
5673
5857
|
* for (let turn = 0; turn < 100; turn++) {
|
|
5674
5858
|
* const messages = await conversation.addIncoming(payload.messages, payload.trigger, turn);
|
|
5675
5859
|
* const result = streamText({ model, messages });
|
|
5676
|
-
* const
|
|
5677
|
-
* if (
|
|
5860
|
+
* const { message } = await chat.pipeAndCapture(result);
|
|
5861
|
+
* if (message) await conversation.addResponse(message);
|
|
5678
5862
|
* }
|
|
5679
5863
|
* ```
|
|
5680
5864
|
*/
|
|
@@ -6168,7 +6352,7 @@ function createChatSession(payload, options) {
|
|
|
6168
6352
|
}
|
|
6169
6353
|
let response;
|
|
6170
6354
|
try {
|
|
6171
|
-
|
|
6355
|
+
const captured = await pipeChatAndCapture(source, {
|
|
6172
6356
|
signal: combinedSignal,
|
|
6173
6357
|
// On a non-final handover turn, thread the spliced partial so a
|
|
6174
6358
|
// resumed tool round's tool-output chunks merge into the
|
|
@@ -6177,20 +6361,27 @@ function createChatSession(payload, options) {
|
|
|
6177
6361
|
// fresh response into the prior assistant message).
|
|
6178
6362
|
...(handoverThisTurn ? { originalMessages: accumulator.uiMessages } : {}),
|
|
6179
6363
|
});
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
sessionMsgSub.off();
|
|
6186
|
-
await chatWriteTurnComplete();
|
|
6187
|
-
return undefined;
|
|
6188
|
-
}
|
|
6189
|
-
// Stop — fall through to accumulate partial response
|
|
6364
|
+
if (runSignal.aborted) {
|
|
6365
|
+
// Full cancel — don't accumulate
|
|
6366
|
+
sessionMsgSub.off();
|
|
6367
|
+
await chatWriteTurnComplete();
|
|
6368
|
+
return undefined;
|
|
6190
6369
|
}
|
|
6191
|
-
|
|
6192
|
-
|
|
6370
|
+
// Surface a genuine stream failure to the caller. A user stop
|
|
6371
|
+
// (status "aborted") falls through so the partial is accumulated.
|
|
6372
|
+
if (captured.status === "error") {
|
|
6373
|
+
if (captured.message) {
|
|
6374
|
+
const partial = cleanupAbortedParts(captured.message);
|
|
6375
|
+
const queuedParts = locals.get(chatResponsePartsKey);
|
|
6376
|
+
if (queuedParts && queuedParts.length > 0) {
|
|
6377
|
+
partial.parts = [...(partial.parts ?? []), ...queuedParts];
|
|
6378
|
+
locals.set(chatResponsePartsKey, []);
|
|
6379
|
+
}
|
|
6380
|
+
await accumulator.addResponse(partial);
|
|
6381
|
+
}
|
|
6382
|
+
throw captured.error;
|
|
6193
6383
|
}
|
|
6384
|
+
response = captured.message;
|
|
6194
6385
|
}
|
|
6195
6386
|
finally {
|
|
6196
6387
|
// Detach at stream end (like the agent loop): the steering queue
|
|
@@ -6732,24 +6923,49 @@ async function mintPublicTokenWithOverride(args) {
|
|
|
6732
6923
|
throw new Error("chat.createStartSessionAction: no API access token configured for JWT mint.");
|
|
6733
6924
|
}
|
|
6734
6925
|
const ctx = { endpoint: "auth", chatId: args.chatId };
|
|
6735
|
-
const
|
|
6926
|
+
const scopes = [`read:sessions:${args.chatId}`, `write:sessions:${args.chatId}`];
|
|
6927
|
+
const serverMint = isAdditionalApiKey(accessToken);
|
|
6928
|
+
const endpoint = serverMint ? "/api/v1/auth/public-tokens" : "/api/v1/auth/jwt/claims";
|
|
6929
|
+
const url = `${resolveChatStartBaseURL("auth", args.chatId, args.baseURLOption)}${endpoint}`;
|
|
6736
6930
|
const init = {
|
|
6737
6931
|
method: "POST",
|
|
6738
6932
|
headers: overrideRequestHeaders(accessToken),
|
|
6933
|
+
...(serverMint
|
|
6934
|
+
? {
|
|
6935
|
+
body: JSON.stringify({
|
|
6936
|
+
scopes,
|
|
6937
|
+
expirationTime: args.expirationTime instanceof Date
|
|
6938
|
+
? Math.floor(args.expirationTime.getTime() / 1000)
|
|
6939
|
+
: args.expirationTime,
|
|
6940
|
+
}),
|
|
6941
|
+
}
|
|
6942
|
+
: {}),
|
|
6739
6943
|
};
|
|
6740
6944
|
const response = args.fetchOverride
|
|
6741
6945
|
? await args.fetchOverride(url, init, ctx)
|
|
6742
6946
|
: await fetch(url, init);
|
|
6743
6947
|
if (!response.ok) {
|
|
6948
|
+
// An additional API key cannot self-sign, so it must use the server mint
|
|
6949
|
+
// endpoint. On a server too old to expose it, explain the recovery path
|
|
6950
|
+
// instead of surfacing a bare 404 (mirrors auth.createServerPublicToken).
|
|
6951
|
+
if (serverMint && response.status === 404) {
|
|
6952
|
+
throw new Error("This additional API key cannot self-sign public tokens, and the server does not support public-token minting. Upgrade the server or use the root API key.");
|
|
6953
|
+
}
|
|
6744
6954
|
const text = await response.text().catch(() => "");
|
|
6745
6955
|
throw new Error(`auth.createPublicToken failed: ${response.status} ${text}`);
|
|
6746
6956
|
}
|
|
6747
|
-
const
|
|
6957
|
+
const responseBody = (await response.json());
|
|
6958
|
+
if (serverMint) {
|
|
6959
|
+
if (typeof responseBody.token !== "string") {
|
|
6960
|
+
throw new Error("auth.createPublicToken failed: server response did not include a token");
|
|
6961
|
+
}
|
|
6962
|
+
return responseBody.token;
|
|
6963
|
+
}
|
|
6748
6964
|
return generateJWT({
|
|
6749
6965
|
secretKey: accessToken,
|
|
6750
6966
|
payload: {
|
|
6751
|
-
...
|
|
6752
|
-
scopes
|
|
6967
|
+
...responseBody,
|
|
6968
|
+
scopes,
|
|
6753
6969
|
},
|
|
6754
6970
|
expirationTime: args.expirationTime,
|
|
6755
6971
|
});
|