@trigger.dev/sdk 0.0.0-prerelease-20260908122921 → 0.0.0-prerelease-20260909071038
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-shared.d.ts +15 -0
- package/dist/commonjs/v3/ai-shared.js +35 -1
- package/dist/commonjs/v3/ai-shared.js.map +1 -1
- package/dist/commonjs/v3/ai.d.ts +76 -9
- package/dist/commonjs/v3/ai.js +316 -222
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.d.ts +8 -0
- package/dist/commonjs/v3/chat-client.js +199 -109
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-react.d.ts +54 -0
- package/dist/commonjs/v3/chat-react.js +83 -0
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +11 -0
- package/dist/commonjs/v3/chat.js +36 -27
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/chatSnapshotIo.d.ts +2 -0
- package/dist/commonjs/v3/chatSnapshotIo.js +174 -0
- package/dist/commonjs/v3/chatSnapshotIo.js.map +1 -0
- package/dist/commonjs/v3/test/index.d.ts +1 -0
- package/dist/commonjs/v3/test/index.js +3 -1
- package/dist/commonjs/v3/test/index.js.map +1 -1
- package/dist/commonjs/v3/test/mock-chat-agent.d.ts +4 -4
- package/dist/commonjs/v3/test/mock-chat-agent.js +25 -4
- package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/commonjs/v3/test/transcript-storage-tests.d.ts +40 -0
- package/dist/commonjs/v3/test/transcript-storage-tests.js +258 -0
- package/dist/commonjs/v3/test/transcript-storage-tests.js.map +1 -0
- package/dist/commonjs/v3/transcriptStorage.d.ts +229 -0
- package/dist/commonjs/v3/transcriptStorage.js +321 -0
- package/dist/commonjs/v3/transcriptStorage.js.map +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai-shared.d.ts +15 -0
- package/dist/esm/v3/ai-shared.js +33 -0
- package/dist/esm/v3/ai-shared.js.map +1 -1
- package/dist/esm/v3/ai.d.ts +76 -9
- package/dist/esm/v3/ai.js +306 -217
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.d.ts +8 -0
- package/dist/esm/v3/chat-client.js +200 -110
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-react.d.ts +54 -0
- package/dist/esm/v3/chat-react.js +81 -0
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +11 -0
- package/dist/esm/v3/chat.js +36 -27
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/chatSnapshotIo.d.ts +2 -0
- package/dist/esm/v3/chatSnapshotIo.js +166 -0
- package/dist/esm/v3/chatSnapshotIo.js.map +1 -0
- package/dist/esm/v3/test/index.d.ts +1 -0
- package/dist/esm/v3/test/index.js +1 -0
- package/dist/esm/v3/test/index.js.map +1 -1
- package/dist/esm/v3/test/mock-chat-agent.d.ts +4 -4
- package/dist/esm/v3/test/mock-chat-agent.js +25 -4
- package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/esm/v3/test/transcript-storage-tests.d.ts +40 -0
- package/dist/esm/v3/test/transcript-storage-tests.js +255 -0
- package/dist/esm/v3/test/transcript-storage-tests.js.map +1 -0
- package/dist/esm/v3/transcriptStorage.d.ts +229 -0
- package/dist/esm/v3/transcriptStorage.js +309 -0
- package/dist/esm/v3/transcriptStorage.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/docs/ai-chat/actions.mdx +2 -2
- package/docs/ai-chat/background-injection.mdx +2 -0
- package/docs/ai-chat/compaction.mdx +2 -0
- package/docs/ai-chat/frontend.mdx +2 -0
- package/docs/ai-chat/lifecycle-hooks.mdx +6 -2
- package/docs/ai-chat/patterns/database-persistence.mdx +6 -2
- package/docs/ai-chat/patterns/persistence-and-replay.mdx +34 -23
- package/docs/ai-chat/reference.mdx +56 -5
- package/docs/ai-chat/transcript-storage.mdx +233 -0
- package/docs/config/extensions/syncEnvVars.mdx +6 -0
- package/docs/deploy-environment-variables.mdx +23 -2
- package/package.json +2 -2
- package/skills/trigger-authoring-chat-agent/SKILL.md +3 -2
- package/skills/trigger-chat-agent-advanced/SKILL.md +5 -5
package/dist/commonjs/v3/ai.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.chat = exports.__buildManagedStreamTextOptionsForTests = exports.upsertIncomingMessage = exports.PENDING_MESSAGE_INJECTED_TYPE = exports.ai = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.__setWriteChatSnapshotImplForTests = __setWriteChatSnapshotImplForTests;
|
|
6
|
-
exports.__readChatSnapshotProductionPathForTests = __readChatSnapshotProductionPathForTests;
|
|
7
|
-
exports.__writeChatSnapshotProductionPathForTests = __writeChatSnapshotProductionPathForTests;
|
|
3
|
+
exports.chat = exports.__buildManagedStreamTextOptionsForTests = exports.upsertIncomingMessage = exports.PENDING_MESSAGE_INJECTED_TYPE = exports.ai = exports.snapshotTranscriptStorage = exports.reduceTranscriptChanges = exports.memoryTranscriptStorage = exports.defaultStorage = exports.__writeChatSnapshotProductionPathForTests = exports.__setWriteChatSnapshotImplForTests = exports.__setReadChatSnapshotImplForTests = exports.__readChatSnapshotProductionPathForTests = void 0;
|
|
4
|
+
exports.__setTranscriptStorageForTests = __setTranscriptStorageForTests;
|
|
8
5
|
exports.__mergeByIdReplaceWinsForTests = __mergeByIdReplaceWinsForTests;
|
|
9
6
|
exports.__setReplaySessionOutTailImplForTests = __setReplaySessionOutTailImplForTests;
|
|
10
7
|
exports.__replaySessionOutTailProductionPathForTests = __replaySessionOutTailProductionPathForTests;
|
|
@@ -19,6 +16,16 @@ const v3_1 = require("@trigger.dev/core/v3");
|
|
|
19
16
|
// ESM-only `ai@7` (see ../imports/ai-runtime.ts).
|
|
20
17
|
const api_1 = require("@opentelemetry/api");
|
|
21
18
|
const ai_runtime_js_1 = require("../imports/ai-runtime.js");
|
|
19
|
+
const transcriptStorage_js_1 = require("./transcriptStorage.js");
|
|
20
|
+
let transcriptStorageOverride;
|
|
21
|
+
/**
|
|
22
|
+
* Test-only override for the storage `chat.agent` persists through, so a
|
|
23
|
+
* test can capture the exact changesets the runtime produces.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
function __setTranscriptStorageForTests(storage) {
|
|
27
|
+
transcriptStorageOverride = storage;
|
|
28
|
+
}
|
|
22
29
|
const ai_shared_js_1 = require("./ai-shared.js");
|
|
23
30
|
Object.defineProperty(exports, "PENDING_MESSAGE_INJECTED_TYPE", { enumerable: true, get: function () { return ai_shared_js_1.PENDING_MESSAGE_INJECTED_TYPE; } });
|
|
24
31
|
Object.defineProperty(exports, "upsertIncomingMessage", { enumerable: true, get: function () { return ai_shared_js_1.upsertIncomingMessage; } });
|
|
@@ -123,182 +130,16 @@ async function findLatestSessionInCursor(chatId) {
|
|
|
123
130
|
}
|
|
124
131
|
return latestCursor;
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* - missing object (404 from the presigned GET — fresh session, never
|
|
137
|
-
* persisted)
|
|
138
|
-
* - presign failure (network/auth issue)
|
|
139
|
-
* - malformed JSON
|
|
140
|
-
* - version mismatch (forward-compat — older runtimes ignore newer blobs)
|
|
141
|
-
*
|
|
142
|
-
* Always swallows errors via `logger.warn`. The agent boot loop must stay
|
|
143
|
-
* available even if S3 hiccups; the worst case is replaying more of
|
|
144
|
-
* `session.out` than strictly necessary.
|
|
145
|
-
* @internal
|
|
146
|
-
*/
|
|
147
|
-
async function readChatSnapshot(sessionId) {
|
|
148
|
-
if (readChatSnapshotImpl) {
|
|
149
|
-
return (await readChatSnapshotImpl(sessionId)) ?? undefined;
|
|
150
|
-
}
|
|
151
|
-
const apiClient = v3_1.apiClientManager.clientOrThrow();
|
|
152
|
-
let presignedUrl;
|
|
153
|
-
try {
|
|
154
|
-
const resp = await apiClient.getChatSnapshotUrl(sessionId);
|
|
155
|
-
presignedUrl = resp.presignedUrl;
|
|
156
|
-
}
|
|
157
|
-
catch (error) {
|
|
158
|
-
v3_1.logger.warn("chat.agent: snapshot presign (read) failed; continuing without snapshot", {
|
|
159
|
-
error: error instanceof Error ? error.message : String(error),
|
|
160
|
-
sessionId,
|
|
161
|
-
});
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
let response;
|
|
165
|
-
try {
|
|
166
|
-
response = await fetch(presignedUrl, { method: "GET" });
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
v3_1.logger.warn("chat.agent: snapshot fetch failed; continuing without snapshot", {
|
|
170
|
-
error: error instanceof Error ? error.message : String(error),
|
|
171
|
-
sessionId,
|
|
172
|
-
});
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
if (response.status === 404) {
|
|
176
|
-
// First-ever boot for this session — no snapshot yet. Caller falls
|
|
177
|
-
// through to replay-only.
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
if (!response.ok) {
|
|
181
|
-
v3_1.logger.warn("chat.agent: snapshot fetch returned non-OK; continuing without snapshot", {
|
|
182
|
-
status: response.status,
|
|
183
|
-
sessionId,
|
|
184
|
-
});
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
let parsed;
|
|
188
|
-
try {
|
|
189
|
-
parsed = await response.json();
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
v3_1.logger.warn("chat.agent: snapshot JSON parse failed; continuing without snapshot", {
|
|
193
|
-
error: error instanceof Error ? error.message : String(error),
|
|
194
|
-
sessionId,
|
|
195
|
-
});
|
|
196
|
-
return undefined;
|
|
197
|
-
}
|
|
198
|
-
if (!parsed || typeof parsed !== "object")
|
|
199
|
-
return undefined;
|
|
200
|
-
const candidate = parsed;
|
|
201
|
-
if (candidate.version !== 1 || !Array.isArray(candidate.messages)) {
|
|
202
|
-
v3_1.logger.warn("chat.agent: snapshot version/shape mismatch; ignoring", {
|
|
203
|
-
version: candidate.version,
|
|
204
|
-
sessionId,
|
|
205
|
-
});
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
return candidate;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Persist the snapshot for a session. Awaited by callers immediately after
|
|
212
|
-
* `onTurnComplete` — the agent may suspend right after this point, and
|
|
213
|
-
* fire-and-forget promises don't reliably complete on suspend.
|
|
214
|
-
*
|
|
215
|
-
* Errors are swallowed via `logger.warn`. A failed write means the next
|
|
216
|
-
* boot replays slightly more of `session.out` (back to the previous
|
|
217
|
-
* snapshot's cursor) instead of failing — the conversation stays
|
|
218
|
-
* coherent, only the boot path does marginally more work.
|
|
219
|
-
* @internal
|
|
220
|
-
*/
|
|
221
|
-
async function writeChatSnapshot(sessionId, snapshot) {
|
|
222
|
-
if (writeChatSnapshotImpl) {
|
|
223
|
-
await writeChatSnapshotImpl(sessionId, snapshot);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const apiClient = v3_1.apiClientManager.clientOrThrow();
|
|
227
|
-
let presignedUrl;
|
|
228
|
-
try {
|
|
229
|
-
const resp = await apiClient.createChatSnapshotUploadUrl(sessionId);
|
|
230
|
-
presignedUrl = resp.presignedUrl;
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
233
|
-
v3_1.logger.warn("chat.agent: snapshot presign (write) failed; next run will replay further", {
|
|
234
|
-
error: error instanceof Error ? error.message : String(error),
|
|
235
|
-
sessionId,
|
|
236
|
-
});
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
let response;
|
|
240
|
-
try {
|
|
241
|
-
response = await fetch(presignedUrl, {
|
|
242
|
-
method: "PUT",
|
|
243
|
-
headers: { "content-type": "application/json" },
|
|
244
|
-
body: JSON.stringify(snapshot),
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
v3_1.logger.warn("chat.agent: snapshot upload failed; next run will replay further", {
|
|
249
|
-
error: error instanceof Error ? error.message : String(error),
|
|
250
|
-
sessionId,
|
|
251
|
-
});
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
v3_1.logger.warn("chat.agent: snapshot upload returned non-OK; next run will replay further", {
|
|
256
|
-
status: response.status,
|
|
257
|
-
sessionId,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Test-only entry point that bypasses `__setReadChatSnapshotImplForTests`
|
|
263
|
-
* and reaches the real `apiClient.getPayloadUrl` + `fetch` + JSON-parse path.
|
|
264
|
-
* Used by `chat-snapshot.test.ts` to verify 404 / 500 / malformed JSON /
|
|
265
|
-
* version-mismatch / network-error behavior end-to-end. Tests mock global
|
|
266
|
-
* `fetch` and the api-client config; this wrapper lets them drive the
|
|
267
|
-
* production code without the override hook short-circuiting.
|
|
268
|
-
*
|
|
269
|
-
* Not part of the public API. The `__` prefix and `ForTests` suffix mirror
|
|
270
|
-
* the override-hook setters above.
|
|
271
|
-
* @internal
|
|
272
|
-
*/
|
|
273
|
-
async function __readChatSnapshotProductionPathForTests(sessionId) {
|
|
274
|
-
const saved = readChatSnapshotImpl;
|
|
275
|
-
readChatSnapshotImpl = undefined;
|
|
276
|
-
try {
|
|
277
|
-
return await readChatSnapshot(sessionId);
|
|
278
|
-
}
|
|
279
|
-
finally {
|
|
280
|
-
readChatSnapshotImpl = saved;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Test-only entry point that bypasses `__setWriteChatSnapshotImplForTests`
|
|
285
|
-
* and reaches the real `apiClient.createUploadPayloadUrl` + `fetch` PUT
|
|
286
|
-
* path. Pairs with `__readChatSnapshotProductionPathForTests` — see that
|
|
287
|
-
* function's note for the rationale.
|
|
288
|
-
*
|
|
289
|
-
* Not part of the public API.
|
|
290
|
-
* @internal
|
|
291
|
-
*/
|
|
292
|
-
async function __writeChatSnapshotProductionPathForTests(sessionId, snapshot) {
|
|
293
|
-
const saved = writeChatSnapshotImpl;
|
|
294
|
-
writeChatSnapshotImpl = undefined;
|
|
295
|
-
try {
|
|
296
|
-
await writeChatSnapshot(sessionId, snapshot);
|
|
297
|
-
}
|
|
298
|
-
finally {
|
|
299
|
-
writeChatSnapshotImpl = saved;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
133
|
+
var chatSnapshotIo_js_1 = require("./chatSnapshotIo.js");
|
|
134
|
+
Object.defineProperty(exports, "__readChatSnapshotProductionPathForTests", { enumerable: true, get: function () { return chatSnapshotIo_js_1.__readChatSnapshotProductionPathForTests; } });
|
|
135
|
+
Object.defineProperty(exports, "__setReadChatSnapshotImplForTests", { enumerable: true, get: function () { return chatSnapshotIo_js_1.__setReadChatSnapshotImplForTests; } });
|
|
136
|
+
Object.defineProperty(exports, "__setWriteChatSnapshotImplForTests", { enumerable: true, get: function () { return chatSnapshotIo_js_1.__setWriteChatSnapshotImplForTests; } });
|
|
137
|
+
Object.defineProperty(exports, "__writeChatSnapshotProductionPathForTests", { enumerable: true, get: function () { return chatSnapshotIo_js_1.__writeChatSnapshotProductionPathForTests; } });
|
|
138
|
+
var transcriptStorage_js_2 = require("./transcriptStorage.js");
|
|
139
|
+
Object.defineProperty(exports, "defaultStorage", { enumerable: true, get: function () { return transcriptStorage_js_2.defaultStorage; } });
|
|
140
|
+
Object.defineProperty(exports, "memoryTranscriptStorage", { enumerable: true, get: function () { return transcriptStorage_js_2.memoryTranscriptStorage; } });
|
|
141
|
+
Object.defineProperty(exports, "reduceTranscriptChanges", { enumerable: true, get: function () { return transcriptStorage_js_2.reduceTranscriptChanges; } });
|
|
142
|
+
Object.defineProperty(exports, "snapshotTranscriptStorage", { enumerable: true, get: function () { return transcriptStorage_js_2.snapshotTranscriptStorage; } });
|
|
302
143
|
/**
|
|
303
144
|
* Merge two `UIMessage[]` lists by `id`, with the second list winning on
|
|
304
145
|
* collision. Used at run boot to combine the snapshot's persisted history
|
|
@@ -1964,6 +1805,12 @@ function spliceHandoverPartial(modelMessages, uiMessages, signal) {
|
|
|
1964
1805
|
* @internal
|
|
1965
1806
|
*/
|
|
1966
1807
|
const chatBackgroundQueueKey = locals_js_1.locals.create("chat.backgroundQueue");
|
|
1808
|
+
/**
|
|
1809
|
+
* Background injections a step-boundary drain handed to the model this turn,
|
|
1810
|
+
* with the transcript message they followed. Reconciled into the model lane
|
|
1811
|
+
* and the persisted injections once the turn's response is in.
|
|
1812
|
+
*/
|
|
1813
|
+
const chatPendingBackgroundKey = locals_js_1.locals.create("chat.pendingBackground");
|
|
1967
1814
|
/**
|
|
1968
1815
|
* System-role context injected mid-conversation, held for the instructions lane.
|
|
1969
1816
|
*
|
|
@@ -3625,6 +3472,12 @@ function toStreamTextOptions(options) {
|
|
|
3625
3472
|
if (bgQueue && bgQueue.length > 0) {
|
|
3626
3473
|
const injected = bgQueue.splice(0); // drain
|
|
3627
3474
|
resultMessages = [...(resultMessages ?? messages), ...injected];
|
|
3475
|
+
const pendingBackground = locals_js_1.locals.get(chatPendingBackgroundKey) ?? [];
|
|
3476
|
+
pendingBackground.push({
|
|
3477
|
+
afterId: locals_js_1.locals.get(chatCurrentUIMessagesKey)?.at(-1)?.id ?? "",
|
|
3478
|
+
messages: injected,
|
|
3479
|
+
});
|
|
3480
|
+
locals_js_1.locals.set(chatPendingBackgroundKey, pendingBackground);
|
|
3628
3481
|
}
|
|
3629
3482
|
return resultMessages ? { messages: resultMessages } : undefined;
|
|
3630
3483
|
};
|
|
@@ -3692,6 +3545,16 @@ function isUIMessageStreamable(value) {
|
|
|
3692
3545
|
"toUIMessageStream" in value &&
|
|
3693
3546
|
typeof value.toUIMessageStream === "function");
|
|
3694
3547
|
}
|
|
3548
|
+
const warnedHydrateMessagesDeprecated = new Set();
|
|
3549
|
+
function warnHydrateMessagesDeprecatedOnce(agentId) {
|
|
3550
|
+
if (warnedHydrateMessagesDeprecated.has(agentId))
|
|
3551
|
+
return;
|
|
3552
|
+
warnedHydrateMessagesDeprecated.add(agentId);
|
|
3553
|
+
console.warn(`[chat.agent] \`hydrateMessages\` on "${agentId}" is deprecated. Give the agent a transcript ` +
|
|
3554
|
+
"storage instead: `save` receives every change to the conversation and `loadContext` " +
|
|
3555
|
+
"lets the application own the model's context, with crash recovery and durable " +
|
|
3556
|
+
"compaction that `hydrateMessages` never had.");
|
|
3557
|
+
}
|
|
3695
3558
|
let warnedMissingOnAction = false;
|
|
3696
3559
|
function warnMissingOnActionOnce() {
|
|
3697
3560
|
if (warnedMissingOnAction)
|
|
@@ -3904,7 +3767,19 @@ function chatCustomAgent(options) {
|
|
|
3904
3767
|
return task;
|
|
3905
3768
|
}
|
|
3906
3769
|
function chatAgent(options) {
|
|
3907
|
-
const { run: userRun, clientDataSchema, onBoot, onRecoveryBoot, onPreload, onChatStart, onValidateMessages, hydrateMessages, actionSchema, onAction, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, tools: toolsOption, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, oomMachine, registry: promptRegistry, system: agentSystem, cacheControl: agentCacheControl, systemProviderOptions: agentSystemProviderOptions, versionSkew, ...restOptions } = options;
|
|
3770
|
+
const { run: userRun, clientDataSchema, onBoot, onRecoveryBoot, onPreload, onChatStart, onValidateMessages, hydrateMessages, storage, actionSchema, onAction, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, tools: toolsOption, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, oomMachine, registry: promptRegistry, system: agentSystem, cacheControl: agentCacheControl, systemProviderOptions: agentSystemProviderOptions, versionSkew, ...restOptions } = options;
|
|
3771
|
+
if (hydrateMessages) {
|
|
3772
|
+
if (storage) {
|
|
3773
|
+
throw new Error(`chat.agent: "${options.id}" sets both \`hydrateMessages\` and \`storage\`. ` +
|
|
3774
|
+
"`hydrateMessages` is deprecated and replaced by the storage: `save` receives every " +
|
|
3775
|
+
"change and `loadContext` on the storage owns the model's context. Remove `hydrateMessages`.");
|
|
3776
|
+
}
|
|
3777
|
+
if (typeof (transcriptStorageOverride ?? transcriptStorage_js_1.defaultStorage).loadContext === "function") {
|
|
3778
|
+
throw new Error(`chat.agent: "${options.id}" sets \`hydrateMessages\` and uses a transcript storage with ` +
|
|
3779
|
+
"`loadContext`. Both would own the model's context; keep one.");
|
|
3780
|
+
}
|
|
3781
|
+
warnHydrateMessagesDeprecatedOnce(options.id);
|
|
3782
|
+
}
|
|
3908
3783
|
const parseClientData = clientDataSchema ? (0, v3_1.getSchemaParseFn)(clientDataSchema) : undefined;
|
|
3909
3784
|
const parseAction = actionSchema ? (0, v3_1.getSchemaParseFn)(actionSchema) : undefined;
|
|
3910
3785
|
// chat.agent does not expose generic retry options (see docstring on
|
|
@@ -3987,6 +3862,24 @@ function chatAgent(options) {
|
|
|
3987
3862
|
// durable snapshot + `session.out` replay (or `hydrateMessages` if
|
|
3988
3863
|
// registered) — the wire is delta-only now, no longer a seed.
|
|
3989
3864
|
let accumulatedMessages = [];
|
|
3865
|
+
/**
|
|
3866
|
+
* Give the model accumulator the background injections a step-boundary
|
|
3867
|
+
* drain handed to the model this turn, and record them for persistence.
|
|
3868
|
+
* Returns how many model messages were appended.
|
|
3869
|
+
*/
|
|
3870
|
+
const reconcilePendingBackground = () => {
|
|
3871
|
+
const pending = locals_js_1.locals.get(chatPendingBackgroundKey);
|
|
3872
|
+
if (!pending || pending.length === 0)
|
|
3873
|
+
return 0;
|
|
3874
|
+
locals_js_1.locals.set(chatPendingBackgroundKey, []);
|
|
3875
|
+
let appended = 0;
|
|
3876
|
+
for (const entry of pending) {
|
|
3877
|
+
accumulatedMessages.push(...entry.messages);
|
|
3878
|
+
laneInjections.push(entry);
|
|
3879
|
+
appended += entry.messages.length;
|
|
3880
|
+
}
|
|
3881
|
+
return appended;
|
|
3882
|
+
};
|
|
3990
3883
|
/**
|
|
3991
3884
|
* Give the model accumulator the steering messages a drain consumed,
|
|
3992
3885
|
* in the form the model actually received. Appended, never reconverted
|
|
@@ -4022,7 +3915,41 @@ function chatAgent(options) {
|
|
|
4022
3915
|
// collectively cost ~600ms on every first-message TTFC. Both reads
|
|
4023
3916
|
// swallow errors internally; the agent stays available either way.
|
|
4024
3917
|
const sessionIdForSnapshot = payload.sessionId ?? payload.chatId;
|
|
3918
|
+
const transcriptStorage = storage ??
|
|
3919
|
+
transcriptStorageOverride ??
|
|
3920
|
+
transcriptStorage_js_1.defaultStorage;
|
|
3921
|
+
const storageLoadContext = transcriptStorage.loadContext?.bind(transcriptStorage);
|
|
3922
|
+
/**
|
|
3923
|
+
* Who supplies the model's context each turn: the deprecated
|
|
3924
|
+
* `hydrateMessages` hook, the storage's `loadContext`, or (undefined)
|
|
3925
|
+
* the runtime's own transcript.
|
|
3926
|
+
*/
|
|
3927
|
+
const loadContextHook = hydrateMessages
|
|
3928
|
+
? (event) => hydrateMessages(event)
|
|
3929
|
+
: storageLoadContext
|
|
3930
|
+
? (event) => storageLoadContext({ chatId: event.chatId, clientData: event.clientData }, event)
|
|
3931
|
+
: undefined;
|
|
3932
|
+
let transcriptShadow = (0, transcriptStorage_js_1.createTranscriptShadow)([]);
|
|
3933
|
+
let bootTranscriptState = null;
|
|
3934
|
+
/**
|
|
3935
|
+
* True while the model lane holds a compaction summary, so it cannot be
|
|
3936
|
+
* rebuilt from the transcript and has to be persisted as state. Reset
|
|
3937
|
+
* wherever the lane is reconverted from the UI lane.
|
|
3938
|
+
*/
|
|
3939
|
+
let laneCompacted = false;
|
|
3940
|
+
/** Conversational `chat.inject` messages in the lane, anchored to the transcript. */
|
|
3941
|
+
let laneInjections = [];
|
|
3942
|
+
let persistedStateSet = false;
|
|
4025
3943
|
let bootSnapshot;
|
|
3944
|
+
let bootClientData = payload.metadata;
|
|
3945
|
+
if (parseClientData) {
|
|
3946
|
+
try {
|
|
3947
|
+
bootClientData = await parseClientData(payload.metadata);
|
|
3948
|
+
}
|
|
3949
|
+
catch {
|
|
3950
|
+
bootClientData = payload.metadata;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
4026
3953
|
/**
|
|
4027
3954
|
* The `lastOutEventId` the most recent snapshot carried.
|
|
4028
3955
|
*
|
|
@@ -4032,31 +3959,95 @@ function chatAgent(options) {
|
|
|
4032
3959
|
* keeps an action's write cursor-neutral.
|
|
4033
3960
|
*/
|
|
4034
3961
|
let lastSnapshotOutEventId;
|
|
3962
|
+
const storageTrigger = (trigger) => trigger === "regenerate-message"
|
|
3963
|
+
? "regenerate-message"
|
|
3964
|
+
: trigger === "action" || trigger === "action-turn"
|
|
3965
|
+
? "action"
|
|
3966
|
+
: "submit-message";
|
|
3967
|
+
/**
|
|
3968
|
+
* Hand the runtime's view of the transcript to the storage as a
|
|
3969
|
+
* changeset: the diff against what was last saved, plus the cursors the
|
|
3970
|
+
* next boot resumes from. The shadow only advances when the save
|
|
3971
|
+
* succeeds, so a failed save is folded into the next changeset.
|
|
3972
|
+
*/
|
|
3973
|
+
/** The runtime's opaque state as of the last save; carried on every changeset's transcript. */
|
|
3974
|
+
let transcriptState = null;
|
|
3975
|
+
const saveTranscript = async (opts) => {
|
|
3976
|
+
const { changes, shadow } = (0, transcriptStorage_js_1.diffTranscript)(transcriptShadow, opts.messages, {
|
|
3977
|
+
nonFinalIds: opts.nonFinalIds,
|
|
3978
|
+
});
|
|
3979
|
+
const throughId = opts.messages.at(-1)?.id ?? "";
|
|
3980
|
+
const queued = locals_js_1.locals.get(chatBackgroundQueueKey) ?? [];
|
|
3981
|
+
const runtimeState = laneCompacted || laneInjections.length > 0 || queued.length > 0
|
|
3982
|
+
? {
|
|
3983
|
+
v: 1,
|
|
3984
|
+
...(laneCompacted
|
|
3985
|
+
? {
|
|
3986
|
+
compaction: {
|
|
3987
|
+
modelMessages: accumulatedMessages,
|
|
3988
|
+
throughId,
|
|
3989
|
+
fingerprint: (0, transcriptStorage_js_1.prefixFingerprint)(shadow, throughId),
|
|
3990
|
+
},
|
|
3991
|
+
}
|
|
3992
|
+
: {}),
|
|
3993
|
+
...(laneInjections.length > 0 ? { injections: laneInjections } : {}),
|
|
3994
|
+
...(queued.length > 0 ? { queued: [...queued] } : {}),
|
|
3995
|
+
}
|
|
3996
|
+
: null;
|
|
3997
|
+
if (runtimeState !== null || persistedStateSet) {
|
|
3998
|
+
changes.push({ op: "state", value: runtimeState });
|
|
3999
|
+
}
|
|
4000
|
+
transcriptState = runtimeState;
|
|
4001
|
+
const inCursor = chatInputRouter().resumeFloor();
|
|
4002
|
+
await transcriptStorage.save({
|
|
4003
|
+
chatId: payload.chatId,
|
|
4004
|
+
clientData: opts.clientData,
|
|
4005
|
+
turn: opts.turn,
|
|
4006
|
+
trigger: opts.trigger,
|
|
4007
|
+
runId: ctx.run.id,
|
|
4008
|
+
ctx,
|
|
4009
|
+
}, {
|
|
4010
|
+
reason: opts.reason,
|
|
4011
|
+
changes,
|
|
4012
|
+
transcript: {
|
|
4013
|
+
entries: opts.messages.map((message) => ({
|
|
4014
|
+
id: message.id,
|
|
4015
|
+
final: !shadow.nonFinal.has(message.id),
|
|
4016
|
+
message,
|
|
4017
|
+
})),
|
|
4018
|
+
state: transcriptState,
|
|
4019
|
+
},
|
|
4020
|
+
cursors: {
|
|
4021
|
+
lastOutEventId: opts.lastOutEventId,
|
|
4022
|
+
lastInEventId: inCursor !== undefined ? String(inCursor) : undefined,
|
|
4023
|
+
},
|
|
4024
|
+
});
|
|
4025
|
+
transcriptShadow = shadow;
|
|
4026
|
+
persistedStateSet = runtimeState !== null;
|
|
4027
|
+
};
|
|
4035
4028
|
/**
|
|
4036
4029
|
* Persist the accumulator outside a turn.
|
|
4037
4030
|
*
|
|
4038
4031
|
* An action is not a turn, so it never reaches the turn-complete path where
|
|
4039
|
-
* the
|
|
4040
|
-
*
|
|
4041
|
-
* `onAction`. Both have to survive, and one write at the end of the action
|
|
4042
|
-
* covers both rather than writing twice for a regenerate that does both.
|
|
4032
|
+
* the transcript is normally saved, but a `chat.history` mutation changes
|
|
4033
|
+
* the conversation and has to survive the run ending.
|
|
4043
4034
|
*
|
|
4044
4035
|
* Cursor-neutral: an action has no turn cursor of its own, and writing
|
|
4045
4036
|
* `undefined` would drop the resume point the last turn established and make
|
|
4046
4037
|
* the next boot replay from further back.
|
|
4047
4038
|
*/
|
|
4048
|
-
const writeSnapshotOutsideTurn = async (reason) => {
|
|
4039
|
+
const writeSnapshotOutsideTurn = async (reason, turnContext) => {
|
|
4049
4040
|
if (hydrateMessages)
|
|
4050
4041
|
return;
|
|
4051
4042
|
try {
|
|
4052
4043
|
await tracer_js_1.tracer.startActiveSpan("snapshot.write", async () => {
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
version: 1,
|
|
4056
|
-
savedAt: Date.now(),
|
|
4044
|
+
await saveTranscript({
|
|
4045
|
+
reason: "action",
|
|
4057
4046
|
messages: accumulatedUIMessages,
|
|
4047
|
+
turn: turnContext.turn,
|
|
4048
|
+
trigger: "action",
|
|
4049
|
+
clientData: turnContext.clientData,
|
|
4058
4050
|
lastOutEventId: lastSnapshotOutEventId,
|
|
4059
|
-
lastInEventId: snapshotInCursor !== undefined ? String(snapshotInCursor) : undefined,
|
|
4060
4051
|
});
|
|
4061
4052
|
}, {
|
|
4062
4053
|
attributes: {
|
|
@@ -4090,7 +4081,7 @@ function chatAgent(options) {
|
|
|
4090
4081
|
// resume-cursor block reuses it instead of re-scanning.
|
|
4091
4082
|
let bootInCursor;
|
|
4092
4083
|
let bootInCursorResolved = false;
|
|
4093
|
-
if (
|
|
4084
|
+
if (couldHavePriorState) {
|
|
4094
4085
|
// Single parent span for the whole boot read phase — snapshot
|
|
4095
4086
|
// read, session.out replay, session.in replay. Per-phase timing
|
|
4096
4087
|
// + result counts are attributes on the span.
|
|
@@ -4098,18 +4089,33 @@ function chatAgent(options) {
|
|
|
4098
4089
|
// snapshot read
|
|
4099
4090
|
const snapStart = Date.now();
|
|
4100
4091
|
try {
|
|
4101
|
-
|
|
4092
|
+
const loaded = hydrateMessages
|
|
4093
|
+
? undefined
|
|
4094
|
+
: await transcriptStorage.load({
|
|
4095
|
+
chatId: payload.chatId,
|
|
4096
|
+
clientData: bootClientData,
|
|
4097
|
+
});
|
|
4098
|
+
if (loaded) {
|
|
4099
|
+
transcriptShadow = (0, transcriptStorage_js_1.createTranscriptShadow)(loaded.messages, new Set(loaded.nonFinalIds ?? []));
|
|
4100
|
+
bootTranscriptState = loaded.state;
|
|
4101
|
+
transcriptState = loaded.state ?? null;
|
|
4102
|
+
persistedStateSet = loaded.state !== null && loaded.state !== undefined;
|
|
4103
|
+
bootSnapshot = {
|
|
4104
|
+
messages: loaded.messages,
|
|
4105
|
+
lastOutEventId: loaded.cursors?.lastOutEventId,
|
|
4106
|
+
lastInEventId: loaded.cursors?.lastInEventId,
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4102
4109
|
}
|
|
4103
4110
|
catch (error) {
|
|
4104
|
-
|
|
4105
|
-
// is just belt-and-suspenders against tracer/span errors.
|
|
4106
|
-
v3_1.logger.warn("chat.agent: snapshot read failed; continuing without snapshot", {
|
|
4111
|
+
v3_1.logger.warn("chat.agent: transcript load failed; continuing from the stream tail", {
|
|
4107
4112
|
error: error instanceof Error ? error.message : String(error),
|
|
4108
4113
|
sessionId: sessionIdForSnapshot,
|
|
4109
4114
|
});
|
|
4110
4115
|
}
|
|
4111
4116
|
bootSpan.setAttribute("chat.boot.snapshot.durationMs", Date.now() - snapStart);
|
|
4112
|
-
bootSpan.setAttribute("chat.boot.snapshot.present",
|
|
4117
|
+
bootSpan.setAttribute("chat.boot.snapshot.present", bootSnapshot !== undefined &&
|
|
4118
|
+
(bootSnapshot.messages.length > 0 || bootSnapshot.lastOutEventId !== undefined));
|
|
4113
4119
|
bootSpan.setAttribute("chat.boot.snapshot.messageCount", bootSnapshot?.messages?.length ?? 0);
|
|
4114
4120
|
// Seed the trim chain from the snapshot's `lastOutEventId` (the SSE
|
|
4115
4121
|
// id of the previous turn's `turn-complete` control record). The
|
|
@@ -4222,7 +4228,7 @@ function chatAgent(options) {
|
|
|
4222
4228
|
resuming: Boolean(payload.continuation) || ctx.attempt.number > 1,
|
|
4223
4229
|
});
|
|
4224
4230
|
// ── Recovery boot + chain reconstruction ────────────────────────
|
|
4225
|
-
|
|
4231
|
+
{
|
|
4226
4232
|
const settledMessages = mergeByIdReplaceWins(bootSnapshot?.messages ?? [], replayedSettled);
|
|
4227
4233
|
const inFlightUsers = replayedInTail.map((r) => r.message);
|
|
4228
4234
|
const partialAssistant = replayedPartial;
|
|
@@ -4375,7 +4381,8 @@ function chatAgent(options) {
|
|
|
4375
4381
|
// path where wire-borne UIMessage[] still seeds the accumulator,
|
|
4376
4382
|
// and it's safe because the route handler isn't subject to the
|
|
4377
4383
|
// `/in/append` 512 KiB cap.
|
|
4378
|
-
if (
|
|
4384
|
+
if (!loadContextHook &&
|
|
4385
|
+
accumulatedUIMessages.length === 0 &&
|
|
4379
4386
|
payload.trigger === "handover-prepare" &&
|
|
4380
4387
|
Array.isArray(payload.headStartMessages) &&
|
|
4381
4388
|
payload.headStartMessages.length > 0) {
|
|
@@ -4404,7 +4411,17 @@ function chatAgent(options) {
|
|
|
4404
4411
|
}
|
|
4405
4412
|
}
|
|
4406
4413
|
try {
|
|
4407
|
-
|
|
4414
|
+
const bootRuntimeState = (0, transcriptStorage_js_1.parseTranscriptRuntimeState)(bootTranscriptState);
|
|
4415
|
+
const restored = await (0, transcriptStorage_js_1.restoreModelLane)(accumulatedUIMessages, bootRuntimeState, (messages) => toModelMessages(messages));
|
|
4416
|
+
accumulatedMessages = restored.messages;
|
|
4417
|
+
laneCompacted = restored.compacted;
|
|
4418
|
+
laneInjections = restored.injections;
|
|
4419
|
+
if (bootRuntimeState?.queued && bootRuntimeState.queued.length > 0) {
|
|
4420
|
+
locals_js_1.locals.set(chatBackgroundQueueKey, [
|
|
4421
|
+
...(locals_js_1.locals.get(chatBackgroundQueueKey) ?? []),
|
|
4422
|
+
...bootRuntimeState.queued,
|
|
4423
|
+
]);
|
|
4424
|
+
}
|
|
4408
4425
|
}
|
|
4409
4426
|
catch (error) {
|
|
4410
4427
|
v3_1.logger.warn("chat.agent: toModelMessages failed at boot; starting empty", {
|
|
@@ -4781,6 +4798,7 @@ function chatAgent(options) {
|
|
|
4781
4798
|
} // end else (no boot-injected first turn)
|
|
4782
4799
|
}
|
|
4783
4800
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
4801
|
+
let turnClientData = payload.metadata;
|
|
4784
4802
|
// Declared here so the finally can detach it — a handler leaked past
|
|
4785
4803
|
// its turn duplicates every mid-stream message into the shared buffer.
|
|
4786
4804
|
let turnMsgSub;
|
|
@@ -4802,6 +4820,7 @@ function chatAgent(options) {
|
|
|
4802
4820
|
// `cleanupAbortedParts` no longer has to be re-applied below.
|
|
4803
4821
|
const cleanedIncomingMessages = incomingMessages.map((msg) => msg.role === "assistant" ? cleanupAbortedParts(msg) : msg);
|
|
4804
4822
|
const clientData = (parseClientData ? await parseClientData(wireMetadata) : wireMetadata);
|
|
4823
|
+
turnClientData = clientData;
|
|
4805
4824
|
const lastUserMessage = extractLastUserMessageText(cleanedIncomingMessages);
|
|
4806
4825
|
// Actions are not turns. They use a different span name
|
|
4807
4826
|
// and don't carry a turn.number. Branched on at `isAction`.
|
|
@@ -4838,6 +4857,7 @@ function chatAgent(options) {
|
|
|
4838
4857
|
locals_js_1.locals.set(chatDeferKey, new Set());
|
|
4839
4858
|
locals_js_1.locals.set(chatCompactionStateKey, undefined);
|
|
4840
4859
|
locals_js_1.locals.set(chatSteeringQueueKey, []);
|
|
4860
|
+
locals_js_1.locals.set(chatPendingBackgroundKey, []);
|
|
4841
4861
|
locals_js_1.locals.set(chatResponsePartsKey, []);
|
|
4842
4862
|
// NOTE: chatBackgroundQueueKey is NOT reset here — messages injected
|
|
4843
4863
|
// by deferred work from the previous turn's onTurnComplete need to
|
|
@@ -4949,9 +4969,9 @@ function chatAgent(options) {
|
|
|
4949
4969
|
? await parseAction(currentWirePayload.action)
|
|
4950
4970
|
: currentWirePayload.action;
|
|
4951
4971
|
// Hydrate messages from backend if configured
|
|
4952
|
-
if (
|
|
4972
|
+
if (loadContextHook) {
|
|
4953
4973
|
const hydrated = await tracer_js_1.tracer.startActiveSpan("hydrateMessages()", async () => {
|
|
4954
|
-
return
|
|
4974
|
+
return loadContextHook({
|
|
4955
4975
|
chatId: currentWirePayload.chatId,
|
|
4956
4976
|
turn,
|
|
4957
4977
|
trigger: "action",
|
|
@@ -4971,6 +4991,8 @@ function chatAgent(options) {
|
|
|
4971
4991
|
});
|
|
4972
4992
|
accumulatedUIMessages = [...hydrated];
|
|
4973
4993
|
accumulatedMessages = await toModelMessages(hydrated);
|
|
4994
|
+
laneCompacted = false;
|
|
4995
|
+
laneInjections = [];
|
|
4974
4996
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
4975
4997
|
}
|
|
4976
4998
|
// Fire onAction — handler may mutate state via
|
|
@@ -5001,6 +5023,8 @@ function chatAgent(options) {
|
|
|
5001
5023
|
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
5002
5024
|
accumulatedUIMessages = [...actionOverride];
|
|
5003
5025
|
accumulatedMessages = await toModelMessages(actionOverride);
|
|
5026
|
+
laneCompacted = false;
|
|
5027
|
+
laneInjections = [];
|
|
5004
5028
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5005
5029
|
actionChangedHistory = true;
|
|
5006
5030
|
}
|
|
@@ -5023,7 +5047,7 @@ function chatAgent(options) {
|
|
|
5023
5047
|
// route handler's first-turn history to the hydrate hook as
|
|
5024
5048
|
// incoming messages instead (gated on the pending handover).
|
|
5025
5049
|
if (turn === 0 &&
|
|
5026
|
-
|
|
5050
|
+
loadContextHook &&
|
|
5027
5051
|
cleanedUIMessages.length === 0 &&
|
|
5028
5052
|
(locals_js_1.locals.get(chatHandoverPartialKey)?.length ?? 0) > 0 &&
|
|
5029
5053
|
Array.isArray(payload.headStartMessages) &&
|
|
@@ -5053,7 +5077,7 @@ function chatAgent(options) {
|
|
|
5053
5077
|
},
|
|
5054
5078
|
}));
|
|
5055
5079
|
}
|
|
5056
|
-
if (
|
|
5080
|
+
if (loadContextHook) {
|
|
5057
5081
|
// Snapshot the ids the accumulator knew BEFORE this
|
|
5058
5082
|
// turn ran — used below to decide whether an
|
|
5059
5083
|
// incoming wire message is genuinely new or just a
|
|
@@ -5071,7 +5095,7 @@ function chatAgent(options) {
|
|
|
5071
5095
|
// was always true for `submit-message` is now true for every
|
|
5072
5096
|
// trigger.
|
|
5073
5097
|
const hydrated = await tracer_js_1.tracer.startActiveSpan("hydrateMessages()", async () => {
|
|
5074
|
-
return
|
|
5098
|
+
return loadContextHook({
|
|
5075
5099
|
chatId: currentWirePayload.chatId,
|
|
5076
5100
|
turn,
|
|
5077
5101
|
trigger: currentWirePayload.trigger,
|
|
@@ -5110,6 +5134,8 @@ function chatAgent(options) {
|
|
|
5110
5134
|
}
|
|
5111
5135
|
accumulatedUIMessages = merged;
|
|
5112
5136
|
accumulatedMessages = await toModelMessages(merged);
|
|
5137
|
+
laneCompacted = false;
|
|
5138
|
+
laneInjections = [];
|
|
5113
5139
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5114
5140
|
// Track new messages for onTurnComplete.newUIMessages.
|
|
5115
5141
|
// Only push for genuinely new ids — HITL continuations
|
|
@@ -5155,6 +5181,8 @@ function chatAgent(options) {
|
|
|
5155
5181
|
accumulatedUIMessages.pop();
|
|
5156
5182
|
}
|
|
5157
5183
|
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5184
|
+
laneCompacted = false;
|
|
5185
|
+
laneInjections = [];
|
|
5158
5186
|
}
|
|
5159
5187
|
else if (cleanedUIMessages.length > 0) {
|
|
5160
5188
|
// Submit-message (and the special-cased
|
|
@@ -5205,6 +5233,8 @@ function chatAgent(options) {
|
|
|
5205
5233
|
if (!inPlace) {
|
|
5206
5234
|
v3_1.logger.warn("chat.agent: replaced message not found at the model lane tail; reconverting the lane");
|
|
5207
5235
|
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5236
|
+
laneCompacted = false;
|
|
5237
|
+
laneInjections = [];
|
|
5208
5238
|
}
|
|
5209
5239
|
}
|
|
5210
5240
|
else {
|
|
@@ -5290,7 +5320,7 @@ function chatAgent(options) {
|
|
|
5290
5320
|
// history rather than from the snapshot the edit replaced.
|
|
5291
5321
|
// The turn then does its own hooks, completion and snapshot.
|
|
5292
5322
|
if (actionChangedHistory) {
|
|
5293
|
-
await writeSnapshotOutsideTurn("action");
|
|
5323
|
+
await writeSnapshotOutsideTurn("action", { turn, clientData });
|
|
5294
5324
|
}
|
|
5295
5325
|
actionTurn = true;
|
|
5296
5326
|
}
|
|
@@ -5302,7 +5332,7 @@ function chatAgent(options) {
|
|
|
5302
5332
|
else {
|
|
5303
5333
|
msgSub?.off();
|
|
5304
5334
|
if (actionChangedHistory) {
|
|
5305
|
-
await writeSnapshotOutsideTurn("action");
|
|
5335
|
+
await writeSnapshotOutsideTurn("action", { turn, clientData });
|
|
5306
5336
|
}
|
|
5307
5337
|
await writeTurnCompleteChunk(currentWirePayload.chatId);
|
|
5308
5338
|
// Don't consume a turn iteration — actions aren't turns.
|
|
@@ -5400,6 +5430,8 @@ function chatAgent(options) {
|
|
|
5400
5430
|
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
5401
5431
|
accumulatedUIMessages = [...turnStartOverride];
|
|
5402
5432
|
accumulatedMessages = await toModelMessages(turnStartOverride);
|
|
5433
|
+
laneCompacted = false;
|
|
5434
|
+
laneInjections = [];
|
|
5403
5435
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5404
5436
|
}
|
|
5405
5437
|
}, {
|
|
@@ -5460,7 +5492,12 @@ function chatAgent(options) {
|
|
|
5460
5492
|
const lastAccumulated = accumulatedMessages[accumulatedMessages.length - 1];
|
|
5461
5493
|
const bgQueue = locals_js_1.locals.get(chatBackgroundQueueKey);
|
|
5462
5494
|
if (bgQueue && bgQueue.length > 0 && lastAccumulated?.role !== "tool") {
|
|
5463
|
-
|
|
5495
|
+
const injected = bgQueue.splice(0);
|
|
5496
|
+
accumulatedMessages.push(...injected);
|
|
5497
|
+
laneInjections.push({
|
|
5498
|
+
afterId: accumulatedUIMessages.at(-1)?.id ?? "",
|
|
5499
|
+
messages: injected,
|
|
5500
|
+
});
|
|
5464
5501
|
}
|
|
5465
5502
|
if (isHeadStartFinalTurn) {
|
|
5466
5503
|
// The synthesized partial UIMessage IS the response.
|
|
@@ -5621,6 +5658,8 @@ function chatAgent(options) {
|
|
|
5621
5658
|
locals_js_1.locals.set(chatPendingSteerKey, pending);
|
|
5622
5659
|
const pendingIds = new Set(pending.map((e) => e.ui.id));
|
|
5623
5660
|
accumulatedMessages = await toModelMessages(runOverride.filter((m) => !pendingIds.has(m.id)));
|
|
5661
|
+
laneCompacted = false;
|
|
5662
|
+
laneInjections = [];
|
|
5624
5663
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5625
5664
|
}
|
|
5626
5665
|
// Check if compaction set a model-only override (preserves UI messages).
|
|
@@ -5643,6 +5682,8 @@ function chatAgent(options) {
|
|
|
5643
5682
|
accumulatedMessages = taskCompactionConfig?.compactModelMessages
|
|
5644
5683
|
? await taskCompactionConfig.compactModelMessages(compactEvent)
|
|
5645
5684
|
: modelOnlyOverride;
|
|
5685
|
+
laneCompacted = true;
|
|
5686
|
+
laneInjections = [];
|
|
5646
5687
|
// Apply UI messages: callback or default (preserve all)
|
|
5647
5688
|
if (taskCompactionConfig?.compactUIMessages) {
|
|
5648
5689
|
accumulatedUIMessages = (await taskCompactionConfig.compactUIMessages(compactEvent));
|
|
@@ -5658,7 +5699,7 @@ function chatAgent(options) {
|
|
|
5658
5699
|
// branches below, so a turn that captured no response is covered.
|
|
5659
5700
|
const steerTailThisTurn = reconcilePendingSteer({
|
|
5660
5701
|
turnNew: turnNewModelMessages,
|
|
5661
|
-
}).reduce((n, e) => n + e.model.length, 0);
|
|
5702
|
+
}).reduce((n, e) => n + e.model.length, 0) + reconcilePendingBackground();
|
|
5662
5703
|
// Append the assistant's response (partial or complete) to the accumulator.
|
|
5663
5704
|
// The onFinish callback fires even on abort/stop, so partial responses
|
|
5664
5705
|
// from stopped generation are captured correctly.
|
|
@@ -5721,6 +5762,8 @@ function chatAgent(options) {
|
|
|
5721
5762
|
if (!ok) {
|
|
5722
5763
|
v3_1.logger.warn("chat.agent: replaced response not found at the model lane tail; reconverting the lane");
|
|
5723
5764
|
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
|
|
5765
|
+
laneCompacted = false;
|
|
5766
|
+
laneInjections = [];
|
|
5724
5767
|
}
|
|
5725
5768
|
}
|
|
5726
5769
|
else {
|
|
@@ -5829,6 +5872,8 @@ function chatAgent(options) {
|
|
|
5829
5872
|
],
|
|
5830
5873
|
},
|
|
5831
5874
|
];
|
|
5875
|
+
laneCompacted = true;
|
|
5876
|
+
laneInjections = [];
|
|
5832
5877
|
// UI messages: callback or default (preserve all)
|
|
5833
5878
|
if (outerCompaction.compactUIMessages) {
|
|
5834
5879
|
accumulatedUIMessages = (await outerCompaction.compactUIMessages(outerCompactEvent));
|
|
@@ -5918,6 +5963,8 @@ function chatAgent(options) {
|
|
|
5918
5963
|
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
5919
5964
|
accumulatedUIMessages = [...override];
|
|
5920
5965
|
accumulatedMessages = await toModelMessages(override);
|
|
5966
|
+
laneCompacted = false;
|
|
5967
|
+
laneInjections = [];
|
|
5921
5968
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5922
5969
|
// Update event so onTurnComplete sees compacted messages
|
|
5923
5970
|
turnCompleteEvent.messages = accumulatedMessages;
|
|
@@ -5964,6 +6011,8 @@ function chatAgent(options) {
|
|
|
5964
6011
|
locals_js_1.locals.set(chatOverrideMessagesKey, undefined);
|
|
5965
6012
|
accumulatedUIMessages = [...turnCompleteOverride];
|
|
5966
6013
|
accumulatedMessages = await toModelMessages(turnCompleteOverride);
|
|
6014
|
+
laneCompacted = false;
|
|
6015
|
+
laneInjections = [];
|
|
5967
6016
|
locals_js_1.locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
|
|
5968
6017
|
}
|
|
5969
6018
|
}, {
|
|
@@ -6017,15 +6066,18 @@ function chatAgent(options) {
|
|
|
6017
6066
|
if (!hydrateMessages) {
|
|
6018
6067
|
try {
|
|
6019
6068
|
await tracer_js_1.tracer.startActiveSpan("snapshot.write", async () => {
|
|
6020
|
-
const snapshotInCursor = chatInputRouter().resumeFloor();
|
|
6021
6069
|
lastSnapshotOutEventId =
|
|
6022
6070
|
turnCompleteResult?.lastEventId ?? lastSnapshotOutEventId;
|
|
6023
|
-
await
|
|
6024
|
-
|
|
6025
|
-
savedAt: Date.now(),
|
|
6071
|
+
await saveTranscript({
|
|
6072
|
+
reason: "turn-complete",
|
|
6026
6073
|
messages: accumulatedUIMessages,
|
|
6074
|
+
turn,
|
|
6075
|
+
trigger: storageTrigger(currentWirePayload.trigger),
|
|
6076
|
+
clientData,
|
|
6027
6077
|
lastOutEventId: lastSnapshotOutEventId,
|
|
6028
|
-
|
|
6078
|
+
nonFinalIds: wasStopped && capturedResponseMessage?.id
|
|
6079
|
+
? new Set([capturedResponseMessage.id])
|
|
6080
|
+
: undefined,
|
|
6029
6081
|
});
|
|
6030
6082
|
}, {
|
|
6031
6083
|
attributes: {
|
|
@@ -6246,6 +6298,7 @@ function chatAgent(options) {
|
|
|
6246
6298
|
let erroredNewUIMessages = buildErroredNew();
|
|
6247
6299
|
let erroredNewModelMessages = [];
|
|
6248
6300
|
const reconciledSteer = reconcilePendingSteer();
|
|
6301
|
+
const backgroundTailThisTurn = reconcilePendingBackground();
|
|
6249
6302
|
if (!responseCommitted) {
|
|
6250
6303
|
try {
|
|
6251
6304
|
if (erroredNewUIMessages.length > 0) {
|
|
@@ -6270,10 +6323,13 @@ function chatAgent(options) {
|
|
|
6270
6323
|
accumulatedMessages.push(...(await toModelMessages(appended.map((m) => stripProviderMetadata(m)))));
|
|
6271
6324
|
}
|
|
6272
6325
|
else {
|
|
6273
|
-
const ok = await replaceModelRun(accumulatedMessages, erroredUIMessages[partialIdx], partialResponse, reconciledSteer.reduce((n, e) => n + e.model.length, 0)
|
|
6326
|
+
const ok = await replaceModelRun(accumulatedMessages, erroredUIMessages[partialIdx], partialResponse, reconciledSteer.reduce((n, e) => n + e.model.length, 0) +
|
|
6327
|
+
backgroundTailThisTurn);
|
|
6274
6328
|
if (!ok) {
|
|
6275
6329
|
v3_1.logger.warn("chat.agent: replaced partial not found at the model lane tail; reconverting the lane");
|
|
6276
6330
|
accumulatedMessages = await toModelMessages(erroredUIMessagesWithPartial);
|
|
6331
|
+
laneCompacted = false;
|
|
6332
|
+
laneInjections = [];
|
|
6277
6333
|
}
|
|
6278
6334
|
}
|
|
6279
6335
|
accumulatedUIMessages = erroredUIMessagesWithPartial;
|
|
@@ -6337,13 +6393,14 @@ function chatAgent(options) {
|
|
|
6337
6393
|
// neither the snapshot nor the replayable `.in` tail.
|
|
6338
6394
|
if (!hydrateMessages) {
|
|
6339
6395
|
try {
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
version: 1,
|
|
6343
|
-
savedAt: Date.now(),
|
|
6396
|
+
await saveTranscript({
|
|
6397
|
+
reason: "turn-error",
|
|
6344
6398
|
messages: erroredUIMessagesWithPartial,
|
|
6345
|
-
|
|
6346
|
-
|
|
6399
|
+
turn,
|
|
6400
|
+
trigger: storageTrigger(currentWirePayload.trigger),
|
|
6401
|
+
clientData: turnClientData,
|
|
6402
|
+
lastOutEventId: lastSnapshotOutEventId,
|
|
6403
|
+
nonFinalIds: includePartial && partialResponse ? new Set([partialResponse.id]) : undefined,
|
|
6347
6404
|
});
|
|
6348
6405
|
}
|
|
6349
6406
|
catch (error) {
|
|
@@ -8660,6 +8717,41 @@ async function mintPublicTokenWithOverride(args) {
|
|
|
8660
8717
|
expirationTime: args.expirationTime,
|
|
8661
8718
|
});
|
|
8662
8719
|
}
|
|
8720
|
+
/**
|
|
8721
|
+
* Creates a server-side helper that reads a conversation from a transcript
|
|
8722
|
+
* storage, for rendering history before the chat connects. Works the same
|
|
8723
|
+
* for every storage, the platform default included, so the browser never
|
|
8724
|
+
* reads a store directly and the secret key stays on the server.
|
|
8725
|
+
*
|
|
8726
|
+
* Wrap it in a Next.js server action (or any server-side handler), scope it
|
|
8727
|
+
* to the authenticated user through `clientData`, and pass the result to
|
|
8728
|
+
* `useLoadTranscript` in the browser.
|
|
8729
|
+
*
|
|
8730
|
+
* @example
|
|
8731
|
+
* ```ts
|
|
8732
|
+
* // actions.ts
|
|
8733
|
+
* "use server";
|
|
8734
|
+
* import { chat, defaultStorage } from "@trigger.dev/sdk/ai";
|
|
8735
|
+
*
|
|
8736
|
+
* export const loadTranscript = chat.createLoadTranscriptAction(defaultStorage, { limit: 50 });
|
|
8737
|
+
* ```
|
|
8738
|
+
*/
|
|
8739
|
+
function createChatLoadTranscriptAction(storage, options) {
|
|
8740
|
+
return async (params) => {
|
|
8741
|
+
if (!params.chatId) {
|
|
8742
|
+
throw new Error("chat.createLoadTranscriptAction: params.chatId is required.");
|
|
8743
|
+
}
|
|
8744
|
+
if (options?.apiClient) {
|
|
8745
|
+
const { apiClient, ...rest } = options;
|
|
8746
|
+
return v3_1.apiClientManager.runWithConfig(apiClient, () => createChatLoadTranscriptAction(storage, rest)(params));
|
|
8747
|
+
}
|
|
8748
|
+
const limit = params.limit ?? options?.limit;
|
|
8749
|
+
return storage.load({ chatId: params.chatId, clientData: params.clientData }, {
|
|
8750
|
+
...(limit !== undefined ? { limit } : {}),
|
|
8751
|
+
...(params.before !== undefined ? { before: params.before } : {}),
|
|
8752
|
+
});
|
|
8753
|
+
};
|
|
8754
|
+
}
|
|
8663
8755
|
exports.chat = {
|
|
8664
8756
|
/** Create a chat agent. See {@link chatAgent}. */
|
|
8665
8757
|
agent: chatAgent,
|
|
@@ -8671,6 +8763,8 @@ exports.chat = {
|
|
|
8671
8763
|
withClientData,
|
|
8672
8764
|
/** Create a server-side helper for starting (or resuming) a Session for a chatId. See {@link createChatStartSessionAction}. */
|
|
8673
8765
|
createStartSessionAction: createChatStartSessionAction,
|
|
8766
|
+
/** Returns a server-side helper that reads a conversation from a transcript storage. */
|
|
8767
|
+
createLoadTranscriptAction: createChatLoadTranscriptAction,
|
|
8674
8768
|
/** Pipe a stream to the chat transport. See {@link pipeChat}. */
|
|
8675
8769
|
pipe: pipeChat,
|
|
8676
8770
|
/** Return from `onAction` to run a turn on the edited history. See {@link chatTurn}. */
|