@trigger.dev/sdk 4.6.4 → 4.7.1
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/ai.d.ts +5 -2
- package/dist/commonjs/v3/ai.js +189 -266
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.js +7 -0
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +1 -0
- package/dist/commonjs/v3/chat-server.js +8 -0
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +28 -4
- package/dist/commonjs/v3/chat.js +41 -9
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/chatRouteWait.d.ts +21 -0
- package/dist/commonjs/v3/chatRouteWait.js +43 -0
- package/dist/commonjs/v3/chatRouteWait.js.map +1 -0
- package/dist/commonjs/v3/compactionResponse.js +5 -0
- package/dist/commonjs/v3/compactionResponse.js.map +1 -1
- package/dist/commonjs/v3/concurrency-shared.d.ts +13 -0
- package/dist/commonjs/v3/concurrency-shared.js +35 -0
- package/dist/commonjs/v3/concurrency-shared.js.map +1 -0
- package/dist/commonjs/v3/concurrencyLimits.d.ts +73 -0
- package/dist/commonjs/v3/concurrencyLimits.js +166 -0
- package/dist/commonjs/v3/concurrencyLimits.js.map +1 -0
- package/dist/commonjs/v3/index.d.ts +2 -1
- package/dist/commonjs/v3/index.js +3 -1
- package/dist/commonjs/v3/index.js.map +1 -1
- package/dist/commonjs/v3/managedChatResponse.d.ts +44 -0
- package/dist/commonjs/v3/managedChatResponse.js +233 -0
- package/dist/commonjs/v3/managedChatResponse.js.map +1 -0
- package/dist/commonjs/v3/queues.d.ts +31 -0
- package/dist/commonjs/v3/queues.js +31 -0
- package/dist/commonjs/v3/queues.js.map +1 -1
- package/dist/commonjs/v3/shared.d.ts +18 -1
- package/dist/commonjs/v3/shared.js +137 -47
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/steeringContext.d.ts +41 -0
- package/dist/commonjs/v3/steeringContext.js +118 -0
- package/dist/commonjs/v3/steeringContext.js.map +1 -0
- package/dist/commonjs/v3/transcriptStorage.d.ts +4 -1
- package/dist/commonjs/v3/transcriptStorage.js +51 -4
- package/dist/commonjs/v3/transcriptStorage.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +5 -2
- package/dist/esm/v3/ai.js +189 -266
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.js +7 -0
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +1 -0
- package/dist/esm/v3/chat-server.js +8 -0
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +28 -4
- package/dist/esm/v3/chat.js +41 -9
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/chatRouteWait.d.ts +21 -0
- package/dist/esm/v3/chatRouteWait.js +40 -0
- package/dist/esm/v3/chatRouteWait.js.map +1 -0
- package/dist/esm/v3/compactionResponse.js +5 -0
- package/dist/esm/v3/compactionResponse.js.map +1 -1
- package/dist/esm/v3/concurrency-shared.d.ts +13 -0
- package/dist/esm/v3/concurrency-shared.js +31 -0
- package/dist/esm/v3/concurrency-shared.js.map +1 -0
- package/dist/esm/v3/concurrencyLimits.d.ts +73 -0
- package/dist/esm/v3/concurrencyLimits.js +158 -0
- package/dist/esm/v3/concurrencyLimits.js.map +1 -0
- package/dist/esm/v3/index.d.ts +2 -1
- package/dist/esm/v3/index.js +2 -1
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/esm/v3/managedChatResponse.d.ts +44 -0
- package/dist/esm/v3/managedChatResponse.js +228 -0
- package/dist/esm/v3/managedChatResponse.js.map +1 -0
- package/dist/esm/v3/queues.d.ts +31 -0
- package/dist/esm/v3/queues.js +31 -0
- package/dist/esm/v3/queues.js.map +1 -1
- package/dist/esm/v3/shared.d.ts +18 -1
- package/dist/esm/v3/shared.js +136 -47
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/steeringContext.d.ts +41 -0
- package/dist/esm/v3/steeringContext.js +113 -0
- package/dist/esm/v3/steeringContext.js.map +1 -0
- package/dist/esm/v3/transcriptStorage.d.ts +4 -1
- package/dist/esm/v3/transcriptStorage.js +51 -4
- package/dist/esm/v3/transcriptStorage.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai-chat/client-protocol.mdx +3 -1
- package/docs/ai-chat/error-handling.mdx +44 -76
- package/docs/ai-chat/fast-starts.mdx +1 -1
- package/docs/ai-chat/frontend.mdx +27 -21
- package/docs/ai-chat/patterns/branching-conversations.mdx +95 -230
- package/docs/ai-chat/patterns/human-in-the-loop.mdx +166 -164
- package/docs/ai-chat/patterns/tool-result-auditing.mdx +28 -27
- package/docs/ai-chat/patterns/version-upgrades.mdx +4 -4
- package/docs/ai-chat/pending-messages.mdx +19 -5
- package/docs/ai-chat/quick-start.mdx +26 -20
- package/docs/ai-chat/reference.mdx +21 -3
- package/docs/ai-chat/sessions.mdx +1 -1
- package/docs/ai-chat/testing.mdx +16 -4
- package/docs/concurrency.mdx +384 -0
- package/docs/config/config-file.mdx +2 -0
- package/docs/database-connections.mdx +3 -3
- package/docs/deploy-environment-variables.mdx +6 -0
- package/docs/deployment/atomic-deployment.mdx +416 -132
- package/docs/deployment/overview.mdx +2 -2
- package/docs/deployment/preview-branches.mdx +2 -2
- package/docs/github-actions.mdx +26 -16
- package/docs/github-integration.mdx +2 -2
- package/docs/idempotency.mdx +43 -5
- package/docs/introduction.mdx +1 -1
- package/docs/limits.mdx +16 -6
- package/docs/manual-setup.mdx +1 -1
- package/docs/observability/query.mdx +25 -0
- package/docs/queues.mdx +271 -0
- package/docs/reports.mdx +1 -1
- package/docs/runs/priority.mdx +2 -25
- package/docs/self-hosting/env/webapp.mdx +9 -0
- package/docs/self-hosting/kubernetes.mdx +14 -3
- package/docs/tasks/overview.mdx +3 -5
- package/docs/troubleshooting-alerts.mdx +124 -1
- package/docs/troubleshooting.mdx +12 -0
- package/docs/vercel-integration.mdx +6 -7
- package/docs/versioning.mdx +1 -1
- package/docs/writing-tasks-introduction.mdx +2 -1
- package/package.json +2 -2
- package/docs/deployment/version-skew-protection.mdx +0 -492
- package/docs/queue-concurrency.mdx +0 -358
package/dist/esm/v3/ai.js
CHANGED
|
@@ -3,9 +3,12 @@ import { accessoryAttributes, apiClientManager, controlSubtype, generateJWT, get
|
|
|
3
3
|
// ESM-only `ai@7` (see ../imports/ai-runtime.ts).
|
|
4
4
|
import { trace } from "@opentelemetry/api";
|
|
5
5
|
import { traceSessionIdle } from "./sessionTracing.js";
|
|
6
|
+
import { waitForChatRouteAfterIdle } from "./chatRouteWait.js";
|
|
6
7
|
import { tool as aiTool, convertToModelMessages, dynamicTool, generateId as generateMessageId, getToolName, isToolUIPart, jsonSchema, readUIMessageStream, streamText as aiStreamText, zodSchema, } from "../imports/ai-runtime.js";
|
|
7
8
|
import { createTranscriptShadow, defaultStorage, diffTranscript, fingerprintMessage, parseTranscriptRuntimeState, restoreModelLane, } from "./transcriptStorage.js";
|
|
8
9
|
import { responseAfterCompaction } from "./compactionResponse.js";
|
|
10
|
+
import { ManagedChatResponse, createOrderedChatWriter } from "./managedChatResponse.js";
|
|
11
|
+
import { convertSteeredMessages, retainStepMessages, steeringMarkers, } from "./steeringContext.js";
|
|
9
12
|
let transcriptStorageOverride;
|
|
10
13
|
/**
|
|
11
14
|
* Test-only override for the storage `chat.agent` persists through, so a
|
|
@@ -33,6 +36,7 @@ import { withResolvedExternalDeploymentId } from "./externalDeploymentId.js";
|
|
|
33
36
|
import { resolvePinToFollow } from "./chatVersionSkew.js";
|
|
34
37
|
import { sessions, } from "./sessions.js";
|
|
35
38
|
import { createTask } from "./shared.js";
|
|
39
|
+
import { triggerConcurrencyBody } from "./concurrency-shared.js";
|
|
36
40
|
import { markChatAgentRunForStreamsWarning } from "./streams.js";
|
|
37
41
|
import { tracer } from "./tracer.js";
|
|
38
42
|
const METADATA_KEY = "tool.execute.options";
|
|
@@ -41,17 +45,17 @@ const METADATA_KEY = "tool.execute.options";
|
|
|
41
45
|
* `ignoreIncompleteToolCalls: true` to prevent failures from
|
|
42
46
|
* stopped/aborted conversations with partial tool parts.
|
|
43
47
|
*/
|
|
44
|
-
function toModelMessages(messages) {
|
|
48
|
+
function toModelMessages(messages, context) {
|
|
45
49
|
// Pass the resolved per-turn `tools` (if any) so the AI SDK can look up each
|
|
46
50
|
// tool's `toModelOutput` and re-apply it to prior-turn tool results. Without
|
|
47
51
|
// `tools` it falls back to JSON-stringifying the raw output (TRI-10149). The
|
|
48
52
|
// conditional spread keeps the options object byte-identical to the no-tools
|
|
49
53
|
// path when nothing was declared.
|
|
50
54
|
const tools = locals.get(chatResolvedToolsKey);
|
|
51
|
-
return
|
|
55
|
+
return convertSteeredMessages(messages, async (batch) => convertToModelMessages(batch, {
|
|
52
56
|
ignoreIncompleteToolCalls: true,
|
|
53
57
|
...(tools ? { tools } : {}),
|
|
54
|
-
});
|
|
58
|
+
}), locals.get(chatSteeringInjectionsKey) ?? new Map(), context ?? locals.get(chatCurrentUIMessagesKey) ?? messages, context !== undefined || locals.get(chatCurrentUIMessagesKey) !== undefined);
|
|
55
59
|
}
|
|
56
60
|
const chatTurnContextKey = locals.create("chat.turnContext");
|
|
57
61
|
/**
|
|
@@ -848,7 +852,7 @@ const chatStream = {
|
|
|
848
852
|
* `onTurnComplete`'s `responseMessage` and `uiMessages`.
|
|
849
853
|
*
|
|
850
854
|
* Non-transient data chunks (`type` starts with `data-`, no `transient: true`)
|
|
851
|
-
* are
|
|
855
|
+
* are accumulated in emission order into the assistant response message.
|
|
852
856
|
* Transient or non-data chunks are streamed only (same as `chat.stream`).
|
|
853
857
|
*
|
|
854
858
|
* @example
|
|
@@ -866,15 +870,7 @@ const chatResponse = {
|
|
|
866
870
|
* response message; everything else is stream-only.
|
|
867
871
|
*/
|
|
868
872
|
write(part) {
|
|
869
|
-
|
|
870
|
-
const { waitUntilComplete } = chatStream.writer({
|
|
871
|
-
spanName: "chat.response.write",
|
|
872
|
-
collapsed: true,
|
|
873
|
-
execute: ({ write }) => {
|
|
874
|
-
write(part);
|
|
875
|
-
},
|
|
876
|
-
});
|
|
877
|
-
waitUntilComplete().catch(() => { });
|
|
873
|
+
managedResponse().writeData(part);
|
|
878
874
|
},
|
|
879
875
|
};
|
|
880
876
|
/**
|
|
@@ -883,60 +879,13 @@ const chatResponse = {
|
|
|
883
879
|
* @internal
|
|
884
880
|
*/
|
|
885
881
|
function createLazyChatWriter() {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
let waitPromise = null;
|
|
889
|
-
let resolveExecute = null;
|
|
890
|
-
let started = false;
|
|
891
|
-
const bufferedParts = [];
|
|
892
|
-
const bufferedStreams = [];
|
|
893
|
-
function ensureInitialized() {
|
|
894
|
-
if (started)
|
|
895
|
-
return;
|
|
896
|
-
started = true;
|
|
897
|
-
const executePromise = new Promise((resolve) => {
|
|
898
|
-
resolveExecute = resolve;
|
|
899
|
-
});
|
|
900
|
-
const { waitUntilComplete } = chatStream.writer({
|
|
882
|
+
return createOrderedChatWriter(() => (locals.get(chatManagedResponseActiveKey) ? managedResponse() : undefined), async (stream) => {
|
|
883
|
+
const { waitUntilComplete } = chatStream.pipe(stream, {
|
|
901
884
|
collapsed: true,
|
|
902
885
|
spanName: "callback writer",
|
|
903
|
-
execute: ({ write, merge }) => {
|
|
904
|
-
writeImpl = write;
|
|
905
|
-
mergeImpl = merge;
|
|
906
|
-
for (const part of bufferedParts.splice(0))
|
|
907
|
-
write(part);
|
|
908
|
-
for (const stream of bufferedStreams.splice(0))
|
|
909
|
-
merge(stream);
|
|
910
|
-
return executePromise;
|
|
911
|
-
},
|
|
912
886
|
});
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
return {
|
|
916
|
-
writer: {
|
|
917
|
-
write(part) {
|
|
918
|
-
ensureInitialized();
|
|
919
|
-
queueResponsePart(part);
|
|
920
|
-
if (writeImpl)
|
|
921
|
-
writeImpl(part);
|
|
922
|
-
else
|
|
923
|
-
bufferedParts.push(part);
|
|
924
|
-
},
|
|
925
|
-
merge(stream) {
|
|
926
|
-
ensureInitialized();
|
|
927
|
-
if (mergeImpl)
|
|
928
|
-
mergeImpl(stream);
|
|
929
|
-
else
|
|
930
|
-
bufferedStreams.push(stream);
|
|
931
|
-
},
|
|
932
|
-
},
|
|
933
|
-
async flush() {
|
|
934
|
-
if (resolveExecute) {
|
|
935
|
-
resolveExecute(); // Signal execute to complete
|
|
936
|
-
await waitPromise(); // Wait for stream to finish piping
|
|
937
|
-
}
|
|
938
|
-
},
|
|
939
|
-
};
|
|
887
|
+
await waitUntilComplete();
|
|
888
|
+
});
|
|
940
889
|
}
|
|
941
890
|
/**
|
|
942
891
|
* Runs a callback with a lazy ChatWriter, flushing the stream after completion.
|
|
@@ -1112,31 +1061,20 @@ async function waitOnChatRoute(route, options) {
|
|
|
1112
1061
|
if (options.onSuspend)
|
|
1113
1062
|
await options.onSuspend();
|
|
1114
1063
|
span.setAttribute("wait.resolved", "suspended");
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const wake = await session.in.awaitWake({
|
|
1126
|
-
timeout: options.timeout,
|
|
1127
|
-
lastSeqNum: wakeFrom,
|
|
1128
|
-
});
|
|
1129
|
-
if (!wake.ok) {
|
|
1130
|
-
span.recordException(wake.error);
|
|
1131
|
-
return { ok: false, error: wake.error };
|
|
1132
|
-
}
|
|
1133
|
-
const record = await router.next(route);
|
|
1134
|
-
if (!record)
|
|
1135
|
-
continue;
|
|
1136
|
-
if (options.onResume)
|
|
1137
|
-
await options.onResume();
|
|
1138
|
-
return { ok: true, output: record.data, record };
|
|
1064
|
+
const result = await waitForChatRouteAfterIdle(router, route, {
|
|
1065
|
+
timeout: options.timeout,
|
|
1066
|
+
wake: async (timeout, lastSeqNum) => {
|
|
1067
|
+
span.setAttribute("wait.lastSeqNum", lastSeqNum ?? -1);
|
|
1068
|
+
return session.in.awaitWake({ timeout, lastSeqNum });
|
|
1069
|
+
},
|
|
1070
|
+
});
|
|
1071
|
+
if (!result.ok) {
|
|
1072
|
+
span.recordException(result.error);
|
|
1073
|
+
return result;
|
|
1139
1074
|
}
|
|
1075
|
+
if (options.onResume)
|
|
1076
|
+
await options.onResume();
|
|
1077
|
+
return { ok: true, output: result.record.data, record: result.record };
|
|
1140
1078
|
}, {
|
|
1141
1079
|
attributes: {
|
|
1142
1080
|
[SemanticInternalAttributes.STYLE_ICON]: "sessions",
|
|
@@ -2537,29 +2475,19 @@ const chatTurnNewUIMessagesKey = locals.create("chat.turnNewUIMessages");
|
|
|
2537
2475
|
const chatPendingSteerKey = locals.create("chat.pendingSteer");
|
|
2538
2476
|
/** @internal — IDs of messages that were successfully injected via prepareStep */
|
|
2539
2477
|
const chatInjectedMessageIdsKey = locals.create("chat.injectedMessageIds");
|
|
2540
|
-
|
|
2541
|
-
const
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
* Queue a chunk for accumulation into the response message (if it's a non-transient data part).
|
|
2554
|
-
* Called by `chat.response.write()` and `ChatWriter.write()`.
|
|
2555
|
-
* @internal
|
|
2556
|
-
*/
|
|
2557
|
-
function queueResponsePart(part) {
|
|
2558
|
-
if (!isNonTransientDataPart(part))
|
|
2559
|
-
return;
|
|
2560
|
-
const parts = locals.get(chatResponsePartsKey) ?? [];
|
|
2561
|
-
parts.push(part);
|
|
2562
|
-
locals.set(chatResponsePartsKey, parts);
|
|
2478
|
+
const chatManagedResponseKey = locals.create("chat.managedResponse");
|
|
2479
|
+
const chatManagedResponseActiveKey = locals.create("chat.managedResponseActive");
|
|
2480
|
+
const chatSteeringInjectionsKey = locals.create("chat.steeringInjections");
|
|
2481
|
+
function managedResponse() {
|
|
2482
|
+
let response = locals.get(chatManagedResponseKey);
|
|
2483
|
+
if (!response || response.isClosed) {
|
|
2484
|
+
response = new ManagedChatResponse(async (stream) => {
|
|
2485
|
+
const { waitUntilComplete } = chatStream.pipe(stream, { spanName: "managed chat response" });
|
|
2486
|
+
await waitUntilComplete();
|
|
2487
|
+
});
|
|
2488
|
+
locals.set(chatManagedResponseKey, response);
|
|
2489
|
+
}
|
|
2490
|
+
return response;
|
|
2563
2491
|
}
|
|
2564
2492
|
/**
|
|
2565
2493
|
* Check that no tool calls are in-flight in a step's content.
|
|
@@ -2911,6 +2839,8 @@ function modelFormOf(m, batch, injected) {
|
|
|
2911
2839
|
* @internal
|
|
2912
2840
|
*/
|
|
2913
2841
|
async function drainSteeringQueue(config, messages, steps, queueOverride) {
|
|
2842
|
+
if (steps.length === 0)
|
|
2843
|
+
managedResponse().beginGeneration();
|
|
2914
2844
|
const queue = queueOverride ?? locals.get(chatSteeringQueueKey);
|
|
2915
2845
|
if (!queue || queue.length === 0)
|
|
2916
2846
|
return EMPTY_DRAIN;
|
|
@@ -3043,31 +2973,24 @@ async function drainSteeringQueue(config, messages, steps, queueOverride) {
|
|
|
3043
2973
|
}
|
|
3044
2974
|
locals.set(chatPendingSteerKey, pendingSteer);
|
|
3045
2975
|
}
|
|
3046
|
-
// Write injection confirmation chunk to the stream so the frontend
|
|
3047
|
-
// knows which messages were injected and where in the response.
|
|
3048
2976
|
if (injected.length > 0) {
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
await waitUntilComplete();
|
|
3067
|
-
}
|
|
3068
|
-
catch {
|
|
3069
|
-
/* non-fatal — stream write failed */
|
|
3070
|
-
}
|
|
2977
|
+
const id = generateMessageId();
|
|
2978
|
+
const messageIds = claimedUIMessages.map((m) => m.id);
|
|
2979
|
+
const injections = locals.get(chatSteeringInjectionsKey) ?? new Map();
|
|
2980
|
+
injections.set(id, { id, messageIds, messages: injected });
|
|
2981
|
+
locals.set(chatSteeringInjectionsKey, injections);
|
|
2982
|
+
const response = managedResponse();
|
|
2983
|
+
// prepareStep can run ahead of the UI consumer. Admit the marker only
|
|
2984
|
+
// once the actual preceding finish-step chunk has entered this stream.
|
|
2985
|
+
response.afterStep(steps.length);
|
|
2986
|
+
response.writeData({
|
|
2987
|
+
type: PENDING_MESSAGE_INJECTED_TYPE,
|
|
2988
|
+
id,
|
|
2989
|
+
data: {
|
|
2990
|
+
messageIds,
|
|
2991
|
+
messages: claimedUIMessages.map((m) => ({ id: m.id, text: textOfUIMessage(m) })),
|
|
2992
|
+
},
|
|
2993
|
+
});
|
|
3071
2994
|
}
|
|
3072
2995
|
// Fire onInjected callback
|
|
3073
2996
|
if (config.onInjected && injected.length > 0) {
|
|
@@ -3368,7 +3291,7 @@ function buildManagedStreamTextOptions(options, config) {
|
|
|
3368
3291
|
* regenerate needs: without it a regenerated answer can call nothing.
|
|
3369
3292
|
*/
|
|
3370
3293
|
tools: (tools ?? agentTools),
|
|
3371
|
-
});
|
|
3294
|
+
}, false);
|
|
3372
3295
|
const promptSystem = locals.get(chatPromptKey)?.text;
|
|
3373
3296
|
/**
|
|
3374
3297
|
* Two managed sources conflict too, not only a caller against a managed one.
|
|
@@ -3395,6 +3318,9 @@ function buildManagedStreamTextOptions(options, config) {
|
|
|
3395
3318
|
return { ...(first ?? {}), ...(second ?? {}) };
|
|
3396
3319
|
};
|
|
3397
3320
|
}
|
|
3321
|
+
if (typeof managed.prepareStep === "function") {
|
|
3322
|
+
managed.prepareStep = retainStepMessages(managed.prepareStep);
|
|
3323
|
+
}
|
|
3398
3324
|
return { ...managed, ...rest };
|
|
3399
3325
|
}
|
|
3400
3326
|
/** @internal Test hook for {@link buildManagedStreamTextOptions}. */
|
|
@@ -3410,7 +3336,7 @@ function createBoundStreamText(registry, agentSystem, agentCacheControl, agentSy
|
|
|
3410
3336
|
}));
|
|
3411
3337
|
return bound;
|
|
3412
3338
|
}
|
|
3413
|
-
function toStreamTextOptions(options) {
|
|
3339
|
+
function toStreamTextOptions(options, retain = true) {
|
|
3414
3340
|
const agentDefaults = locals.get(chatAgentManagedConfigKey);
|
|
3415
3341
|
if (agentDefaults) {
|
|
3416
3342
|
options = {
|
|
@@ -3617,6 +3543,9 @@ function toStreamTextOptions(options) {
|
|
|
3617
3543
|
return resultMessages ? { messages: resultMessages } : undefined;
|
|
3618
3544
|
};
|
|
3619
3545
|
}
|
|
3546
|
+
if (retain && typeof result.prepareStep === "function") {
|
|
3547
|
+
result.prepareStep = retainStepMessages(result.prepareStep);
|
|
3548
|
+
}
|
|
3620
3549
|
return result;
|
|
3621
3550
|
}
|
|
3622
3551
|
const actionTurnBrand = Symbol.for("trigger.dev/chat/actionTurn");
|
|
@@ -3751,7 +3680,7 @@ function isReadableStream(value) {
|
|
|
3751
3680
|
* }
|
|
3752
3681
|
* ```
|
|
3753
3682
|
*/
|
|
3754
|
-
async function pipeChat(source, options) {
|
|
3683
|
+
async function pipeChat(source, options, capture) {
|
|
3755
3684
|
locals.set(chatPipeCountKey, (locals.get(chatPipeCountKey) ?? 0) + 1);
|
|
3756
3685
|
let stream;
|
|
3757
3686
|
if (isUIMessageStreamable(source)) {
|
|
@@ -3780,8 +3709,18 @@ async function pipeChat(source, options) {
|
|
|
3780
3709
|
// accepts opaque UIMessageStreamable / raw iterables whose element
|
|
3781
3710
|
// type we don't know at compile time. Cast — runtime behaviour is
|
|
3782
3711
|
// identical (bytes go to session.out either way).
|
|
3783
|
-
|
|
3784
|
-
|
|
3712
|
+
if (capture) {
|
|
3713
|
+
const response = managedResponse();
|
|
3714
|
+
response.seed(capture.originalMessages);
|
|
3715
|
+
await response.pipe(stream, options?.signal);
|
|
3716
|
+
}
|
|
3717
|
+
else {
|
|
3718
|
+
// Raw/manual pipes intentionally do not feed the managed capture. Never
|
|
3719
|
+
// leave injection events waiting for finish-step chunks it cannot see.
|
|
3720
|
+
managedResponse().useRawPipe();
|
|
3721
|
+
const { waitUntilComplete } = chatStream.pipe(stream, pipeOptions);
|
|
3722
|
+
await waitUntilComplete();
|
|
3723
|
+
}
|
|
3785
3724
|
}
|
|
3786
3725
|
function chatCustomAgent(options) {
|
|
3787
3726
|
const { clientDataSchema, onClientDataValidationError, clientDataReportErrorAt, run: userRun, ...restOptions } = options;
|
|
@@ -4034,11 +3973,13 @@ function chatAgent(options) {
|
|
|
4034
3973
|
if (!pending || pending.length === 0)
|
|
4035
3974
|
return [];
|
|
4036
3975
|
locals.set(chatPendingSteerKey, []);
|
|
4037
|
-
|
|
3976
|
+
const inlineIds = new Set(steeringMarkers(options?.response ? [options.response] : []).flatMap((m) => m.messageIds));
|
|
3977
|
+
const standalone = pending.filter((entry) => !inlineIds.has(entry.ui.id));
|
|
3978
|
+
for (const entry of standalone) {
|
|
4038
3979
|
accumulatedMessages.push(...entry.model);
|
|
4039
3980
|
options?.turnNew?.push(...entry.model);
|
|
4040
3981
|
}
|
|
4041
|
-
return
|
|
3982
|
+
return standalone;
|
|
4042
3983
|
};
|
|
4043
3984
|
// Accumulated UI messages for persistence. Mirrors the model accumulator
|
|
4044
3985
|
// but in frontend-friendly UIMessage format (with parts, id, etc.).
|
|
@@ -4132,7 +4073,10 @@ function chatAgent(options) {
|
|
|
4132
4073
|
});
|
|
4133
4074
|
const throughId = opts.messages.at(-1)?.id ?? "";
|
|
4134
4075
|
const queued = locals.get(chatBackgroundQueueKey) ?? [];
|
|
4135
|
-
const
|
|
4076
|
+
const transcriptIds = new Set(opts.messages.map((message) => message.id));
|
|
4077
|
+
const markerIds = new Set(steeringMarkers(opts.messages).map((marker) => marker.id));
|
|
4078
|
+
const steering = [...(locals.get(chatSteeringInjectionsKey)?.values() ?? [])].filter((entry) => markerIds.has(entry.id) || entry.messageIds.some((id) => transcriptIds.has(id)));
|
|
4079
|
+
const runtimeState = laneCompacted || laneInjections.length > 0 || queued.length > 0 || steering.length > 0
|
|
4136
4080
|
? {
|
|
4137
4081
|
v: 1,
|
|
4138
4082
|
...(laneCompacted
|
|
@@ -4145,6 +4089,7 @@ function chatAgent(options) {
|
|
|
4145
4089
|
: {}),
|
|
4146
4090
|
...(laneInjections.length > 0 ? { injections: laneInjections } : {}),
|
|
4147
4091
|
...(queued.length > 0 ? { queued: [...queued] } : {}),
|
|
4092
|
+
...(steering.length > 0 ? { steering } : {}),
|
|
4148
4093
|
}
|
|
4149
4094
|
: null;
|
|
4150
4095
|
if (runtimeState !== null || persistedStateSet) {
|
|
@@ -4614,6 +4559,8 @@ function chatAgent(options) {
|
|
|
4614
4559
|
}
|
|
4615
4560
|
try {
|
|
4616
4561
|
const bootRuntimeState = parseTranscriptRuntimeState(bootTranscriptState);
|
|
4562
|
+
locals.set(chatSteeringInjectionsKey, new Map((bootRuntimeState?.steering ?? []).map((entry) => [entry.id, entry])));
|
|
4563
|
+
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
4617
4564
|
const restored = await restoreModelLane(accumulatedUIMessages, bootRuntimeState, (messages) => toModelMessages(messages));
|
|
4618
4565
|
accumulatedMessages = restored.messages;
|
|
4619
4566
|
laneCompacted = restored.compacted;
|
|
@@ -5061,7 +5008,9 @@ function chatAgent(options) {
|
|
|
5061
5008
|
locals.set(chatCompactionStateKey, undefined);
|
|
5062
5009
|
locals.set(chatSteeringQueueKey, []);
|
|
5063
5010
|
locals.set(chatPendingBackgroundKey, []);
|
|
5064
|
-
locals.
|
|
5011
|
+
await locals.get(chatManagedResponseKey)?.close();
|
|
5012
|
+
locals.set(chatManagedResponseKey, undefined);
|
|
5013
|
+
locals.set(chatManagedResponseActiveKey, true);
|
|
5065
5014
|
// NOTE: chatBackgroundQueueKey is NOT reset here — messages injected
|
|
5066
5015
|
// by deferred work from the previous turn's onTurnComplete need to
|
|
5067
5016
|
// survive into the next turn. The queue is drained before run().
|
|
@@ -5225,7 +5174,7 @@ function chatAgent(options) {
|
|
|
5225
5174
|
if (actionOverride) {
|
|
5226
5175
|
locals.set(chatOverrideMessagesKey, undefined);
|
|
5227
5176
|
accumulatedUIMessages = [...actionOverride];
|
|
5228
|
-
accumulatedMessages = await toModelMessages(actionOverride);
|
|
5177
|
+
accumulatedMessages = await toModelMessages(actionOverride, actionOverride);
|
|
5229
5178
|
laneCompacted = false;
|
|
5230
5179
|
laneInjections = [];
|
|
5231
5180
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
@@ -5383,7 +5332,7 @@ function chatAgent(options) {
|
|
|
5383
5332
|
accumulatedUIMessages[accumulatedUIMessages.length - 1].role !== "user") {
|
|
5384
5333
|
accumulatedUIMessages.pop();
|
|
5385
5334
|
}
|
|
5386
|
-
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5335
|
+
accumulatedMessages = await toModelMessages(accumulatedUIMessages, accumulatedUIMessages);
|
|
5387
5336
|
laneCompacted = false;
|
|
5388
5337
|
laneInjections = [];
|
|
5389
5338
|
}
|
|
@@ -5435,7 +5384,7 @@ function chatAgent(options) {
|
|
|
5435
5384
|
}
|
|
5436
5385
|
if (!inPlace) {
|
|
5437
5386
|
logger.warn("chat.agent: replaced message not found at the model lane tail; reconverting the lane");
|
|
5438
|
-
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5387
|
+
accumulatedMessages = await toModelMessages(accumulatedUIMessages, accumulatedUIMessages);
|
|
5439
5388
|
laneCompacted = false;
|
|
5440
5389
|
laneInjections = [];
|
|
5441
5390
|
}
|
|
@@ -5652,7 +5601,7 @@ function chatAgent(options) {
|
|
|
5652
5601
|
if (turnStartOverride) {
|
|
5653
5602
|
locals.set(chatOverrideMessagesKey, undefined);
|
|
5654
5603
|
accumulatedUIMessages = [...turnStartOverride];
|
|
5655
|
-
accumulatedMessages = await toModelMessages(turnStartOverride);
|
|
5604
|
+
accumulatedMessages = await toModelMessages(turnStartOverride, turnStartOverride);
|
|
5656
5605
|
laneCompacted = false;
|
|
5657
5606
|
laneInjections = [];
|
|
5658
5607
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
@@ -5732,6 +5681,7 @@ function chatAgent(options) {
|
|
|
5732
5681
|
capturedResponseMessage = lastUI;
|
|
5733
5682
|
capturedFinishReason = "stop";
|
|
5734
5683
|
}
|
|
5684
|
+
managedResponse().seed(accumulatedUIMessages);
|
|
5735
5685
|
// Don't call userRun. Don't pipe. Skip directly
|
|
5736
5686
|
// to the post-turn flow below.
|
|
5737
5687
|
}
|
|
@@ -5791,7 +5741,7 @@ function chatAgent(options) {
|
|
|
5791
5741
|
await pipeChat(tapUIMessageChunks(uiStream, turnBufferedChunks), {
|
|
5792
5742
|
signal: combinedSignal,
|
|
5793
5743
|
spanName: "stream response",
|
|
5794
|
-
});
|
|
5744
|
+
}, { originalMessages: isActionTurn ? undefined : accumulatedUIMessages });
|
|
5795
5745
|
}
|
|
5796
5746
|
}
|
|
5797
5747
|
catch (error) {
|
|
@@ -5819,6 +5769,10 @@ function chatAgent(options) {
|
|
|
5819
5769
|
new Promise((r) => setTimeout(r, 2_000)),
|
|
5820
5770
|
]);
|
|
5821
5771
|
}
|
|
5772
|
+
capturedResponseMessage =
|
|
5773
|
+
(await locals.get(chatManagedResponseKey)?.snapshot()) ?? capturedResponseMessage;
|
|
5774
|
+
if (capturedResponseMessage)
|
|
5775
|
+
capturedPartialResponse = capturedResponseMessage;
|
|
5822
5776
|
// Capture token usage from the streamText result (if available).
|
|
5823
5777
|
// totalUsage is a PromiseLike that resolves after the stream is consumed.
|
|
5824
5778
|
// Race with a 2s timeout — on stop-abort the AI SDK's totalUsage
|
|
@@ -5942,6 +5896,7 @@ function chatAgent(options) {
|
|
|
5942
5896
|
// branches below, so a turn that captured no response is covered.
|
|
5943
5897
|
const steerTailThisTurn = reconcilePendingSteer({
|
|
5944
5898
|
turnNew: turnNewModelMessages,
|
|
5899
|
+
response: capturedResponseMessage,
|
|
5945
5900
|
}).reduce((n, e) => n + e.model.length, 0) + reconcilePendingBackground();
|
|
5946
5901
|
// Append the assistant's response (partial or complete) to the accumulator.
|
|
5947
5902
|
// The onFinish callback fires even on abort/stop, so partial responses
|
|
@@ -5964,15 +5919,6 @@ function chatAgent(options) {
|
|
|
5964
5919
|
id: generateMessageId(),
|
|
5965
5920
|
};
|
|
5966
5921
|
}
|
|
5967
|
-
// Append any non-transient data parts queued via chat.response or writer.write()
|
|
5968
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
5969
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
5970
|
-
capturedResponseMessage = {
|
|
5971
|
-
...capturedResponseMessage,
|
|
5972
|
-
parts: [...capturedResponseMessage.parts, ...queuedParts],
|
|
5973
|
-
};
|
|
5974
|
-
locals.set(chatResponsePartsKey, []);
|
|
5975
|
-
}
|
|
5976
5922
|
const responseHasContent = capturedResponseMessage.parts.some((part) => part.type !== "step-start");
|
|
5977
5923
|
if (responseHasContent) {
|
|
5978
5924
|
// Tool-approval continuations: the AI SDK reuses the trailing
|
|
@@ -6034,7 +5980,7 @@ function chatAgent(options) {
|
|
|
6034
5980
|
locals.set(chatHandoverSplicedRunKey, undefined);
|
|
6035
5981
|
if (!ok) {
|
|
6036
5982
|
logger.warn("chat.agent: replaced response not found at the model lane tail; reconverting the lane");
|
|
6037
|
-
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5983
|
+
accumulatedMessages = await toModelMessages(accumulatedUIMessages, accumulatedUIMessages);
|
|
6038
5984
|
laneCompacted = false;
|
|
6039
5985
|
laneInjections = [];
|
|
6040
5986
|
}
|
|
@@ -6052,22 +5998,6 @@ function chatAgent(options) {
|
|
|
6052
5998
|
responseWasSkipped = true;
|
|
6053
5999
|
}
|
|
6054
6000
|
}
|
|
6055
|
-
// If there's no captured response (manual pipe mode) but there are
|
|
6056
|
-
// queued data parts, create a minimal response message to hold them.
|
|
6057
|
-
if (!capturedResponseMessage) {
|
|
6058
|
-
const remainingParts = locals.get(chatResponsePartsKey);
|
|
6059
|
-
if (remainingParts && remainingParts.length > 0) {
|
|
6060
|
-
capturedResponseMessage = {
|
|
6061
|
-
id: generateMessageId(),
|
|
6062
|
-
role: "assistant",
|
|
6063
|
-
parts: [...remainingParts],
|
|
6064
|
-
};
|
|
6065
|
-
locals.set(chatResponsePartsKey, []);
|
|
6066
|
-
accumulatedUIMessages.push(capturedResponseMessage);
|
|
6067
|
-
turnNewUIMessages.push(capturedResponseMessage);
|
|
6068
|
-
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
6069
|
-
}
|
|
6070
|
-
}
|
|
6071
6001
|
if (capturedResponseMessage) {
|
|
6072
6002
|
responseCommitted = true;
|
|
6073
6003
|
capturedPartialResponse = capturedResponseMessage;
|
|
@@ -6229,6 +6159,8 @@ function chatAgent(options) {
|
|
|
6229
6159
|
totalUsage: cumulativeUsage,
|
|
6230
6160
|
finishReason: capturedFinishReason,
|
|
6231
6161
|
};
|
|
6162
|
+
const beforeHookRevision = locals.get(chatManagedResponseKey)?.revision ?? 0;
|
|
6163
|
+
let beforeHookHistoryEdited = false;
|
|
6232
6164
|
// Fire onBeforeTurnComplete — stream is still open so the hook
|
|
6233
6165
|
// can write custom chunks to the frontend (e.g. compaction progress).
|
|
6234
6166
|
if (onBeforeTurnComplete) {
|
|
@@ -6239,9 +6171,10 @@ function chatAgent(options) {
|
|
|
6239
6171
|
// Check if the hook replaced messages (compaction or chat.history)
|
|
6240
6172
|
const override = locals.get(chatOverrideMessagesKey);
|
|
6241
6173
|
if (override) {
|
|
6174
|
+
beforeHookHistoryEdited = true;
|
|
6242
6175
|
locals.set(chatOverrideMessagesKey, undefined);
|
|
6243
6176
|
accumulatedUIMessages = [...override];
|
|
6244
|
-
accumulatedMessages = await toModelMessages(override);
|
|
6177
|
+
accumulatedMessages = await toModelMessages(override, override);
|
|
6245
6178
|
laneCompacted = false;
|
|
6246
6179
|
laneInjections = [];
|
|
6247
6180
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
@@ -6258,35 +6191,33 @@ function chatAgent(options) {
|
|
|
6258
6191
|
},
|
|
6259
6192
|
});
|
|
6260
6193
|
}
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
turnCompleteEvent.responseMessage = capturedResponseMessage;
|
|
6194
|
+
const editedResponse = beforeHookHistoryEdited && capturedResponseMessage
|
|
6195
|
+
? accumulatedUIMessages.find((message) => message.id === capturedResponseMessage?.id)
|
|
6196
|
+
: undefined;
|
|
6197
|
+
const finalManagedResponse = await locals
|
|
6198
|
+
.get(chatManagedResponseKey)
|
|
6199
|
+
?.snapshot(editedResponse
|
|
6200
|
+
? { message: editedResponse, from: beforeHookRevision }
|
|
6201
|
+
: undefined);
|
|
6202
|
+
if (finalManagedResponse?.parts.some((part) => part.type !== "step-start")) {
|
|
6203
|
+
const idx = accumulatedUIMessages.findIndex((m) => m.id === finalManagedResponse.id);
|
|
6204
|
+
const finalized = (wasStopped ? cleanupAbortedParts(finalManagedResponse) : finalManagedResponse);
|
|
6205
|
+
if (idx !== -1 || responseWasSkipped || !capturedResponseMessage) {
|
|
6206
|
+
if (idx !== -1)
|
|
6207
|
+
accumulatedUIMessages[idx] = finalized;
|
|
6208
|
+
else
|
|
6209
|
+
accumulatedUIMessages.push(finalized);
|
|
6210
|
+
const deltaIdx = turnNewUIMessages.findIndex((m) => m.id === finalized.id);
|
|
6211
|
+
if (deltaIdx !== -1)
|
|
6212
|
+
turnNewUIMessages[deltaIdx] = finalized;
|
|
6213
|
+
else
|
|
6214
|
+
turnNewUIMessages.push(finalized);
|
|
6215
|
+
capturedResponseMessage = finalized;
|
|
6216
|
+
capturedPartialResponse = finalized;
|
|
6217
|
+
turnCompleteEvent.responseMessage = finalized;
|
|
6286
6218
|
turnCompleteEvent.uiMessages = accumulatedUIMessages;
|
|
6287
6219
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
6288
6220
|
}
|
|
6289
|
-
locals.set(chatResponsePartsKey, []);
|
|
6290
6221
|
}
|
|
6291
6222
|
settleRecoveredTurn(currentWirePayload);
|
|
6292
6223
|
// Write turn-complete control chunk — closes the frontend stream.
|
|
@@ -6303,7 +6234,7 @@ function chatAgent(options) {
|
|
|
6303
6234
|
if (turnCompleteOverride) {
|
|
6304
6235
|
locals.set(chatOverrideMessagesKey, undefined);
|
|
6305
6236
|
accumulatedUIMessages = [...turnCompleteOverride];
|
|
6306
|
-
accumulatedMessages = await toModelMessages(turnCompleteOverride);
|
|
6237
|
+
accumulatedMessages = await toModelMessages(turnCompleteOverride, turnCompleteOverride);
|
|
6307
6238
|
laneCompacted = false;
|
|
6308
6239
|
laneInjections = [];
|
|
6309
6240
|
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
@@ -6539,7 +6470,8 @@ function chatAgent(options) {
|
|
|
6539
6470
|
!accumulatedUIMessages.some((m) => m.id === erroredWireMessage.id)
|
|
6540
6471
|
? [...accumulatedUIMessages, erroredWireMessage]
|
|
6541
6472
|
: accumulatedUIMessages;
|
|
6542
|
-
let partialResponse =
|
|
6473
|
+
let partialResponse = (await locals.get(chatManagedResponseKey)?.snapshot()) ??
|
|
6474
|
+
capturedPartialResponse ??
|
|
6543
6475
|
(await assemblePartialFromChunks(turnBufferedChunks));
|
|
6544
6476
|
if (partialResponse) {
|
|
6545
6477
|
partialResponse = cleanupAbortedParts(partialResponse);
|
|
@@ -6547,23 +6479,16 @@ function chatAgent(options) {
|
|
|
6547
6479
|
let partialIdx = partialResponse?.id
|
|
6548
6480
|
? erroredUIMessages.findIndex((m) => m.id === partialResponse.id)
|
|
6549
6481
|
: -1;
|
|
6550
|
-
if (partialResponse &&
|
|
6482
|
+
if (partialResponse &&
|
|
6483
|
+
capturedPartialResponse === undefined &&
|
|
6484
|
+
!locals.get(chatManagedResponseKey)?.continues(partialResponse.id) &&
|
|
6485
|
+
partialIdx !== -1) {
|
|
6551
6486
|
partialResponse = undefined;
|
|
6552
6487
|
partialIdx = -1;
|
|
6553
6488
|
}
|
|
6554
6489
|
if (partialResponse && !partialResponse.id) {
|
|
6555
6490
|
partialResponse = { ...partialResponse, id: generateMessageId() };
|
|
6556
6491
|
}
|
|
6557
|
-
if (partialResponse && !responseCommitted) {
|
|
6558
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
6559
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
6560
|
-
partialResponse = {
|
|
6561
|
-
...partialResponse,
|
|
6562
|
-
parts: [...partialResponse.parts, ...queuedParts],
|
|
6563
|
-
};
|
|
6564
|
-
locals.set(chatResponsePartsKey, []);
|
|
6565
|
-
}
|
|
6566
|
-
}
|
|
6567
6492
|
const includePartial = partialResponse != null && !responseCommitted;
|
|
6568
6493
|
// What the stream left behind, by content. After `onTurnComplete` the
|
|
6569
6494
|
// partial is still unfinished only if the message under its id is
|
|
@@ -6596,7 +6521,7 @@ function chatAgent(options) {
|
|
|
6596
6521
|
};
|
|
6597
6522
|
let erroredNewUIMessages = buildErroredNew();
|
|
6598
6523
|
let erroredNewModelMessages = [];
|
|
6599
|
-
const reconciledSteer = reconcilePendingSteer();
|
|
6524
|
+
const reconciledSteer = reconcilePendingSteer({ response: partialResponse });
|
|
6600
6525
|
const backgroundTailThisTurn = reconcilePendingBackground();
|
|
6601
6526
|
if (!responseCommitted) {
|
|
6602
6527
|
try {
|
|
@@ -6607,14 +6532,7 @@ function chatAgent(options) {
|
|
|
6607
6532
|
* the model received it (what `prepare` produced), matching the
|
|
6608
6533
|
* lane. The wire message and partial are converted as before.
|
|
6609
6534
|
*/
|
|
6610
|
-
|
|
6611
|
-
for (const m of erroredNewUIMessages) {
|
|
6612
|
-
const recorded = steerModelById.get(m.id);
|
|
6613
|
-
if (recorded)
|
|
6614
|
-
erroredNewModelMessages.push(...recorded);
|
|
6615
|
-
else
|
|
6616
|
-
erroredNewModelMessages.push(...(await toModelMessages([stripProviderMetadata(m)])));
|
|
6617
|
-
}
|
|
6535
|
+
erroredNewModelMessages = await toModelMessages(erroredNewUIMessages.map(stripProviderMetadata));
|
|
6618
6536
|
}
|
|
6619
6537
|
if (erroredUIMessagesWithPartial !== accumulatedUIMessages) {
|
|
6620
6538
|
if (partialIdx === -1) {
|
|
@@ -6626,7 +6544,7 @@ function chatAgent(options) {
|
|
|
6626
6544
|
backgroundTailThisTurn);
|
|
6627
6545
|
if (!ok) {
|
|
6628
6546
|
logger.warn("chat.agent: replaced partial not found at the model lane tail; reconverting the lane");
|
|
6629
|
-
accumulatedMessages = await toModelMessages(erroredUIMessagesWithPartial);
|
|
6547
|
+
accumulatedMessages = await toModelMessages(erroredUIMessagesWithPartial, erroredUIMessagesWithPartial);
|
|
6630
6548
|
laneCompacted = false;
|
|
6631
6549
|
laneInjections = [];
|
|
6632
6550
|
}
|
|
@@ -6683,7 +6601,7 @@ function chatAgent(options) {
|
|
|
6683
6601
|
// Convert first: a rejected conversion (a tool's `toModelOutput`
|
|
6684
6602
|
// can throw) must leave every lane on the history it had.
|
|
6685
6603
|
const overrideUIMessages = [...errorTurnOverride];
|
|
6686
|
-
const overrideModelMessages = await toModelMessages(errorTurnOverride);
|
|
6604
|
+
const overrideModelMessages = await toModelMessages(errorTurnOverride, errorTurnOverride);
|
|
6687
6605
|
erroredUIMessagesWithPartial = overrideUIMessages;
|
|
6688
6606
|
accumulatedUIMessages = overrideUIMessages;
|
|
6689
6607
|
accumulatedMessages = overrideModelMessages;
|
|
@@ -6776,6 +6694,11 @@ function chatAgent(options) {
|
|
|
6776
6694
|
}
|
|
6777
6695
|
finally {
|
|
6778
6696
|
turnMsgSub?.off();
|
|
6697
|
+
locals.set(chatManagedResponseActiveKey, false);
|
|
6698
|
+
await locals
|
|
6699
|
+
.get(chatManagedResponseKey)
|
|
6700
|
+
?.close()
|
|
6701
|
+
.catch(() => { });
|
|
6779
6702
|
}
|
|
6780
6703
|
}
|
|
6781
6704
|
}
|
|
@@ -7745,7 +7668,7 @@ async function pipeChatAndCapture(source, options) {
|
|
|
7745
7668
|
await pipeChat(tappedStream, {
|
|
7746
7669
|
signal: options?.signal,
|
|
7747
7670
|
spanName: options?.spanName ?? "stream response",
|
|
7748
|
-
});
|
|
7671
|
+
}, { originalMessages: options?.originalMessages });
|
|
7749
7672
|
// The pipe can drain cleanly on a stop — the source stream just ends
|
|
7750
7673
|
// early — so classify by the signal rather than relying on a throw.
|
|
7751
7674
|
if (options?.signal?.aborted) {
|
|
@@ -7771,6 +7694,9 @@ async function pipeChatAndCapture(source, options) {
|
|
|
7771
7694
|
if (!captured && bufferedChunks.length > 0) {
|
|
7772
7695
|
captured = await assemblePartialFromChunks(bufferedChunks);
|
|
7773
7696
|
}
|
|
7697
|
+
captured = (await locals.get(chatManagedResponseKey)?.snapshot()) ?? captured;
|
|
7698
|
+
if (!locals.get(chatTurnContextKey))
|
|
7699
|
+
await locals.get(chatManagedResponseKey)?.close();
|
|
7774
7700
|
return {
|
|
7775
7701
|
message: captured,
|
|
7776
7702
|
status,
|
|
@@ -7804,6 +7730,7 @@ class ChatMessageAccumulator {
|
|
|
7804
7730
|
_handoverRun;
|
|
7805
7731
|
_pendingMessages;
|
|
7806
7732
|
_steeringQueue = [];
|
|
7733
|
+
_pendingSteer = [];
|
|
7807
7734
|
constructor(options) {
|
|
7808
7735
|
this._compaction = options?.compaction;
|
|
7809
7736
|
this._pendingMessages = options?.pendingMessages;
|
|
@@ -7876,6 +7803,19 @@ class ChatMessageAccumulator {
|
|
|
7876
7803
|
return { isFinal: signal.isFinal, skipped: false };
|
|
7877
7804
|
}
|
|
7878
7805
|
async addResponse(response) {
|
|
7806
|
+
const inlineIds = new Set(steeringMarkers([response]).flatMap((m) => m.messageIds));
|
|
7807
|
+
for (const entry of this._pendingSteer.splice(0)) {
|
|
7808
|
+
if (!inlineIds.has(entry.ui.id))
|
|
7809
|
+
continue;
|
|
7810
|
+
// absorbSteering is public and updates context immediately. Move just
|
|
7811
|
+
// those exact appended objects into the response's chronological run;
|
|
7812
|
+
// never rebuild a possibly compacted lane from the UI transcript.
|
|
7813
|
+
for (const message of entry.model) {
|
|
7814
|
+
const index = this.modelMessages.lastIndexOf(message);
|
|
7815
|
+
if (index !== -1)
|
|
7816
|
+
this.modelMessages.splice(index, 1);
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7879
7819
|
if (!response.id) {
|
|
7880
7820
|
response = { ...response, id: generateMessageId() };
|
|
7881
7821
|
}
|
|
@@ -7952,7 +7892,10 @@ class ChatMessageAccumulator {
|
|
|
7952
7892
|
this.uiMessages.push(...fresh);
|
|
7953
7893
|
// Record what the model received. Only when the whole batch is new is
|
|
7954
7894
|
// `injected` known to describe exactly these messages.
|
|
7955
|
-
|
|
7895
|
+
const model = injected && fresh.length === claimed.length ? injected : await toModelMessages(fresh);
|
|
7896
|
+
this.modelMessages.push(...model);
|
|
7897
|
+
for (const ui of fresh)
|
|
7898
|
+
this._pendingSteer.push({ ui, model: modelFormOf(ui, fresh, model) });
|
|
7956
7899
|
}
|
|
7957
7900
|
/**
|
|
7958
7901
|
* Get and clear unconsumed steering messages.
|
|
@@ -7972,7 +7915,7 @@ class ChatMessageAccumulator {
|
|
|
7972
7915
|
const comp = this._compaction;
|
|
7973
7916
|
const pm = this._pendingMessages;
|
|
7974
7917
|
const queue = this._steeringQueue;
|
|
7975
|
-
return async ({ messages, steps }) => {
|
|
7918
|
+
return retainStepMessages(async ({ messages, steps }) => {
|
|
7976
7919
|
let resultMessages;
|
|
7977
7920
|
// 1. Compaction
|
|
7978
7921
|
if (comp) {
|
|
@@ -7985,7 +7928,7 @@ class ChatMessageAccumulator {
|
|
|
7985
7928
|
}
|
|
7986
7929
|
}
|
|
7987
7930
|
// 2. Pending message injection
|
|
7988
|
-
if (pm
|
|
7931
|
+
if (pm) {
|
|
7989
7932
|
const { injected, claimed } = await drainSteeringQueue(pm, resultMessages ?? messages, steps, queue);
|
|
7990
7933
|
await this.absorbSteering(claimed, injected);
|
|
7991
7934
|
if (injected.length > 0) {
|
|
@@ -7993,7 +7936,7 @@ class ChatMessageAccumulator {
|
|
|
7993
7936
|
}
|
|
7994
7937
|
}
|
|
7995
7938
|
return resultMessages ? { messages: resultMessages } : undefined;
|
|
7996
|
-
};
|
|
7939
|
+
});
|
|
7997
7940
|
}
|
|
7998
7941
|
/**
|
|
7999
7942
|
* Run outer-loop compaction if needed. Call after adding the response
|
|
@@ -8305,7 +8248,9 @@ function createChatSession(payload, options) {
|
|
|
8305
8248
|
// Reset stop signal for this turn
|
|
8306
8249
|
stop.reset();
|
|
8307
8250
|
// Reset per-turn state
|
|
8308
|
-
locals.
|
|
8251
|
+
await locals.get(chatManagedResponseKey)?.close();
|
|
8252
|
+
locals.set(chatManagedResponseKey, undefined);
|
|
8253
|
+
locals.set(chatManagedResponseActiveKey, true);
|
|
8309
8254
|
// Set up steering queue and pending messages config in locals
|
|
8310
8255
|
// so toStreamTextOptions() auto-injects prepareStep for steering
|
|
8311
8256
|
const turnSteeringQueue = [];
|
|
@@ -8450,11 +8395,6 @@ function createChatSession(payload, options) {
|
|
|
8450
8395
|
if (captured.status === "error") {
|
|
8451
8396
|
if (captured.message) {
|
|
8452
8397
|
const partial = cleanupAbortedParts(captured.message);
|
|
8453
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
8454
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
8455
|
-
partial.parts = [...(partial.parts ?? []), ...queuedParts];
|
|
8456
|
-
locals.set(chatResponsePartsKey, []);
|
|
8457
|
-
}
|
|
8458
8398
|
await accumulator.addResponse(partial);
|
|
8459
8399
|
}
|
|
8460
8400
|
throw captured.error;
|
|
@@ -8470,26 +8410,8 @@ function createChatSession(payload, options) {
|
|
|
8470
8410
|
const cleaned = stop.signal.aborted && !runSignal.aborted
|
|
8471
8411
|
? cleanupAbortedParts(response)
|
|
8472
8412
|
: response;
|
|
8473
|
-
// Append any non-transient data parts queued via chat.response or writer.write()
|
|
8474
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
8475
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
8476
|
-
cleaned.parts = [...(cleaned.parts ?? []), ...queuedParts];
|
|
8477
|
-
locals.set(chatResponsePartsKey, []);
|
|
8478
|
-
}
|
|
8479
8413
|
await accumulator.addResponse(cleaned);
|
|
8480
8414
|
}
|
|
8481
|
-
else {
|
|
8482
|
-
// No response (manual pipe mode) but there are queued data parts
|
|
8483
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
8484
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
8485
|
-
await accumulator.addResponse({
|
|
8486
|
-
id: generateMessageId(),
|
|
8487
|
-
role: "assistant",
|
|
8488
|
-
parts: queuedParts,
|
|
8489
|
-
});
|
|
8490
|
-
locals.set(chatResponsePartsKey, []);
|
|
8491
|
-
}
|
|
8492
|
-
}
|
|
8493
8415
|
// Capture token usage from the streamText result. Race with a 2s
|
|
8494
8416
|
// timeout — on stop-abort the AI SDK's totalUsage promise can hang
|
|
8495
8417
|
// indefinitely, which would wedge the turn loop (same guard as
|
|
@@ -8580,15 +8502,6 @@ function createChatSession(payload, options) {
|
|
|
8580
8502
|
return response;
|
|
8581
8503
|
},
|
|
8582
8504
|
async addResponse(response) {
|
|
8583
|
-
// Append any non-transient data parts queued via chat.response or writer.write()
|
|
8584
|
-
const queuedParts = locals.get(chatResponsePartsKey);
|
|
8585
|
-
if (queuedParts && queuedParts.length > 0) {
|
|
8586
|
-
response = {
|
|
8587
|
-
...response,
|
|
8588
|
-
parts: [...(response.parts ?? []), ...queuedParts],
|
|
8589
|
-
};
|
|
8590
|
-
locals.set(chatResponsePartsKey, []);
|
|
8591
|
-
}
|
|
8592
8505
|
await accumulator.addResponse(response);
|
|
8593
8506
|
},
|
|
8594
8507
|
async done() {
|
|
@@ -8600,7 +8513,7 @@ function createChatSession(payload, options) {
|
|
|
8600
8513
|
const hasPending = !!sessionPendingMessages;
|
|
8601
8514
|
if (!hasCompaction && !hasPending)
|
|
8602
8515
|
return undefined;
|
|
8603
|
-
return async ({ messages: stepMsgs, steps, }) => {
|
|
8516
|
+
return retainStepMessages(async ({ messages: stepMsgs, steps, }) => {
|
|
8604
8517
|
let resultMessages;
|
|
8605
8518
|
if (sessionCompaction) {
|
|
8606
8519
|
const compactResult = await chatCompact(stepMsgs, steps, {
|
|
@@ -8619,7 +8532,7 @@ function createChatSession(payload, options) {
|
|
|
8619
8532
|
}
|
|
8620
8533
|
}
|
|
8621
8534
|
return resultMessages ? { messages: resultMessages } : undefined;
|
|
8622
|
-
};
|
|
8535
|
+
});
|
|
8623
8536
|
},
|
|
8624
8537
|
};
|
|
8625
8538
|
return { done: false, value: turnObj };
|
|
@@ -8887,6 +8800,10 @@ function createChatStartSessionAction(taskId, options) {
|
|
|
8887
8800
|
const clientDataMetadata = params.clientData !== undefined ? { metadata: params.clientData } : {};
|
|
8888
8801
|
const maxAttempts = params.triggerConfig?.maxAttempts ?? options?.triggerConfig?.maxAttempts;
|
|
8889
8802
|
const maxDuration = params.triggerConfig?.maxDuration ?? options?.triggerConfig?.maxDuration;
|
|
8803
|
+
const concurrency = params.triggerConfig?.concurrency !== undefined
|
|
8804
|
+
? params.triggerConfig.concurrency
|
|
8805
|
+
: options?.triggerConfig?.concurrency;
|
|
8806
|
+
const concurrencyKey = params.triggerConfig?.concurrencyKey ?? options?.triggerConfig?.concurrencyKey;
|
|
8890
8807
|
const idleTimeoutInSeconds = params.triggerConfig?.idleTimeoutInSeconds ?? options?.triggerConfig?.idleTimeoutInSeconds;
|
|
8891
8808
|
// Only `undefined` means "not supplied": a per-call `null` (opt out) has to beat a pinning
|
|
8892
8809
|
// action default, which neither truthiness nor `??` would allow.
|
|
@@ -8908,6 +8825,8 @@ function createChatStartSessionAction(taskId, options) {
|
|
|
8908
8825
|
...(options?.triggerConfig?.queue || params.triggerConfig?.queue
|
|
8909
8826
|
? { queue: params.triggerConfig?.queue ?? options?.triggerConfig?.queue }
|
|
8910
8827
|
: {}),
|
|
8828
|
+
...(concurrency !== undefined ? triggerConcurrencyBody(concurrency) : {}),
|
|
8829
|
+
...(concurrencyKey !== undefined ? { concurrencyKey } : {}),
|
|
8911
8830
|
tags,
|
|
8912
8831
|
...(maxAttempts !== undefined ? { maxAttempts } : {}),
|
|
8913
8832
|
...(maxDuration !== undefined ? { maxDuration } : {}),
|
|
@@ -9255,6 +9174,10 @@ export const chat = {
|
|
|
9255
9174
|
* @internal
|
|
9256
9175
|
*/
|
|
9257
9176
|
async function writeTurnCompleteChunk(_chatId, publicAccessToken) {
|
|
9177
|
+
locals.set(chatManagedResponseActiveKey, false);
|
|
9178
|
+
const response = locals.get(chatManagedResponseKey);
|
|
9179
|
+
if (response)
|
|
9180
|
+
await response.close();
|
|
9258
9181
|
const session = getChatSession();
|
|
9259
9182
|
// A handover-prepare boot claims the handover kinds so a signal arriving
|
|
9260
9183
|
// before `waitForHandover` attaches is not drained. Released here rather than
|