@trigger.dev/sdk 4.5.0-rc.6 → 4.5.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/v3/ai.d.ts +171 -5
- package/dist/commonjs/v3/ai.js +309 -22
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +8 -0
- package/dist/commonjs/v3/chat-server.js +32 -10
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat-server.test.js +51 -0
- package/dist/commonjs/v3/chat-server.test.js.map +1 -1
- package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
- package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/commonjs/v3/sessions.d.ts +3 -2
- package/dist/commonjs/v3/sessions.js +3 -2
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +171 -5
- package/dist/esm/v3/ai.js +309 -22
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +8 -0
- package/dist/esm/v3/chat-server.js +32 -10
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat-server.test.js +51 -0
- package/dist/esm/v3/chat-server.test.js.map +1 -1
- package/dist/esm/v3/createStartSessionAction.test.js +30 -0
- package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/esm/v3/sessions.d.ts +3 -2
- package/dist/esm/v3/sessions.js +3 -2
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai/prompts.mdx +430 -0
- package/docs/ai-chat/actions.mdx +115 -0
- package/docs/ai-chat/anatomy.mdx +71 -0
- package/docs/ai-chat/backend.mdx +817 -0
- package/docs/ai-chat/background-injection.mdx +221 -0
- package/docs/ai-chat/changelog.mdx +850 -0
- package/docs/ai-chat/chat-local.mdx +174 -0
- package/docs/ai-chat/client-protocol.mdx +1081 -0
- package/docs/ai-chat/compaction.mdx +411 -0
- package/docs/ai-chat/custom-agents.mdx +364 -0
- package/docs/ai-chat/error-handling.mdx +415 -0
- package/docs/ai-chat/fast-starts.mdx +672 -0
- package/docs/ai-chat/frontend.mdx +580 -0
- package/docs/ai-chat/how-it-works.mdx +230 -0
- package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
- package/docs/ai-chat/mcp.mdx +101 -0
- package/docs/ai-chat/overview.mdx +90 -0
- package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
- package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
- package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
- package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
- package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
- package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
- package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
- package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
- package/docs/ai-chat/patterns/skills.mdx +221 -0
- package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
- package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
- package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
- package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
- package/docs/ai-chat/pending-messages.mdx +343 -0
- package/docs/ai-chat/prompt-caching.mdx +206 -0
- package/docs/ai-chat/quick-start.mdx +161 -0
- package/docs/ai-chat/reference.mdx +909 -0
- package/docs/ai-chat/server-chat.mdx +263 -0
- package/docs/ai-chat/sessions.mdx +333 -0
- package/docs/ai-chat/testing.mdx +682 -0
- package/docs/ai-chat/tools.mdx +191 -0
- package/docs/ai-chat/types.mdx +242 -0
- package/docs/ai-chat/upgrade-guide.mdx +515 -0
- package/docs/apikeys.mdx +54 -0
- package/docs/building-with-ai.mdx +261 -0
- package/docs/bulk-actions.mdx +49 -0
- package/docs/changelog.mdx +6 -0
- package/docs/cli-deploy-commands.mdx +9 -0
- package/docs/cli-dev-commands.mdx +9 -0
- package/docs/cli-dev.mdx +8 -0
- package/docs/cli-init-commands.mdx +58 -0
- package/docs/cli-introduction.mdx +25 -0
- package/docs/cli-list-profiles-commands.mdx +42 -0
- package/docs/cli-login-commands.mdx +33 -0
- package/docs/cli-logout-commands.mdx +33 -0
- package/docs/cli-preview-archive.mdx +59 -0
- package/docs/cli-promote-commands.mdx +9 -0
- package/docs/cli-switch.mdx +43 -0
- package/docs/cli-update-commands.mdx +42 -0
- package/docs/cli-whoami-commands.mdx +33 -0
- package/docs/community.mdx +6 -0
- package/docs/config/config-file.mdx +602 -0
- package/docs/config/extensions/additionalFiles.mdx +38 -0
- package/docs/config/extensions/additionalPackages.mdx +40 -0
- package/docs/config/extensions/aptGet.mdx +34 -0
- package/docs/config/extensions/audioWaveform.mdx +20 -0
- package/docs/config/extensions/custom.mdx +380 -0
- package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
- package/docs/config/extensions/esbuildPlugin.mdx +31 -0
- package/docs/config/extensions/ffmpeg.mdx +45 -0
- package/docs/config/extensions/lightpanda.mdx +56 -0
- package/docs/config/extensions/overview.mdx +67 -0
- package/docs/config/extensions/playwright.mdx +195 -0
- package/docs/config/extensions/prismaExtension.mdx +1014 -0
- package/docs/config/extensions/puppeteer.mdx +30 -0
- package/docs/config/extensions/pythonExtension.mdx +182 -0
- package/docs/config/extensions/syncEnvVars.mdx +291 -0
- package/docs/context.mdx +235 -0
- package/docs/database-connections.mdx +213 -0
- package/docs/deploy-environment-variables.mdx +435 -0
- package/docs/deployment/atomic-deployment.mdx +172 -0
- package/docs/deployment/overview.mdx +257 -0
- package/docs/deployment/preview-branches.mdx +224 -0
- package/docs/errors-retrying.mdx +379 -0
- package/docs/github-actions.mdx +222 -0
- package/docs/github-integration.mdx +136 -0
- package/docs/github-repo.mdx +8 -0
- package/docs/help-email.mdx +6 -0
- package/docs/help-slack.mdx +11 -0
- package/docs/hidden-tasks.mdx +56 -0
- package/docs/how-it-works.mdx +454 -0
- package/docs/how-to-reduce-your-spend.mdx +217 -0
- package/docs/idempotency.mdx +504 -0
- package/docs/introduction.mdx +223 -0
- package/docs/limits.mdx +241 -0
- package/docs/logging.mdx +195 -0
- package/docs/machines.mdx +952 -0
- package/docs/manual-setup.mdx +632 -0
- package/docs/mcp-agent-rules.mdx +41 -0
- package/docs/mcp-introduction.mdx +385 -0
- package/docs/mcp-tools.mdx +273 -0
- package/docs/migrating-from-v3.mdx +334 -0
- package/docs/observability/dashboards.mdx +102 -0
- package/docs/observability/query.mdx +585 -0
- package/docs/open-source-contributing.mdx +16 -0
- package/docs/open-source-self-hosting.mdx +541 -0
- package/docs/private-networking/aws-console-setup.mdx +304 -0
- package/docs/private-networking/overview.mdx +144 -0
- package/docs/private-networking/troubleshooting.mdx +78 -0
- package/docs/queue-concurrency.mdx +354 -0
- package/docs/quick-start.mdx +97 -0
- package/docs/realtime/auth.mdx +208 -0
- package/docs/realtime/backend/overview.mdx +45 -0
- package/docs/realtime/backend/streams.mdx +418 -0
- package/docs/realtime/backend/subscribe.mdx +225 -0
- package/docs/realtime/how-it-works.mdx +94 -0
- package/docs/realtime/overview.mdx +63 -0
- package/docs/realtime/react-hooks/overview.mdx +73 -0
- package/docs/realtime/react-hooks/streams.mdx +449 -0
- package/docs/realtime/react-hooks/subscribe.mdx +674 -0
- package/docs/realtime/react-hooks/swr.mdx +87 -0
- package/docs/realtime/react-hooks/triggering.mdx +194 -0
- package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
- package/docs/realtime/run-object.mdx +174 -0
- package/docs/replaying.mdx +72 -0
- package/docs/request-feature.mdx +6 -0
- package/docs/roadmap.mdx +6 -0
- package/docs/run-tests.mdx +20 -0
- package/docs/run-usage.mdx +113 -0
- package/docs/runs/heartbeats.mdx +38 -0
- package/docs/runs/max-duration.mdx +139 -0
- package/docs/runs/metadata.mdx +734 -0
- package/docs/runs/priority.mdx +31 -0
- package/docs/runs.mdx +396 -0
- package/docs/self-hosting/docker.mdx +458 -0
- package/docs/self-hosting/env/supervisor.mdx +74 -0
- package/docs/self-hosting/env/webapp.mdx +276 -0
- package/docs/self-hosting/kubernetes.mdx +601 -0
- package/docs/self-hosting/overview.mdx +108 -0
- package/docs/skills.mdx +85 -0
- package/docs/tags.mdx +120 -0
- package/docs/tasks/overview.mdx +697 -0
- package/docs/tasks/scheduled.mdx +382 -0
- package/docs/tasks/schemaTask.mdx +413 -0
- package/docs/tasks/streams.mdx +884 -0
- package/docs/triggering.mdx +1320 -0
- package/docs/troubleshooting-alerts.mdx +385 -0
- package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
- package/docs/troubleshooting-github-issues.mdx +6 -0
- package/docs/troubleshooting-uptime-status.mdx +6 -0
- package/docs/troubleshooting.mdx +398 -0
- package/docs/upgrading-packages.mdx +80 -0
- package/docs/vercel-integration.mdx +207 -0
- package/docs/versioning.mdx +56 -0
- package/docs/video-walkthrough.mdx +23 -0
- package/docs/wait-for-token.mdx +540 -0
- package/docs/wait-for.mdx +42 -0
- package/docs/wait-until.mdx +53 -0
- package/docs/wait.mdx +18 -0
- package/docs/writing-tasks-introduction.mdx +33 -0
- package/package.json +8 -5
- package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
- package/skills/trigger-authoring-tasks/SKILL.md +254 -0
- package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
- package/skills/trigger-cost-savings/SKILL.md +116 -0
- package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
package/dist/esm/v3/ai.js
CHANGED
|
@@ -49,6 +49,10 @@ const chatTurnContextKey = locals.create("chat.turnContext");
|
|
|
49
49
|
* @internal
|
|
50
50
|
*/
|
|
51
51
|
const chatSessionHandleKey = locals.create("chat.sessionHandle");
|
|
52
|
+
// The external `chatId` from the boot payload — the value `ToolCallExecutionOptions.chatId`
|
|
53
|
+
// is documented to carry. Custom-agent loops never set per-turn context, so subtask tool
|
|
54
|
+
// metadata reads this directly rather than the Session handle id.
|
|
55
|
+
const chatExternalIdKey = locals.create("chat.externalId");
|
|
52
56
|
/**
|
|
53
57
|
* S2 seq_num of the most recent `turn-complete` control record written by
|
|
54
58
|
* this worker. Read by `writeTurnCompleteChunk` to know what to trim back
|
|
@@ -103,6 +107,43 @@ async function findLatestSessionInCursor(chatId) {
|
|
|
103
107
|
export async function __findLatestSessionInCursorForTests(chatId) {
|
|
104
108
|
return findLatestSessionInCursor(chatId);
|
|
105
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Seed the `.in` resume cursor for custom-agent loops (`chat.customAgent`
|
|
112
|
+
* raw loops and `chat.createSession`) the way `chat.agent`'s boot does.
|
|
113
|
+
*
|
|
114
|
+
* MUST run before anything attaches a `.in` listener (`createStopSignal`,
|
|
115
|
+
* `chat.messages.on`, the first wait): attaching opens the SSE tail with
|
|
116
|
+
* `Last-Event-ID` from the seeded cursor, so attach-then-seed replays
|
|
117
|
+
* every record from seq 0 — already-answered user messages get delivered
|
|
118
|
+
* into the new run's first wait and the loop re-answers them.
|
|
119
|
+
*
|
|
120
|
+
* Seeds both cursors: `setLastSeqNum` controls the SSE `Last-Event-ID`,
|
|
121
|
+
* `setLastDispatchedSeqNum` gates waiter dispatch — seeding only the
|
|
122
|
+
* former still re-delivers records the manager buffered before the seed.
|
|
123
|
+
*
|
|
124
|
+
* No-ops on fresh boots and when a cursor is already seeded (e.g. the
|
|
125
|
+
* `chatCustomAgent` wrapper ran before a nested `createChatSession`).
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
async function seedSessionInResumeCursorForCustomLoop(payload) {
|
|
129
|
+
if (sessionStreams.lastSeqNum(payload.chatId, "in") !== undefined)
|
|
130
|
+
return;
|
|
131
|
+
// No continuation/attempt gate: the wire may omit `continuation` on a
|
|
132
|
+
// run that still has prior turns (chat.agent covers that case via its
|
|
133
|
+
// snapshot). The scan doubles as the prior-state probe — a fresh
|
|
134
|
+
// session has no turn-complete on `.out`, returns no cursor, and
|
|
135
|
+
// seeds nothing. Cost on fresh boots is one non-blocking records read.
|
|
136
|
+
try {
|
|
137
|
+
const cursor = await findLatestSessionInCursor(payload.chatId);
|
|
138
|
+
if (cursor !== undefined) {
|
|
139
|
+
sessionStreams.setLastSeqNum(payload.chatId, "in", cursor);
|
|
140
|
+
sessionStreams.setLastDispatchedSeqNum(payload.chatId, "in", cursor);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
logger.warn("chat session: session.in resume cursor lookup failed; old messages may replay", { error: error instanceof Error ? error.message : String(error) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
106
147
|
let readChatSnapshotImpl;
|
|
107
148
|
export function __setReadChatSnapshotImplForTests(impl) {
|
|
108
149
|
readChatSnapshotImpl = impl;
|
|
@@ -654,6 +695,16 @@ function createTaskToolExecuteHandler(task) {
|
|
|
654
695
|
toolMeta.continuation = chatCtx.continuation;
|
|
655
696
|
toolMeta.clientData = chatCtx.clientData;
|
|
656
697
|
}
|
|
698
|
+
else {
|
|
699
|
+
// Hand-rolled chat.customAgent loops never set per-turn context, but
|
|
700
|
+
// the wrapper records the boot payload's external chatId at run boot
|
|
701
|
+
// — thread it so subtask chat helpers (`chat.stream.writer` with
|
|
702
|
+
// target "root") can open the parent's session.
|
|
703
|
+
const chatExternalId = locals.get(chatExternalIdKey);
|
|
704
|
+
if (chatExternalId) {
|
|
705
|
+
toolMeta.chatId = chatExternalId;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
657
708
|
const chatLocals = {};
|
|
658
709
|
for (const entry of chatLocalRegistry) {
|
|
659
710
|
const value = locals.get(entry.key);
|
|
@@ -1186,6 +1237,36 @@ const handoverInput = {
|
|
|
1186
1237
|
}
|
|
1187
1238
|
},
|
|
1188
1239
|
};
|
|
1240
|
+
/**
|
|
1241
|
+
* Wait for a `chat.headStart` handover signal inside a custom-agent loop or
|
|
1242
|
+
* `chat.createSession`. Returns:
|
|
1243
|
+
* - `null` — this run is not a `handover-prepare` boot, or the wait idled out /
|
|
1244
|
+
* the warm handler crashed before signaling. Treat as "no handover".
|
|
1245
|
+
* - `{ kind: "handover-skip" }` — the warm handler aborted; exit without a turn.
|
|
1246
|
+
* - `{ kind: "handover", partialAssistantMessage, messageId?, isFinal }` — splice
|
|
1247
|
+
* the partial (`chat.MessageAccumulator.applyHandover`) and, when `isFinal` is
|
|
1248
|
+
* false, fall through to `streamText` to run the handed-over tool round.
|
|
1249
|
+
*
|
|
1250
|
+
* For the common case prefer `accumulator.consumeHandover()`, which also seeds
|
|
1251
|
+
* `payload.headStartMessages` and applies the partial for you.
|
|
1252
|
+
*
|
|
1253
|
+
* Must be called at turn 0 before any `chat.messages.waitWithIdleTimeout` —
|
|
1254
|
+
* that facade consumes and discards non-message chunks, which would swallow the
|
|
1255
|
+
* handover signal.
|
|
1256
|
+
*/
|
|
1257
|
+
async function waitForHandover(options) {
|
|
1258
|
+
if (options.payload.trigger !== "handover-prepare")
|
|
1259
|
+
return null;
|
|
1260
|
+
const result = await handoverInput.waitWithIdleTimeout({
|
|
1261
|
+
idleTimeoutInSeconds: options.idleTimeoutInSeconds ?? options.payload.idleTimeoutInSeconds ?? 60,
|
|
1262
|
+
timeout: options.timeout,
|
|
1263
|
+
spanName: options.spanName ?? "waiting for handover signal",
|
|
1264
|
+
});
|
|
1265
|
+
// Non-ok = idle timeout or the warm handler crashed without signaling.
|
|
1266
|
+
if (!result.ok)
|
|
1267
|
+
return null;
|
|
1268
|
+
return result.output;
|
|
1269
|
+
}
|
|
1189
1270
|
/**
|
|
1190
1271
|
* Per-turn deferred promises. Registered via `chat.defer()`, awaited
|
|
1191
1272
|
* before `onTurnComplete` fires. Reset each turn.
|
|
@@ -1282,6 +1363,27 @@ function synthesizeHandoverUIMessage(partial, messageId) {
|
|
|
1282
1363
|
parts,
|
|
1283
1364
|
};
|
|
1284
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Splice a head-start handover partial into an accumulating message pair
|
|
1368
|
+
* (model + UI). Dedups by `messageId` against the UI chain (so a hydrated
|
|
1369
|
+
* history that already persisted the partial isn't doubled), then pushes the
|
|
1370
|
+
* partial into `modelMessages` and the synthesized UIMessage into `uiMessages`.
|
|
1371
|
+
* Shared by the `chat.agent` turn-0 splice and `ChatMessageAccumulator.applyHandover`.
|
|
1372
|
+
* @internal
|
|
1373
|
+
*/
|
|
1374
|
+
function spliceHandoverPartial(modelMessages, uiMessages, signal) {
|
|
1375
|
+
if (!signal.partialAssistantMessage || signal.partialAssistantMessage.length === 0) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
// Skip if the hydrated chain already persisted the partial under this id.
|
|
1379
|
+
const alreadyInChain = signal.messageId !== undefined && uiMessages.some((m) => m.id === signal.messageId);
|
|
1380
|
+
if (alreadyInChain)
|
|
1381
|
+
return;
|
|
1382
|
+
modelMessages.push(...signal.partialAssistantMessage);
|
|
1383
|
+
const partialUI = synthesizeHandoverUIMessage(signal.partialAssistantMessage, signal.messageId);
|
|
1384
|
+
if (partialUI)
|
|
1385
|
+
uiMessages.push(partialUI);
|
|
1386
|
+
}
|
|
1285
1387
|
/**
|
|
1286
1388
|
* Per-turn background context queue. Messages added via `chat.backgroundWork.inject()`
|
|
1287
1389
|
* are drained at the next `prepareStep` boundary and appended to the model messages.
|
|
@@ -2234,11 +2336,18 @@ function isCompactionSafe(messages) {
|
|
|
2234
2336
|
}
|
|
2235
2337
|
/** @internal */
|
|
2236
2338
|
const chatPromptKey = locals.create("chat.prompt");
|
|
2339
|
+
/**
|
|
2340
|
+
* @internal Provider options attached to the system message that
|
|
2341
|
+
* `toStreamTextOptions()` builds from the stored prompt — lets a provider cache
|
|
2342
|
+
* the system block. Stored separately so it works for both the `ResolvedPrompt`
|
|
2343
|
+
* and plain-string forms without mutating the prompt object.
|
|
2344
|
+
*/
|
|
2345
|
+
const chatPromptProviderOptionsKey = locals.create("chat.prompt.providerOptions");
|
|
2237
2346
|
/**
|
|
2238
2347
|
* Store a resolved prompt (or plain string) for the current run.
|
|
2239
2348
|
* Call from any hook (`onPreload`, `onChatStart`, `onTurnStart`) or `run()`.
|
|
2240
2349
|
*/
|
|
2241
|
-
function setChatPrompt(resolved) {
|
|
2350
|
+
function setChatPrompt(resolved, options) {
|
|
2242
2351
|
if (typeof resolved === "string") {
|
|
2243
2352
|
locals.set(chatPromptKey, {
|
|
2244
2353
|
text: resolved,
|
|
@@ -2255,6 +2364,9 @@ function setChatPrompt(resolved) {
|
|
|
2255
2364
|
else {
|
|
2256
2365
|
locals.set(chatPromptKey, resolved);
|
|
2257
2366
|
}
|
|
2367
|
+
// Always overwrite the slot (even with undefined) so a later prompt.set with
|
|
2368
|
+
// no options clears a previous prompt's cache opt-in rather than leaking it.
|
|
2369
|
+
locals.set(chatPromptProviderOptionsKey, options?.providerOptions);
|
|
2258
2370
|
}
|
|
2259
2371
|
/**
|
|
2260
2372
|
* Read the stored prompt. Throws if `chat.prompt.set()` has not been called.
|
|
@@ -2420,7 +2532,21 @@ function toStreamTextOptions(options) {
|
|
|
2420
2532
|
const promptText = prompt?.text ?? "";
|
|
2421
2533
|
const skillsText = skills && skills.length > 0 ? buildSkillsSystemPrompt(skills) : "";
|
|
2422
2534
|
if (promptText || skillsText) {
|
|
2423
|
-
|
|
2535
|
+
const systemText = [promptText, skillsText].filter(Boolean).join("\n\n");
|
|
2536
|
+
// Resolve system-prompt provider options for caching. Precedence (most
|
|
2537
|
+
// specific wins, no deep merge): explicit `systemProviderOptions` →
|
|
2538
|
+
// `cacheControl` sugar → `providerOptions` stored on `chat.prompt.set()`.
|
|
2539
|
+
const systemProviderOptions = options?.systemProviderOptions ??
|
|
2540
|
+
(options?.cacheControl
|
|
2541
|
+
? { anthropic: { cacheControl: options.cacheControl } }
|
|
2542
|
+
: undefined) ??
|
|
2543
|
+
locals.get(chatPromptProviderOptionsKey);
|
|
2544
|
+
// A bare string stays a bare string (the unchanged default). With provider
|
|
2545
|
+
// options, emit a structured `SystemModelMessage` so the provider can cache
|
|
2546
|
+
// the system block — `streamText`'s `system` accepts string | message.
|
|
2547
|
+
result.system = systemProviderOptions
|
|
2548
|
+
? { role: "system", content: systemText, providerOptions: systemProviderOptions }
|
|
2549
|
+
: systemText;
|
|
2424
2550
|
}
|
|
2425
2551
|
// Prompt-related options (only if chat.prompt.set() was called)
|
|
2426
2552
|
if (prompt) {
|
|
@@ -2602,6 +2728,7 @@ function chatCustomAgent(options) {
|
|
|
2602
2728
|
// `chat.createStartSessionAction`) before this run is triggered.
|
|
2603
2729
|
// No client-side upsert needed.
|
|
2604
2730
|
locals.set(chatSessionHandleKey, sessions.open(payload.chatId));
|
|
2731
|
+
locals.set(chatExternalIdKey, payload.chatId);
|
|
2605
2732
|
locals.set(chatAgentRunContextKey, runOptions.ctx);
|
|
2606
2733
|
// Initialize the turn-complete trim slot so `chat.writeTurnComplete`
|
|
2607
2734
|
// trims `session.out` back to the previous turn boundary. Without
|
|
@@ -2611,6 +2738,10 @@ function chatCustomAgent(options) {
|
|
|
2611
2738
|
markChatAgentRunForStreamsWarning();
|
|
2612
2739
|
taskContext.setConversationId(payload.chatId);
|
|
2613
2740
|
stampConversationIdOnActiveSpan(payload.chatId);
|
|
2741
|
+
// Seed the `.in` resume cursor before user code attaches any `.in`
|
|
2742
|
+
// listener — otherwise a continuation boot replays already-answered
|
|
2743
|
+
// messages into the loop's first wait.
|
|
2744
|
+
await seedSessionInResumeCursorForCustomLoop(payload);
|
|
2614
2745
|
return userRun(payload, runOptions);
|
|
2615
2746
|
},
|
|
2616
2747
|
});
|
|
@@ -2657,6 +2788,7 @@ function chatAgent(options) {
|
|
|
2657
2788
|
// `chat.createStartSessionAction` or browser-direct) before this
|
|
2658
2789
|
// run is triggered — no client-side upsert needed here.
|
|
2659
2790
|
locals.set(chatSessionHandleKey, sessions.open(payload.chatId));
|
|
2791
|
+
locals.set(chatExternalIdKey, payload.chatId);
|
|
2660
2792
|
// Mutable holder; advances in `writeTurnCompleteChunk` after each turn
|
|
2661
2793
|
// and is the trim target for the NEXT turn's trim record.
|
|
2662
2794
|
locals.set(lastTurnCompleteSeqNumKey, { value: undefined });
|
|
@@ -3866,18 +3998,10 @@ function chatAgent(options) {
|
|
|
3866
3998
|
// `UIMessageStreamError: No tool invocation found`.
|
|
3867
3999
|
const pendingHandoverPartial = locals.get(chatHandoverPartialKey);
|
|
3868
4000
|
if (pendingHandoverPartial && pendingHandoverPartial.length > 0) {
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
accumulatedUIMessages.some((m) => m.id === handoverMessageId);
|
|
3874
|
-
if (!alreadyInChain) {
|
|
3875
|
-
accumulatedMessages.push(...pendingHandoverPartial);
|
|
3876
|
-
const partialUI = synthesizeHandoverUIMessage(pendingHandoverPartial, handoverMessageId);
|
|
3877
|
-
if (partialUI) {
|
|
3878
|
-
accumulatedUIMessages.push(partialUI);
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
4001
|
+
spliceHandoverPartial(accumulatedMessages, accumulatedUIMessages, {
|
|
4002
|
+
partialAssistantMessage: pendingHandoverPartial,
|
|
4003
|
+
messageId: locals.get(chatHandoverMessageIdKey),
|
|
4004
|
+
});
|
|
3881
4005
|
locals.set(chatHandoverPartialKey, []); // consume once
|
|
3882
4006
|
}
|
|
3883
4007
|
}
|
|
@@ -5437,8 +5561,19 @@ async function pipeChatAndCapture(source, options) {
|
|
|
5437
5561
|
const onFinishPromise = new Promise((r) => {
|
|
5438
5562
|
resolveOnFinish = r;
|
|
5439
5563
|
});
|
|
5564
|
+
const resolvedOptions = resolveUIMessageStreamOptions();
|
|
5440
5565
|
const uiStream = source.toUIMessageStream({
|
|
5441
|
-
...
|
|
5566
|
+
...resolvedOptions,
|
|
5567
|
+
// Thread the prior chain (incl. a spliced handover partial) so a resumed
|
|
5568
|
+
// tool round's tool-output chunks merge into the originating tool-call
|
|
5569
|
+
// instead of throwing "No tool invocation found".
|
|
5570
|
+
...(options?.originalMessages ? { originalMessages: options.originalMessages } : {}),
|
|
5571
|
+
// Stamp a server-generated id on the start chunk, same as chat.agent's
|
|
5572
|
+
// pipe. Without it the AI SDK regenerates the assistant id when a
|
|
5573
|
+
// prepareStep injection (steering) starts a new step mid-stream, and
|
|
5574
|
+
// the frontend replaces the partial message — wiping the
|
|
5575
|
+
// pre-injection text from the UI and the captured response.
|
|
5576
|
+
generateMessageId: resolvedOptions.generateMessageId ?? generateMessageId,
|
|
5442
5577
|
onFinish: ({ responseMessage }) => {
|
|
5443
5578
|
captured = responseMessage;
|
|
5444
5579
|
resolveOnFinish();
|
|
@@ -5508,10 +5643,65 @@ class ChatMessageAccumulator {
|
|
|
5508
5643
|
this.uiMessages = [...uiMessages];
|
|
5509
5644
|
this.modelMessages = await toModelMessages(uiMessages);
|
|
5510
5645
|
}
|
|
5646
|
+
/**
|
|
5647
|
+
* Splice a `chat.headStart` handover partial into the accumulator (the warm
|
|
5648
|
+
* step-1 response). Dedups by `messageId` so a seeded/hydrated history that
|
|
5649
|
+
* already carries the partial isn't doubled. Seed any prior history first
|
|
5650
|
+
* (e.g. `setMessages(payload.headStartMessages)`). Low-level — see
|
|
5651
|
+
* `consumeHandover` for the wait+seed+apply convenience.
|
|
5652
|
+
*/
|
|
5653
|
+
applyHandover(signal) {
|
|
5654
|
+
spliceHandoverPartial(this.modelMessages, this.uiMessages, signal);
|
|
5655
|
+
}
|
|
5656
|
+
/**
|
|
5657
|
+
* One-call `chat.headStart` handover for a custom-agent loop: waits for the
|
|
5658
|
+
* handover signal, seeds prior history from `payload.headStartMessages`,
|
|
5659
|
+
* applies the warm step-1 partial, and reports what to do next.
|
|
5660
|
+
*
|
|
5661
|
+
* Returns `{ isFinal, skipped }`:
|
|
5662
|
+
* - `skipped: true` — not a `handover-prepare` run, the wait idled out, or the
|
|
5663
|
+
* warm handler aborted. Exit the run without a turn.
|
|
5664
|
+
* - `isFinal: true` — step 1 IS the response (pure text). Write turn-complete
|
|
5665
|
+
* and continue; do not call `streamText`.
|
|
5666
|
+
* - `isFinal: false` — fall through to `streamText`, which runs the pending
|
|
5667
|
+
* tool round handed over from step 1.
|
|
5668
|
+
*/
|
|
5669
|
+
async consumeHandover(options) {
|
|
5670
|
+
const signal = await waitForHandover({
|
|
5671
|
+
payload: options.payload,
|
|
5672
|
+
idleTimeoutInSeconds: options.idleTimeoutInSeconds,
|
|
5673
|
+
timeout: options.timeout,
|
|
5674
|
+
});
|
|
5675
|
+
if (!signal || signal.kind === "handover-skip") {
|
|
5676
|
+
return { isFinal: false, skipped: true };
|
|
5677
|
+
}
|
|
5678
|
+
if (options.payload.headStartMessages && options.payload.headStartMessages.length > 0) {
|
|
5679
|
+
await this.setMessages(options.payload.headStartMessages);
|
|
5680
|
+
}
|
|
5681
|
+
this.applyHandover(signal);
|
|
5682
|
+
return { isFinal: signal.isFinal, skipped: false };
|
|
5683
|
+
}
|
|
5511
5684
|
async addResponse(response) {
|
|
5512
5685
|
if (!response.id) {
|
|
5513
5686
|
response = { ...response, id: generateMessageId() };
|
|
5514
5687
|
}
|
|
5688
|
+
// Tool-approval and handover-resume continuations reuse the trailing
|
|
5689
|
+
// assistant's ID (via originalMessages on the pipe), so the captured
|
|
5690
|
+
// response can carry the same ID as a message already in the chain
|
|
5691
|
+
// (e.g. a spliced handover partial). Replace in place instead of pushing
|
|
5692
|
+
// a duplicate, mirroring the chat.agent accumulator.
|
|
5693
|
+
const existingIdx = this.uiMessages.findIndex((m) => m.id === response.id);
|
|
5694
|
+
if (existingIdx !== -1) {
|
|
5695
|
+
this.uiMessages[existingIdx] = response;
|
|
5696
|
+
try {
|
|
5697
|
+
// Reconvert all model messages since we replaced rather than appended.
|
|
5698
|
+
this.modelMessages = await toModelMessages(this.uiMessages.map((m) => stripProviderMetadata(m)));
|
|
5699
|
+
}
|
|
5700
|
+
catch {
|
|
5701
|
+
// Conversion failed — leave the existing model messages in place
|
|
5702
|
+
}
|
|
5703
|
+
return;
|
|
5704
|
+
}
|
|
5515
5705
|
this.uiMessages.push(response);
|
|
5516
5706
|
try {
|
|
5517
5707
|
const msgs = await toModelMessages([stripProviderMetadata(response)]);
|
|
@@ -5644,14 +5834,18 @@ class ChatMessageAccumulator {
|
|
|
5644
5834
|
* signaling, and idle/suspend between turns. You control: initialization,
|
|
5645
5835
|
* model/tool selection, persistence, and any custom per-turn logic.
|
|
5646
5836
|
*
|
|
5837
|
+
* Call from inside a `chat.customAgent()` run — the wrapper binds the
|
|
5838
|
+
* backing Session that the iterator's stop signal and message channels
|
|
5839
|
+
* resolve to. (A plain `task()` does not bind it, so `createSession`
|
|
5840
|
+
* would throw "session handle is not initialized".)
|
|
5841
|
+
*
|
|
5647
5842
|
* @example
|
|
5648
5843
|
* ```ts
|
|
5649
|
-
* import { task } from "@trigger.dev/sdk";
|
|
5650
5844
|
* import { chat, type ChatTaskWirePayload } from "@trigger.dev/sdk/ai";
|
|
5651
5845
|
* import { streamText } from "ai";
|
|
5652
5846
|
* import { openai } from "@ai-sdk/openai";
|
|
5653
5847
|
*
|
|
5654
|
-
* export const myChat =
|
|
5848
|
+
* export const myChat = chat.customAgent({
|
|
5655
5849
|
* id: "my-chat",
|
|
5656
5850
|
* run: async (payload: ChatTaskWirePayload, { signal }) => {
|
|
5657
5851
|
* const session = chat.createSession(payload, { signal });
|
|
@@ -5675,13 +5869,46 @@ function createChatSession(payload, options) {
|
|
|
5675
5869
|
[Symbol.asyncIterator]() {
|
|
5676
5870
|
let currentPayload = payload;
|
|
5677
5871
|
let turn = -1;
|
|
5678
|
-
|
|
5872
|
+
// Created on the first next() call, AFTER the resume-cursor seed —
|
|
5873
|
+
// createStopSignal attaches the `.in` SSE tail, and attaching
|
|
5874
|
+
// before the seed replays every record from seq 0 (the seed is a
|
|
5875
|
+
// no-op when the chatCustomAgent wrapper already ran it).
|
|
5876
|
+
let stop;
|
|
5877
|
+
let booted = false;
|
|
5679
5878
|
const accumulator = new ChatMessageAccumulator();
|
|
5680
5879
|
let previousTurnUsage;
|
|
5681
5880
|
let cumulativeUsage = emptyUsage();
|
|
5682
5881
|
return {
|
|
5683
5882
|
async next() {
|
|
5883
|
+
if (!booted) {
|
|
5884
|
+
booted = true;
|
|
5885
|
+
await seedSessionInResumeCursorForCustomLoop(currentPayload);
|
|
5886
|
+
stop = createStopSignal();
|
|
5887
|
+
}
|
|
5684
5888
|
turn++;
|
|
5889
|
+
// Head-start handover: the server triggered this run with
|
|
5890
|
+
// `trigger: "handover-prepare"` and signals the warm step-1 partial on
|
|
5891
|
+
// `session.in`. Wait for it BEFORE any `messagesInput.waitWithIdleTimeout`
|
|
5892
|
+
// (that facade consumes-and-discards non-message chunks and would swallow
|
|
5893
|
+
// the signal). Turn-0 only — continuation boots never carry this trigger.
|
|
5894
|
+
let handoverThisTurn = null;
|
|
5895
|
+
let pendingHandoverSignal = null;
|
|
5896
|
+
if (turn === 0 && currentPayload.trigger === "handover-prepare") {
|
|
5897
|
+
const signal = await waitForHandover({
|
|
5898
|
+
payload: currentPayload,
|
|
5899
|
+
idleTimeoutInSeconds: sessionIdleTimeoutOpt ?? currentPayload.idleTimeoutInSeconds ?? idleTimeoutInSeconds,
|
|
5900
|
+
timeout,
|
|
5901
|
+
});
|
|
5902
|
+
if (!signal || signal.kind === "handover-skip" || runSignal.aborted) {
|
|
5903
|
+
stop.cleanup();
|
|
5904
|
+
return { done: true, value: undefined };
|
|
5905
|
+
}
|
|
5906
|
+
pendingHandoverSignal = signal;
|
|
5907
|
+
handoverThisTurn = { isFinal: signal.isFinal };
|
|
5908
|
+
// Rewrite to a normal first-turn message turn so the rest of the loop
|
|
5909
|
+
// (steering setup, addIncoming, turnObj) runs unchanged.
|
|
5910
|
+
currentPayload = { ...currentPayload, trigger: "submit-message", message: undefined };
|
|
5911
|
+
}
|
|
5685
5912
|
// First turn: wait when the boot payload carries no message.
|
|
5686
5913
|
// Preload boots wait for the first real message; continuation
|
|
5687
5914
|
// boots (fresh run via `ensureRunForSession` / end-and-continue)
|
|
@@ -5788,6 +6015,16 @@ function createChatSession(payload, options) {
|
|
|
5788
6015
|
? [currentPayload.message]
|
|
5789
6016
|
: [];
|
|
5790
6017
|
const messages = await accumulator.addIncoming(incomingForAccumulator, currentPayload.trigger, turn);
|
|
6018
|
+
// Apply the head-start handover AFTER addIncoming — turn-0 addIncoming
|
|
6019
|
+
// replaces accumulator state, which would wipe a pre-applied splice.
|
|
6020
|
+
// Seed prior history first, then splice the warm step-1 partial.
|
|
6021
|
+
if (pendingHandoverSignal) {
|
|
6022
|
+
const priorHistory = currentPayload.headStartMessages;
|
|
6023
|
+
if (priorHistory && priorHistory.length > 0) {
|
|
6024
|
+
await accumulator.setMessages(priorHistory);
|
|
6025
|
+
}
|
|
6026
|
+
accumulator.applyHandover(pendingHandoverSignal);
|
|
6027
|
+
}
|
|
5791
6028
|
// chat.requestUpgrade() called before this turn — signal transport and exit
|
|
5792
6029
|
if (locals.get(chatUpgradeRequestedKey)) {
|
|
5793
6030
|
await writeUpgradeRequiredChunk();
|
|
@@ -5814,13 +6051,38 @@ function createChatSession(payload, options) {
|
|
|
5814
6051
|
continuation: currentPayload.continuation ?? false,
|
|
5815
6052
|
previousTurnUsage,
|
|
5816
6053
|
totalUsage: cumulativeUsage,
|
|
6054
|
+
handover: handoverThisTurn,
|
|
5817
6055
|
async setMessages(uiMessages) {
|
|
5818
6056
|
await accumulator.setMessages(uiMessages);
|
|
5819
6057
|
},
|
|
5820
6058
|
async complete(source) {
|
|
6059
|
+
// Head-start final turn: the warm step-1 partial is already spliced
|
|
6060
|
+
// into the accumulator and IS the response — nothing to pipe. Only
|
|
6061
|
+
// valid on a final handover; a missing source on any other turn is a
|
|
6062
|
+
// mistake (it would silently finalize without an assistant response).
|
|
6063
|
+
if (!source) {
|
|
6064
|
+
if (!handoverThisTurn?.isFinal) {
|
|
6065
|
+
throw new Error("turn.complete() requires a stream source unless turn.handover.isFinal is true");
|
|
6066
|
+
}
|
|
6067
|
+
const response = accumulator.uiMessages.at(-1);
|
|
6068
|
+
if (!response || response.role !== "assistant") {
|
|
6069
|
+
throw new Error("turn.complete() could not find the spliced handover response");
|
|
6070
|
+
}
|
|
6071
|
+
sessionMsgSub.off();
|
|
6072
|
+
await chatWriteTurnComplete();
|
|
6073
|
+
return response;
|
|
6074
|
+
}
|
|
5821
6075
|
let response;
|
|
5822
6076
|
try {
|
|
5823
|
-
response = await pipeChatAndCapture(source, {
|
|
6077
|
+
response = await pipeChatAndCapture(source, {
|
|
6078
|
+
signal: combinedSignal,
|
|
6079
|
+
// On a non-final handover turn, thread the spliced partial so a
|
|
6080
|
+
// resumed tool round's tool-output chunks merge into the
|
|
6081
|
+
// handed-over tool-call. Gated on the handover turn only — a
|
|
6082
|
+
// normal turn must not pass originalMessages (it would merge the
|
|
6083
|
+
// fresh response into the prior assistant message).
|
|
6084
|
+
...(handoverThisTurn ? { originalMessages: accumulator.uiMessages } : {}),
|
|
6085
|
+
});
|
|
5824
6086
|
}
|
|
5825
6087
|
catch (error) {
|
|
5826
6088
|
if (error instanceof Error && error.name === "AbortError") {
|
|
@@ -5975,7 +6237,8 @@ function createChatSession(payload, options) {
|
|
|
5975
6237
|
return { done: false, value: turnObj };
|
|
5976
6238
|
},
|
|
5977
6239
|
async return() {
|
|
5978
|
-
stop
|
|
6240
|
+
// `stop` only exists once next() has booted the iterator.
|
|
6241
|
+
stop?.cleanup();
|
|
5979
6242
|
return { done: true, value: undefined };
|
|
5980
6243
|
},
|
|
5981
6244
|
};
|
|
@@ -6218,8 +6481,8 @@ function createChatStartSessionAction(taskId, options) {
|
|
|
6218
6481
|
// run-list filter by chat works without the customer having to wire it
|
|
6219
6482
|
// up. Mirrors the browser-mediated `TriggerChatTransport.doStart` path.
|
|
6220
6483
|
const userTags = params.triggerConfig?.tags ?? options?.triggerConfig?.tags ?? [];
|
|
6221
|
-
//
|
|
6222
|
-
const tags = [`chat:${params.chatId}`, ...userTags].slice(0,
|
|
6484
|
+
// SessionTriggerConfig.tags allows at most 5; the auto chat tag takes one slot.
|
|
6485
|
+
const tags = [`chat:${params.chatId}`, ...userTags].slice(0, 5);
|
|
6223
6486
|
const clientDataMetadata = params.clientData !== undefined ? { metadata: params.clientData } : {};
|
|
6224
6487
|
const triggerConfig = {
|
|
6225
6488
|
basePayload: {
|
|
@@ -6243,6 +6506,20 @@ function createChatStartSessionAction(taskId, options) {
|
|
|
6243
6506
|
maxAttempts: params.triggerConfig?.maxAttempts ?? options?.triggerConfig?.maxAttempts,
|
|
6244
6507
|
}
|
|
6245
6508
|
: {}),
|
|
6509
|
+
...(options?.triggerConfig?.maxDuration !== undefined ||
|
|
6510
|
+
params.triggerConfig?.maxDuration !== undefined
|
|
6511
|
+
? {
|
|
6512
|
+
maxDuration: params.triggerConfig?.maxDuration ?? options?.triggerConfig?.maxDuration,
|
|
6513
|
+
}
|
|
6514
|
+
: {}),
|
|
6515
|
+
...(options?.triggerConfig?.region || params.triggerConfig?.region
|
|
6516
|
+
? { region: params.triggerConfig?.region ?? options?.triggerConfig?.region }
|
|
6517
|
+
: {}),
|
|
6518
|
+
...(options?.triggerConfig?.lockToVersion || params.triggerConfig?.lockToVersion
|
|
6519
|
+
? {
|
|
6520
|
+
lockToVersion: params.triggerConfig?.lockToVersion ?? options?.triggerConfig?.lockToVersion,
|
|
6521
|
+
}
|
|
6522
|
+
: {}),
|
|
6246
6523
|
...(options?.triggerConfig?.idleTimeoutInSeconds !== undefined ||
|
|
6247
6524
|
params.triggerConfig?.idleTimeoutInSeconds !== undefined
|
|
6248
6525
|
? {
|
|
@@ -6421,10 +6698,20 @@ export const chat = {
|
|
|
6421
6698
|
MessageAccumulator: ChatMessageAccumulator,
|
|
6422
6699
|
/** Create a chat session (async iterator). See {@link createChatSession}. */
|
|
6423
6700
|
createSession: createChatSession,
|
|
6701
|
+
/**
|
|
6702
|
+
* Wait for a `chat.headStart` handover signal inside a `chat.customAgent`
|
|
6703
|
+
* loop (turn 0). See {@link waitForHandover}. For most loops prefer the
|
|
6704
|
+
* `chat.MessageAccumulator.consumeHandover()` convenience, which also seeds
|
|
6705
|
+
* `payload.headStartMessages` and applies the partial.
|
|
6706
|
+
*/
|
|
6707
|
+
waitForHandover,
|
|
6424
6708
|
/**
|
|
6425
6709
|
* Store and retrieve a resolved prompt for the current run.
|
|
6426
6710
|
*
|
|
6427
6711
|
* - `chat.prompt.set(resolved)` — store a `ResolvedPrompt` or plain string
|
|
6712
|
+
* - `chat.prompt.set(resolved, { providerOptions })` — also attach provider
|
|
6713
|
+
* options to the system block so a provider can cache it (e.g. Anthropic
|
|
6714
|
+
* prompt caching). See the prompt-caching guide.
|
|
6428
6715
|
* - `chat.prompt()` — read the stored prompt (throws if not set)
|
|
6429
6716
|
*/
|
|
6430
6717
|
prompt: Object.assign(getChatPrompt, { set: setChatPrompt }),
|