@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.
Files changed (76) hide show
  1. package/dist/commonjs/v3/ai-shared.d.ts +15 -0
  2. package/dist/commonjs/v3/ai-shared.js +35 -1
  3. package/dist/commonjs/v3/ai-shared.js.map +1 -1
  4. package/dist/commonjs/v3/ai.d.ts +76 -9
  5. package/dist/commonjs/v3/ai.js +316 -222
  6. package/dist/commonjs/v3/ai.js.map +1 -1
  7. package/dist/commonjs/v3/chat-client.d.ts +8 -0
  8. package/dist/commonjs/v3/chat-client.js +199 -109
  9. package/dist/commonjs/v3/chat-client.js.map +1 -1
  10. package/dist/commonjs/v3/chat-react.d.ts +54 -0
  11. package/dist/commonjs/v3/chat-react.js +83 -0
  12. package/dist/commonjs/v3/chat-react.js.map +1 -1
  13. package/dist/commonjs/v3/chat.d.ts +11 -0
  14. package/dist/commonjs/v3/chat.js +36 -27
  15. package/dist/commonjs/v3/chat.js.map +1 -1
  16. package/dist/commonjs/v3/chatSnapshotIo.d.ts +2 -0
  17. package/dist/commonjs/v3/chatSnapshotIo.js +174 -0
  18. package/dist/commonjs/v3/chatSnapshotIo.js.map +1 -0
  19. package/dist/commonjs/v3/test/index.d.ts +1 -0
  20. package/dist/commonjs/v3/test/index.js +3 -1
  21. package/dist/commonjs/v3/test/index.js.map +1 -1
  22. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +4 -4
  23. package/dist/commonjs/v3/test/mock-chat-agent.js +25 -4
  24. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  25. package/dist/commonjs/v3/test/transcript-storage-tests.d.ts +40 -0
  26. package/dist/commonjs/v3/test/transcript-storage-tests.js +258 -0
  27. package/dist/commonjs/v3/test/transcript-storage-tests.js.map +1 -0
  28. package/dist/commonjs/v3/transcriptStorage.d.ts +229 -0
  29. package/dist/commonjs/v3/transcriptStorage.js +321 -0
  30. package/dist/commonjs/v3/transcriptStorage.js.map +1 -0
  31. package/dist/commonjs/version.js +1 -1
  32. package/dist/esm/v3/ai-shared.d.ts +15 -0
  33. package/dist/esm/v3/ai-shared.js +33 -0
  34. package/dist/esm/v3/ai-shared.js.map +1 -1
  35. package/dist/esm/v3/ai.d.ts +76 -9
  36. package/dist/esm/v3/ai.js +306 -217
  37. package/dist/esm/v3/ai.js.map +1 -1
  38. package/dist/esm/v3/chat-client.d.ts +8 -0
  39. package/dist/esm/v3/chat-client.js +200 -110
  40. package/dist/esm/v3/chat-client.js.map +1 -1
  41. package/dist/esm/v3/chat-react.d.ts +54 -0
  42. package/dist/esm/v3/chat-react.js +81 -0
  43. package/dist/esm/v3/chat-react.js.map +1 -1
  44. package/dist/esm/v3/chat.d.ts +11 -0
  45. package/dist/esm/v3/chat.js +36 -27
  46. package/dist/esm/v3/chat.js.map +1 -1
  47. package/dist/esm/v3/chatSnapshotIo.d.ts +2 -0
  48. package/dist/esm/v3/chatSnapshotIo.js +166 -0
  49. package/dist/esm/v3/chatSnapshotIo.js.map +1 -0
  50. package/dist/esm/v3/test/index.d.ts +1 -0
  51. package/dist/esm/v3/test/index.js +1 -0
  52. package/dist/esm/v3/test/index.js.map +1 -1
  53. package/dist/esm/v3/test/mock-chat-agent.d.ts +4 -4
  54. package/dist/esm/v3/test/mock-chat-agent.js +25 -4
  55. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  56. package/dist/esm/v3/test/transcript-storage-tests.d.ts +40 -0
  57. package/dist/esm/v3/test/transcript-storage-tests.js +255 -0
  58. package/dist/esm/v3/test/transcript-storage-tests.js.map +1 -0
  59. package/dist/esm/v3/transcriptStorage.d.ts +229 -0
  60. package/dist/esm/v3/transcriptStorage.js +309 -0
  61. package/dist/esm/v3/transcriptStorage.js.map +1 -0
  62. package/dist/esm/version.js +1 -1
  63. package/docs/ai-chat/actions.mdx +2 -2
  64. package/docs/ai-chat/background-injection.mdx +2 -0
  65. package/docs/ai-chat/compaction.mdx +2 -0
  66. package/docs/ai-chat/frontend.mdx +2 -0
  67. package/docs/ai-chat/lifecycle-hooks.mdx +6 -2
  68. package/docs/ai-chat/patterns/database-persistence.mdx +6 -2
  69. package/docs/ai-chat/patterns/persistence-and-replay.mdx +34 -23
  70. package/docs/ai-chat/reference.mdx +56 -5
  71. package/docs/ai-chat/transcript-storage.mdx +233 -0
  72. package/docs/config/extensions/syncEnvVars.mdx +6 -0
  73. package/docs/deploy-environment-variables.mdx +23 -2
  74. package/package.json +2 -2
  75. package/skills/trigger-authoring-chat-agent/SKILL.md +3 -2
  76. package/skills/trigger-chat-agent-advanced/SKILL.md +5 -5
@@ -0,0 +1,229 @@
1
+ import { type TaskRunContext, type TranscriptSnapshotEntry } from "@trigger.dev/core/v3";
2
+ import type { ModelMessage, UIMessage } from "ai";
3
+ /**
4
+ * One change to a transcript. Ops address messages by id; position is the
5
+ * storage's own concern.
6
+ *
7
+ * - `put` upserts by `message.id`: an unknown id appends, a known id is
8
+ * replaced in place. `final` is false when the runtime captured a partial
9
+ * assistant message from an errored or stopped turn; it defaults to true.
10
+ * - `remove` deletes by id and is a no-op for an unknown id.
11
+ * - `truncateAfter` drops every message ordered after `afterId` (a rollback)
12
+ * and is a no-op when `afterId` is unknown.
13
+ * - `state` replaces the opaque runtime record; `null` clears it.
14
+ */
15
+ export type TranscriptChange = {
16
+ op: "put";
17
+ message: UIMessage;
18
+ final?: boolean;
19
+ } | {
20
+ op: "remove";
21
+ id: string;
22
+ } | {
23
+ op: "truncateAfter";
24
+ afterId: string;
25
+ } | {
26
+ op: "state";
27
+ value: unknown | null;
28
+ };
29
+ export type TranscriptChangeReason = "turn-complete" | "turn-error" | "action" | "compaction" | "recovery";
30
+ export type TranscriptCursors = {
31
+ lastOutEventId?: string;
32
+ lastInEventId?: string;
33
+ };
34
+ /**
35
+ * What the runtime hands to `save`: the ordered changes since the last save
36
+ * (one transaction where the backend supports it), the whole transcript as
37
+ * it stands after those changes, and the stream cursors the next boot should
38
+ * resume from. A row-per-message store applies `changes`; a store that keeps
39
+ * the conversation as one document writes `transcript` as-is and needs no
40
+ * state of its own. Cursors are runtime-computed and persisted opaquely; a
41
+ * storage never interprets them.
42
+ */
43
+ export type TranscriptChangeset = {
44
+ reason: TranscriptChangeReason;
45
+ changes: TranscriptChange[];
46
+ transcript: TranscriptState;
47
+ cursors?: TranscriptCursors;
48
+ };
49
+ /** The tenant scope of a read. A render read has no run, so this is all `load` gets. */
50
+ export type TranscriptScope<TClientData = unknown> = {
51
+ chatId: string;
52
+ clientData: TClientData;
53
+ };
54
+ /** The run context of a write. Supplied by the runtime; always in-run. */
55
+ export type TranscriptStorageContext<TClientData = unknown> = TranscriptScope<TClientData> & {
56
+ turn: number;
57
+ trigger: "submit-message" | "regenerate-message" | "action";
58
+ runId: string;
59
+ ctx: TaskRunContext;
60
+ };
61
+ export type TranscriptLoadOptions = {
62
+ /** Return at most this many messages, the most recent ones. */
63
+ limit?: number;
64
+ /** Return messages ordered before this message id (a `nextCursor` from a previous page). */
65
+ before?: string;
66
+ };
67
+ export type TranscriptLoadResult<TUIMessage extends UIMessage = UIMessage> = {
68
+ messages: TUIMessage[];
69
+ state: unknown | null;
70
+ cursors?: TranscriptCursors;
71
+ /** The id to pass as `before` for the previous page; absent on the last page. */
72
+ nextCursor?: string;
73
+ /**
74
+ * Ids among `messages` that were saved with `final: false` (a partial
75
+ * answer). A storage that keeps `final` returns them so a partial stays
76
+ * partial when a continuation saves it again unchanged; a storage that
77
+ * does not keep `final` may leave this out.
78
+ */
79
+ nonFinalIds?: string[];
80
+ };
81
+ /** What `loadContext` receives on every turn and action. */
82
+ export type LoadContextEvent<TClientData = unknown, TUIMessage extends UIMessage = UIMessage> = {
83
+ chatId: string;
84
+ /** The turn number (0-indexed). */
85
+ turn: number;
86
+ trigger: "submit-message" | "regenerate-message" | "action";
87
+ /** The messages the frontend sent for this turn. Empty for actions. */
88
+ incomingMessages: TUIMessage[];
89
+ /** The runtime's transcript before this turn, including any tail it recovered. */
90
+ previousMessages: TUIMessage[];
91
+ clientData?: TClientData;
92
+ continuation: boolean;
93
+ previousRunId?: string;
94
+ };
95
+ /**
96
+ * A persistence adapter for a `chat.agent` transcript. The runtime calls
97
+ * `load` once at a continuation boot and `save` after every change to the
98
+ * conversation. Both are best-effort from the runtime's point of view: an
99
+ * error is logged and the turn continues.
100
+ *
101
+ * `loadContext` is optional. Its presence declares that the application
102
+ * owns the model's context: the runtime calls it on every turn and action
103
+ * and uses what it returns as the conversation, instead of the transcript
104
+ * it accumulated. Tail recovery still runs and `save` is still called.
105
+ */
106
+ export type TranscriptStorage<TClientData = unknown> = {
107
+ load<TUIMessage extends UIMessage = UIMessage>(scope: TranscriptScope<TClientData>, opts?: TranscriptLoadOptions): Promise<TranscriptLoadResult<TUIMessage>>;
108
+ save(ctx: TranscriptStorageContext<TClientData>, changeset: TranscriptChangeset): Promise<void>;
109
+ loadContext?<TUIMessage extends UIMessage = UIMessage>(scope: TranscriptScope<TClientData>, event: LoadContextEvent<TClientData, TUIMessage>): Promise<TUIMessage[]> | TUIMessage[];
110
+ };
111
+ /** An in-memory transcript: ordered entries plus the opaque state record. */
112
+ export type TranscriptState<TUIMessage extends UIMessage = UIMessage> = {
113
+ entries: TranscriptSnapshotEntry<TUIMessage>[];
114
+ state: unknown | null;
115
+ };
116
+ export declare function emptyTranscriptState<TUIMessage extends UIMessage = UIMessage>(): TranscriptState<TUIMessage>;
117
+ /**
118
+ * Apply changes to a transcript, in order. Pure: returns a new state and
119
+ * never mutates the input. Replaying the same changes converges, which is
120
+ * what lets a storage retry a failed write without checking what landed.
121
+ */
122
+ export declare function reduceTranscriptChanges<TUIMessage extends UIMessage = UIMessage>(prev: TranscriptState<TUIMessage>, changes: readonly TranscriptChange[]): TranscriptState<TUIMessage>;
123
+ /**
124
+ * What the runtime remembers about the transcript it last handed to
125
+ * `save`: the ids in order and a fingerprint per message, so the next
126
+ * changeset can be derived from the accumulator without asking the storage.
127
+ */
128
+ export type TranscriptShadow = {
129
+ ids: string[];
130
+ fingerprints: Map<string, string>;
131
+ /** Ids last saved with `final: false`. Everything else was saved final. */
132
+ nonFinal: Set<string>;
133
+ };
134
+ /**
135
+ * Build the shadow for `messages`. An id is non-final when this save says so
136
+ * (`nonFinalIds`) or when it was non-final before and its content has not
137
+ * changed: a partial answer stays partial until the message itself changes.
138
+ */
139
+ export declare function createTranscriptShadow(messages: readonly UIMessage[], nonFinalIds?: ReadonlySet<string>, previous?: TranscriptShadow): TranscriptShadow;
140
+ /**
141
+ * Derive the changes that take `shadow` to `next`.
142
+ *
143
+ * The common prefix (by id) is compared message by message and a changed
144
+ * message becomes an in-place `put`. Past the prefix, everything the shadow
145
+ * still had is dropped with one `truncateAfter` on the last common id (or
146
+ * one `remove` per message when there is no common prefix) and everything
147
+ * `next` has is appended with `put`, in order. Applying the result with
148
+ * {@link reduceTranscriptChanges} reproduces `next` exactly, including
149
+ * order, for any two lists.
150
+ */
151
+ export declare function diffTranscript(shadow: TranscriptShadow, next: readonly UIMessage[], options?: {
152
+ nonFinalIds?: ReadonlySet<string>;
153
+ }): {
154
+ changes: TranscriptChange[];
155
+ shadow: TranscriptShadow;
156
+ };
157
+ /**
158
+ * The built-in storage: the whole transcript as one versioned blob in the
159
+ * platform's object store, the same blob the Sessions dashboard renders.
160
+ *
161
+ * Stateless. Every changeset carries the whole transcript as it stands after
162
+ * the changes, so `save` serialises that and rewrites the blob: one PUT per
163
+ * turn, no read and nothing kept in memory between saves.
164
+ */
165
+ export declare function snapshotTranscriptStorage(): TranscriptStorage<unknown>;
166
+ /** The storage `chat.agent` uses when none is configured: {@link snapshotTranscriptStorage}. */
167
+ export declare const defaultStorage: TranscriptStorage<unknown>;
168
+ export type MemoryTranscriptStorage = TranscriptStorage<unknown> & {
169
+ /** The stored transcript for a chat, or `undefined` when nothing has been saved. */
170
+ transcript(chatId: string): (TranscriptState & {
171
+ cursors?: TranscriptCursors;
172
+ }) | undefined;
173
+ /** Every changeset `save` received, in order. */
174
+ readonly changesets: Array<{
175
+ ctx: TranscriptStorageContext<unknown>;
176
+ changeset: TranscriptChangeset;
177
+ }>;
178
+ };
179
+ /**
180
+ * A storage that keeps every transcript in process memory. The reference
181
+ * implementation for the conformance tests, and a way to inspect exactly
182
+ * what the runtime hands a storage.
183
+ */
184
+ export declare function memoryTranscriptStorage(): MemoryTranscriptStorage;
185
+ type ModelLaneInjection = {
186
+ afterId: string;
187
+ messages: ModelMessage[];
188
+ };
189
+ /**
190
+ * What the runtime keeps in the storage's `state` slot: the parts of the
191
+ * model's context that cannot be rebuilt from the transcript. Opaque to a
192
+ * storage; only the runtime reads it.
193
+ *
194
+ * `compaction` is the whole model lane after a compaction, valid for the
195
+ * transcript prefix ending at `throughId` whose fingerprint matches, so a
196
+ * rollback or edit of that prefix makes it unusable and the next save
197
+ * clears it. `injections` are conversational messages `chat.inject` added,
198
+ * anchored after the transcript message they followed.
199
+ */
200
+ export type TranscriptRuntimeState = {
201
+ v: 1;
202
+ compaction?: {
203
+ modelMessages: ModelMessage[];
204
+ throughId: string;
205
+ fingerprint: string;
206
+ };
207
+ injections?: ModelLaneInjection[];
208
+ /** `chat.inject` messages queued but not yet drained into a turn when the save happened. */
209
+ queued?: ModelMessage[];
210
+ };
211
+ export declare function parseTranscriptRuntimeState(value: unknown): TranscriptRuntimeState | undefined;
212
+ /**
213
+ * A 32-bit FNV-1a hash over the fingerprints of the messages up to and
214
+ * including `throughId`, in order. Cheap enough to compute on every save
215
+ * because the per-message fingerprints already exist in the shadow.
216
+ */
217
+ export declare function prefixFingerprint(shadow: TranscriptShadow, throughId: string): string;
218
+ /**
219
+ * Rebuild the model lane for a transcript at boot. Uses the persisted
220
+ * compacted lane when the transcript prefix it covers is unchanged, then
221
+ * converts the rest of the transcript, re-inserting persisted injections
222
+ * after the messages they followed.
223
+ */
224
+ export declare function restoreModelLane<TUIMessage extends UIMessage>(messages: TUIMessage[], state: TranscriptRuntimeState | undefined, convert: (messages: TUIMessage[]) => Promise<ModelMessage[]>): Promise<{
225
+ messages: ModelMessage[];
226
+ compacted: boolean;
227
+ injections: ModelLaneInjection[];
228
+ }>;
229
+ export {};
@@ -0,0 +1,309 @@
1
+ import { apiClientManager, pageTranscriptEntries, } from "@trigger.dev/core/v3";
2
+ import { readChatSnapshot, writeChatSnapshot } from "./chatSnapshotIo.js";
3
+ export function emptyTranscriptState() {
4
+ return { entries: [], state: null };
5
+ }
6
+ /**
7
+ * Apply changes to a transcript, in order. Pure: returns a new state and
8
+ * never mutates the input. Replaying the same changes converges, which is
9
+ * what lets a storage retry a failed write without checking what landed.
10
+ */
11
+ export function reduceTranscriptChanges(prev, changes) {
12
+ let entries = prev.entries.slice();
13
+ let state = prev.state;
14
+ for (const change of changes) {
15
+ switch (change.op) {
16
+ case "put": {
17
+ const message = change.message;
18
+ const entry = { id: message.id, final: change.final ?? true, message };
19
+ const idx = entries.findIndex((e) => e.id === message.id);
20
+ if (idx === -1)
21
+ entries.push(entry);
22
+ else
23
+ entries[idx] = entry;
24
+ break;
25
+ }
26
+ case "remove": {
27
+ entries = entries.filter((e) => e.id !== change.id);
28
+ break;
29
+ }
30
+ case "truncateAfter": {
31
+ const idx = entries.findIndex((e) => e.id === change.afterId);
32
+ if (idx !== -1)
33
+ entries = entries.slice(0, idx + 1);
34
+ break;
35
+ }
36
+ case "state": {
37
+ state = change.value ?? null;
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ return { entries, state };
43
+ }
44
+ function fingerprintMessage(message) {
45
+ return JSON.stringify(message);
46
+ }
47
+ /**
48
+ * Build the shadow for `messages`. An id is non-final when this save says so
49
+ * (`nonFinalIds`) or when it was non-final before and its content has not
50
+ * changed: a partial answer stays partial until the message itself changes.
51
+ */
52
+ export function createTranscriptShadow(messages, nonFinalIds, previous) {
53
+ const ids = [];
54
+ const fingerprints = new Map();
55
+ const nonFinal = new Set();
56
+ for (const message of messages) {
57
+ const fingerprint = fingerprintMessage(message);
58
+ ids.push(message.id);
59
+ fingerprints.set(message.id, fingerprint);
60
+ if (nonFinalIds?.has(message.id) ||
61
+ (previous?.nonFinal.has(message.id) && previous.fingerprints.get(message.id) === fingerprint)) {
62
+ nonFinal.add(message.id);
63
+ }
64
+ }
65
+ return { ids, fingerprints, nonFinal };
66
+ }
67
+ /**
68
+ * Derive the changes that take `shadow` to `next`.
69
+ *
70
+ * The common prefix (by id) is compared message by message and a changed
71
+ * message becomes an in-place `put`. Past the prefix, everything the shadow
72
+ * still had is dropped with one `truncateAfter` on the last common id (or
73
+ * one `remove` per message when there is no common prefix) and everything
74
+ * `next` has is appended with `put`, in order. Applying the result with
75
+ * {@link reduceTranscriptChanges} reproduces `next` exactly, including
76
+ * order, for any two lists.
77
+ */
78
+ export function diffTranscript(shadow, next, options = {}) {
79
+ const nextShadow = createTranscriptShadow(next, options.nonFinalIds, shadow);
80
+ const changes = [];
81
+ const put = (message) => {
82
+ const final = nextShadow.nonFinal.has(message.id) ? false : undefined;
83
+ changes.push(final === undefined ? { op: "put", message } : { op: "put", message, final });
84
+ };
85
+ let lcp = 0;
86
+ while (lcp < shadow.ids.length && lcp < next.length && shadow.ids[lcp] === next[lcp].id) {
87
+ lcp++;
88
+ }
89
+ for (let i = 0; i < lcp; i++) {
90
+ const message = next[i];
91
+ if (shadow.fingerprints.get(message.id) !== nextShadow.fingerprints.get(message.id) ||
92
+ shadow.nonFinal.has(message.id) !== nextShadow.nonFinal.has(message.id)) {
93
+ put(message);
94
+ }
95
+ }
96
+ if (shadow.ids.length > lcp) {
97
+ if (lcp > 0) {
98
+ changes.push({ op: "truncateAfter", afterId: shadow.ids[lcp - 1] });
99
+ }
100
+ else {
101
+ for (const id of shadow.ids)
102
+ changes.push({ op: "remove", id });
103
+ }
104
+ }
105
+ for (let i = lcp; i < next.length; i++) {
106
+ put(next[i]);
107
+ }
108
+ return { changes, shadow: nextShadow };
109
+ }
110
+ /**
111
+ * The built-in storage: the whole transcript as one versioned blob in the
112
+ * platform's object store, the same blob the Sessions dashboard renders.
113
+ *
114
+ * Stateless. Every changeset carries the whole transcript as it stands after
115
+ * the changes, so `save` serialises that and rewrites the blob: one PUT per
116
+ * turn, no read and nothing kept in memory between saves.
117
+ */
118
+ export function snapshotTranscriptStorage() {
119
+ return {
120
+ async load(scope, opts) {
121
+ if (opts?.limit !== undefined || opts?.before !== undefined) {
122
+ const page = await readTranscriptPage(scope.chatId, opts);
123
+ if (page)
124
+ return page;
125
+ }
126
+ const snapshot = await readChatSnapshot(scope.chatId);
127
+ const full = snapshot
128
+ ? { entries: snapshot.messages, state: snapshot.state }
129
+ : emptyTranscriptState();
130
+ return {
131
+ ...pageEntries(full.entries, opts),
132
+ state: full.state,
133
+ cursors: snapshot
134
+ ? { lastOutEventId: snapshot.lastOutEventId, lastInEventId: snapshot.lastInEventId }
135
+ : undefined,
136
+ };
137
+ },
138
+ async save(ctx, changeset) {
139
+ await writeChatSnapshot(ctx.chatId, {
140
+ version: 2,
141
+ savedAt: Date.now(),
142
+ messages: changeset.transcript.entries,
143
+ state: changeset.transcript.state,
144
+ lastOutEventId: changeset.cursors?.lastOutEventId,
145
+ lastInEventId: changeset.cursors?.lastInEventId,
146
+ });
147
+ },
148
+ };
149
+ }
150
+ /**
151
+ * A page of the platform transcript read server-side, for a render read with
152
+ * `limit`/`before` from an app server holding a secret key. `undefined` when
153
+ * the call is not possible from here (a run's public token cannot use the
154
+ * endpoint), so the caller falls back to reading the whole blob.
155
+ */
156
+ async function readTranscriptPage(chatId, opts) {
157
+ try {
158
+ const page = await apiClientManager.clientOrThrow().getSessionTranscript(chatId, opts);
159
+ return {
160
+ messages: page.messages,
161
+ state: page.state ?? null,
162
+ cursors: page.cursors,
163
+ nextCursor: page.nextCursor,
164
+ };
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ /** The storage `chat.agent` uses when none is configured: {@link snapshotTranscriptStorage}. */
171
+ export const defaultStorage = snapshotTranscriptStorage();
172
+ function pageEntries(all, opts) {
173
+ const page = pageTranscriptEntries(all, opts);
174
+ return {
175
+ messages: page.entries.map((e) => e.message),
176
+ nextCursor: page.nextCursor,
177
+ nonFinalIds: page.entries.filter((e) => !e.final).map((e) => e.id),
178
+ };
179
+ }
180
+ /**
181
+ * A storage that keeps every transcript in process memory. The reference
182
+ * implementation for the conformance tests, and a way to inspect exactly
183
+ * what the runtime hands a storage.
184
+ */
185
+ export function memoryTranscriptStorage() {
186
+ const transcripts = new Map();
187
+ const changesets = [];
188
+ return {
189
+ changesets,
190
+ transcript(chatId) {
191
+ return transcripts.get(chatId);
192
+ },
193
+ async load(scope, opts) {
194
+ const stored = transcripts.get(scope.chatId);
195
+ if (!stored)
196
+ return { messages: [], state: null, cursors: undefined, nextCursor: undefined };
197
+ return {
198
+ ...pageEntries(stored.entries, opts),
199
+ state: stored.state,
200
+ cursors: stored.cursors,
201
+ };
202
+ },
203
+ async save(ctx, changeset) {
204
+ changesets.push({ ctx, changeset });
205
+ const prev = transcripts.get(ctx.chatId);
206
+ const next = reduceTranscriptChanges(prev ?? emptyTranscriptState(), changeset.changes);
207
+ transcripts.set(ctx.chatId, { ...next, cursors: changeset.cursors ?? prev?.cursors });
208
+ },
209
+ };
210
+ }
211
+ export function parseTranscriptRuntimeState(value) {
212
+ if (!value || typeof value !== "object")
213
+ return undefined;
214
+ const record = value;
215
+ if (record.v !== 1)
216
+ return undefined;
217
+ const out = { v: 1 };
218
+ const compaction = record.compaction;
219
+ if (compaction &&
220
+ typeof compaction === "object" &&
221
+ Array.isArray(compaction.modelMessages) &&
222
+ typeof compaction.throughId === "string" &&
223
+ typeof compaction.fingerprint === "string") {
224
+ out.compaction = {
225
+ modelMessages: compaction.modelMessages,
226
+ throughId: compaction.throughId,
227
+ fingerprint: compaction.fingerprint,
228
+ };
229
+ }
230
+ if (Array.isArray(record.injections)) {
231
+ out.injections = record.injections.flatMap((entry) => {
232
+ const inj = entry;
233
+ return inj && typeof inj.afterId === "string" && Array.isArray(inj.messages)
234
+ ? [{ afterId: inj.afterId, messages: inj.messages }]
235
+ : [];
236
+ });
237
+ }
238
+ if (Array.isArray(record.queued) && record.queued.length > 0) {
239
+ out.queued = record.queued;
240
+ }
241
+ return out;
242
+ }
243
+ /**
244
+ * A 32-bit FNV-1a hash over the fingerprints of the messages up to and
245
+ * including `throughId`, in order. Cheap enough to compute on every save
246
+ * because the per-message fingerprints already exist in the shadow.
247
+ */
248
+ export function prefixFingerprint(shadow, throughId) {
249
+ let hash = 0x811c9dc5;
250
+ if (throughId === "")
251
+ return hash.toString(16).padStart(8, "0");
252
+ const mix = (s) => {
253
+ for (let i = 0; i < s.length; i++) {
254
+ hash ^= s.charCodeAt(i);
255
+ hash = Math.imul(hash, 0x01000193) >>> 0;
256
+ }
257
+ };
258
+ for (const id of shadow.ids) {
259
+ mix(id);
260
+ mix("");
261
+ mix(shadow.fingerprints.get(id) ?? "");
262
+ mix("");
263
+ if (id === throughId)
264
+ return hash.toString(16).padStart(8, "0");
265
+ }
266
+ return "";
267
+ }
268
+ /**
269
+ * Rebuild the model lane for a transcript at boot. Uses the persisted
270
+ * compacted lane when the transcript prefix it covers is unchanged, then
271
+ * converts the rest of the transcript, re-inserting persisted injections
272
+ * after the messages they followed.
273
+ */
274
+ export async function restoreModelLane(messages, state, convert) {
275
+ const lane = [];
276
+ let start = 0;
277
+ let compacted = false;
278
+ if (state?.compaction) {
279
+ const throughId = state.compaction.throughId;
280
+ const idx = throughId === "" ? -1 : messages.findIndex((m) => m.id === throughId);
281
+ if ((idx !== -1 || throughId === "") &&
282
+ prefixFingerprint(createTranscriptShadow(messages), throughId) ===
283
+ state.compaction.fingerprint) {
284
+ lane.push(...state.compaction.modelMessages);
285
+ start = idx + 1;
286
+ compacted = true;
287
+ }
288
+ }
289
+ const anchored = (state?.injections ?? [])
290
+ .map((inj) => ({
291
+ inj,
292
+ idx: inj.afterId === "" ? -1 : messages.findIndex((m) => m.id === inj.afterId),
293
+ }))
294
+ .filter(({ inj, idx }) => (inj.afterId === "" ? start === 0 : idx >= start))
295
+ .sort((a, b) => a.idx - b.idx);
296
+ let cursor = start;
297
+ for (const { inj, idx } of anchored) {
298
+ if (idx + 1 > cursor) {
299
+ lane.push(...(await convert(messages.slice(cursor, idx + 1))));
300
+ cursor = idx + 1;
301
+ }
302
+ lane.push(...inj.messages);
303
+ }
304
+ if (cursor < messages.length) {
305
+ lane.push(...(await convert(messages.slice(cursor))));
306
+ }
307
+ return { messages: lane, compacted, injections: anchored.map(({ inj }) => inj) };
308
+ }
309
+ //# sourceMappingURL=transcriptStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcriptStorage.js","sourceRoot":"","sources":["../../../src/v3/transcriptStorage.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export const VERSION = "0.0.0-prerelease-20260908122921";
1
+ export const VERSION = "0.0.0-prerelease-20260909071038";
2
2
  //# sourceMappingURL=version.js.map
@@ -83,9 +83,9 @@ onAction: async ({ action }) => {
83
83
 
84
84
  An action that returns nothing does not fire `onTurnComplete`, and that is where an app that owns its own transcript normally writes. What that means depends on which persistence model you use.
85
85
 
86
- **Platform-managed** (no `hydrateMessages`): nothing to do. After an action that changed the conversation, the runtime writes the snapshot, so the edit survives the run ending. An action that returns `chat.turn()` is followed by a turn, which persists its answer the way every turn does.
86
+ **Transcript storage** (the default, or your own `storage`): nothing to do. After an action that changed the conversation, the runtime hands the storage a changeset with `reason: "action"`. An undo is one `truncateAfter`; a regenerate is a `truncateAfter` followed by the new answer's `put` when the turn completes; an edit is a `put` for the edited id. The changeset carries the same resume cursors as the last turn. See [Transcript storage](/ai-chat/transcript-storage#what-the-runtime-saves). An action that returns `chat.turn()` is followed by a turn, which persists its answer the way every turn does.
87
87
 
88
- **Your own store** (`hydrateMessages` registered): the runtime deliberately does not write, because your store is the source of truth. A history edit lives only in the running worker until you persist it, and a continuation rehydrates from your store, not from what the worker had in memory. Mirror each edit in your store, not only additions: a regenerate is a delete *and* an insert. The answer that follows `chat.turn()` reaches your store through `onTurnComplete`, like any turn's answer.
88
+ **Your own store through the deprecated `hydrateMessages`**: the runtime deliberately does not write, because your store is the source of truth. A history edit lives only in the running worker until you persist it, and a continuation rehydrates from your store, not from what the worker had in memory. Mirror each edit in your store, not only additions: a regenerate is a delete *and* an insert. The answer that follows `chat.turn()` reaches your store through `onTurnComplete`, like any turn's answer.
89
89
 
90
90
  ```ts
91
91
  onAction: async ({ action, chatId }) => {
@@ -250,6 +250,8 @@ chat.inject(messages: ModelMessage[]): void
250
250
 
251
251
  Queue model messages for injection at the next opportunity. Messages persist across the idle wait between turns, and are not reset when a new turn starts.
252
252
 
253
+ Lifetime: a conversational message (`role: "user"` or `"assistant"`) becomes part of the model's context from the next turn onward, for the rest of the conversation. It is written to the [transcript storage](/ai-chat/transcript-storage)'s `state`, anchored to the message it followed, so it survives a continuation run and comes back in the same place. It does not appear in the UI transcript. A history edit that rebuilds the context drops it. This holds however the message reached the model: drained before `run()` or at a step boundary inside a multi-step turn. A message that is still queued when the run ends (injected from the last `onTurnComplete` before an exit, for example) is carried in the storage's `state` too and is queued again when the next run boots, so it reaches the next turn. A `role: "system"` message is appended to the instructions for the next turn only and is consumed once. Injecting the same notice every turn adds a copy every turn; dedupe on your side.
254
+
253
255
  **Parameters:**
254
256
 
255
257
  | Parameter | Type | Description |
@@ -61,6 +61,8 @@ After each turn completes:
61
61
 
62
62
  On the next turn, the LLM receives the compact summary instead of the full history — dramatically reducing token usage while preserving context.
63
63
 
64
+ The compacted context is durable. The runtime writes it to the [transcript storage](/ai-chat/transcript-storage)'s `state` alongside the messages, so a new run that boots to continue the conversation starts from the summary rather than re-reading the whole transcript and summarising it again. An undo or edit that reaches into the summarised part of the conversation clears the stored summary, and compaction runs again from the edited history when the threshold is next crossed.
65
+
64
66
  <Note>
65
67
  This is Trigger.dev's provider-agnostic compaction. To persist a **provider's own** compaction across turns instead (Anthropic context editing or OpenAI stored responses), and to fall back between providers without re-sending history, see [Native compaction & provider fallback](/ai-chat/patterns/native-compaction).
66
68
  </Note>
@@ -137,6 +137,8 @@ On page load, fetch both the messages and the session state from your database,
137
137
 
138
138
  Because the underlying Session row outlives individual runs, a chat you were in yesterday resumes against the same chat — even if the original run has long since exited. The transport hydrates from the persisted state and uses `lastEventId` to resubscribe; if the client tries to send a new message and no run is alive, the server triggers a fresh continuation run on the same session before the message is appended.
139
139
 
140
+ If you do not keep your own copy of the conversation, load it from the agent's [transcript storage](/ai-chat/transcript-storage#reading-the-transcript) instead: `chat.createLoadTranscriptAction(storage)` on the server and `useLoadTranscript(chatId, action, { transport })` in the browser return the messages and seed the transport's resume cursor, for the default storage and your own alike.
141
+
140
142
  ```tsx app/chat/[chatId]/ChatPage.tsx
141
143
  "use client";
142
144
 
@@ -86,7 +86,7 @@ export const myChat = chat.agent({
86
86
 
87
87
  Fires once on a continuation boot when the dead predecessor was mid-stream — a partial assistant survives on `session.out`. The runtime reconstructs context automatically via a smart default; this hook is the override path for policies that need something different.
88
88
 
89
- The hook does NOT fire when there's no partial — clean continuations after `chat.endRun()` or `chat.requestUpgrade()`, fresh chats, OOM retries on top of a complete snapshot. Those paths dispatch any in-flight user message as a normal turn on the new run without involving the hook. It also does NOT fire when [`hydrateMessages`](#hydratemessages) is registered (the customer owns persistence).
89
+ The hook does NOT fire when there's no partial — clean continuations after `chat.endRun()` or `chat.requestUpgrade()`, fresh chats, OOM retries on top of a complete snapshot. Those paths dispatch any in-flight user message as a normal turn on the new run without involving the hook. It fires regardless of whether [`hydrateMessages`](#hydratemessages) or a [transcript storage](/ai-chat/transcript-storage) is registered: crash recovery is runtime-owned for every agent.
90
90
 
91
91
  ```ts
92
92
  export const myChat = chat.agent({
@@ -260,6 +260,10 @@ export const myChat = chat.agent({
260
260
 
261
261
  ## hydrateMessages
262
262
 
263
+ <Warning>
264
+ `hydrateMessages` is deprecated. Give the agent a [transcript storage](/ai-chat/transcript-storage) instead: `loadContext` on the storage decides the model's context, and `save` persists every change, so crash recovery and durable compaction cover it. Existing agents keep working with a one-time warning. Setting `hydrateMessages` together with `storage` is an error.
265
+ </Warning>
266
+
263
267
  Load the full message history from your backend on every turn, replacing the built-in linear accumulator. When set, the hook's return value becomes the accumulated state; the normal accumulation logic (append for submit, replace for regenerate) is skipped entirely.
264
268
 
265
269
  Use this when the backend should be the source of truth for message history: abuse prevention, branching conversations (DAGs), or rollback/undo support.
@@ -315,7 +319,7 @@ After the hook returns, the runtime overlays the wire's tool-state advances (`ou
315
319
  </Note>
316
320
 
317
321
  <Tip>
318
- Registering `hydrateMessages` short-circuits the runtime's [snapshot + replay](/ai-chat/patterns/persistence-and-replay) reconstruction at run boot — your hook is the single source of truth for history, so the runtime skips reading or writing the snapshot entirely. No object storage traffic, no replay cost. The trade-off is that you own persistence end-to-end.
322
+ Registering `hydrateMessages` turns off the runtime's transcript reads and writes: your hook is the source of truth for history, and the runtime does not read or write its [snapshot](/ai-chat/patterns/persistence-and-replay). Crash recovery still runs. When a new run boots, the dead run's unfinished answer and unacknowledged messages are replayed from the session streams, `onRecoveryBoot` fires, and the hook receives the recovered tail in `previousMessages`. Persisting that tail is yours to do; a [transcript storage](/ai-chat/transcript-storage) does it for you.
319
323
  </Tip>
320
324
 
321
325
  <Note>