@guuey/threads 0.31.1 → 0.33.0

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/README.md CHANGED
@@ -79,6 +79,87 @@ and Cognito sessions are served on-platform only. Errors surface as
79
79
  `HttpThreadStoreError` (`status`, `code`). Reads retry once on a network
80
80
  failure or 5xx; writes never do.
81
81
 
82
+ ## Row size limits
83
+
84
+ A store with an item-size limit declares it as the port's optional
85
+ `rowBudget` (`{ maxBytes, bytesOf(row), numberFits?(n) }`). The DynamoDB-backed
86
+ bindings, `HttpThreadPersistence` included, declare `DYNAMODB_ROW_BUDGET`:
87
+ 380,000 bytes measured by `dynamoItemBytes`, an upper bound under DynamoDB's
88
+ own item-size rules, and `dynamoNumberFits`, which refuses a number DynamoDB
89
+ cannot hold as written (outside the safe-integer range, NaN or an infinity, or
90
+ a non-zero magnitude below 1e-130). A port without a budget stores rows exactly
91
+ as mapped.
92
+
93
+ `appendFold` checks every row of a turn against the budget before it writes any:
94
+
95
+ - A value is stored as a marker in its place,
96
+ `{ "com.guuey/elided": { "bytes": <UTF-8 length of its JSON> } }`. A
97
+ tool-call value is, when it carries a number the store cannot hold
98
+ (`reason: "number"`). An oversized row leaves values out until it fits
99
+ (`reason: "size"`), the largest first within each step: first a
100
+ tool-result's `uiData` that is an inline UI resource with text
101
+ (`{ uri, text }`) when a card row of the same turn carries the same value
102
+ for the same call (`field: "uiData"`; the card row holds the resource, and
103
+ the thread's readers draw the card from it), then each
104
+ `providerMetadata.wireInput` copy, then each `input`. Text blocks are never
105
+ elided. The result lists each one in `elidedToolInputs`.
106
+ - A turn that still cannot be stored is never written in part. With
107
+ `unstorableNotice: { text }` the turn is stored as ONE agent row carrying that
108
+ text and `content.error.code` `"stored_turn_too_large"` or
109
+ `"stored_turn_unstorable_number"` (`classifyStoredRow` reads either as a
110
+ failure), and the result carries `unstorable`. Without it, `appendFold`
111
+ throws `StoredRowTooLargeError` or `StoredRowUnstorableNumberError` and
112
+ writes nothing.
113
+
114
+ `appendMessage` refuses such a row the same way before it allocates a seq.
115
+ `withItemSizeLimit(port)` from `@guuey/threads/testing` wraps any port to
116
+ declare the budget and refuse what DynamoDB would (on `putMessage` and
117
+ `putSnapshot`), for tests.
118
+
119
+ The turn's snapshot is planned under the same budget, also before any row is
120
+ written (`planSnapshot`), so the snapshot write never fails after the rows
121
+ landed:
122
+
123
+ - A `workingState` that carries a number the store cannot hold is not stored.
124
+ The snapshot keeps `priorWorkingState` (the state the turn was seeded with),
125
+ or no state when none is given.
126
+ - A thread-memory element that carries such a number is left out.
127
+ - Over the budget, the turn's state is refused first, then the memory records
128
+ this turn wrote are left out, the largest first. Records stored unchanged
129
+ from `priorThreadMemory`, and carried elements, stay.
130
+ - A snapshot that still does not fit is not written, and the prior one stands.
131
+
132
+ The result's `refusedSnapshot` (`{ state?, memoryNumber, memorySize,
133
+ notWritten }`) reports what was left out, as counts.
134
+
135
+ ## Sealed messages
136
+
137
+ A thread row may carry `sealIntervals`: stretches of messages (`fromSeq` to
138
+ `toSeq`, both inclusive; no `toSeq` = still open) that the app's operator may
139
+ not read. A reader that shows conversation data to the operator checks them
140
+ with the exported helpers, never by hand:
141
+
142
+ ```ts
143
+ import { isSeqSealed, readableThreadPreview, readSealState } from "@guuey/threads";
144
+
145
+ const seal = readSealState(row.sealIntervals); // absent = legacy, wholly readable
146
+ if (isSeqSealed(seal, message.seq)) {
147
+ /* withhold this message */
148
+ }
149
+ const preview = readableThreadPreview(row); // '' when its source message is sealed
150
+ ```
151
+
152
+ A seal record that is present but malformed reads as wholly sealed. Each
153
+ preview records the seq it was taken from (`lastMessagePreviewSeq`) and a
154
+ digest of its text (`lastMessagePreviewDigest`, `previewDigest(preview)`) in
155
+ the same update. On a thread with any sealed part, a preview whose seq is
156
+ missing, or whose digest is missing or does not match its text, is withheld. A
157
+ binding that implements the port writes both with the preview.
158
+
159
+ An event row (a hand-off, a report) leaves the preview as it was, like a card
160
+ row: it is written from the whole conversation, so it never becomes the
161
+ thread's preview.
162
+
82
163
  ## Support
83
164
 
84
165
  Questions and help: the guuey community on Discord — https://guuey.com/discord.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A closed conversation (see {@link ThreadRow.closedAt}): how a stored row
3
+ * reads as closed, and the error a binding answers an append on one with.
4
+ */
5
+ import type { ThreadClosure, ThreadRow } from "./rows.js";
6
+ /**
7
+ * The thread's closure, or `undefined` while it is open. `closedAt` alone
8
+ * decides: a row without it (every row written before closing existed) is
9
+ * open, and a row with it is closed even when its reason did not land
10
+ * (`code`/`message` then read as `""`).
11
+ */
12
+ export declare function threadClosure(row: ThreadRow): ThreadClosure | undefined;
13
+ /**
14
+ * A binding's answer to `incrementSeq` on a closed thread: the conversation
15
+ * takes no new message. A caller that meant to continue it starts a new one.
16
+ */
17
+ export declare class ThreadClosedError extends Error {
18
+ readonly threadId: string;
19
+ readonly code = "THREAD_CLOSED";
20
+ constructor(threadId: string);
21
+ }
22
+ //# sourceMappingURL=closed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"closed.d.ts","sourceRoot":"","sources":["../src/closed.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAOvE;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAE9B,QAAQ,CAAC,QAAQ,EAAE,MAAM;IADrC,QAAQ,CAAC,IAAI,mBAAmB;gBACX,QAAQ,EAAE,MAAM;CAItC"}
package/dist/closed.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The thread's closure, or `undefined` while it is open. `closedAt` alone
3
+ * decides: a row without it (every row written before closing existed) is
4
+ * open, and a row with it is closed even when its reason did not land
5
+ * (`code`/`message` then read as `""`).
6
+ */
7
+ export function threadClosure(row) {
8
+ if (typeof row.closedAt !== "string" || row.closedAt.length === 0)
9
+ return undefined;
10
+ return {
11
+ code: typeof row.closedCode === "string" ? row.closedCode : "",
12
+ message: typeof row.closedMessage === "string" ? row.closedMessage : "",
13
+ at: row.closedAt,
14
+ };
15
+ }
16
+ /**
17
+ * A binding's answer to `incrementSeq` on a closed thread: the conversation
18
+ * takes no new message. A caller that meant to continue it starts a new one.
19
+ */
20
+ export class ThreadClosedError extends Error {
21
+ threadId;
22
+ code = "THREAD_CLOSED";
23
+ constructor(threadId) {
24
+ super(`thread ${threadId} is closed`);
25
+ this.threadId = threadId;
26
+ this.name = "ThreadClosedError";
27
+ }
28
+ }
@@ -5,7 +5,7 @@
5
5
  * docs/superpowers/specs/2026-06-23-agjson-persistence-fold-design.md §6–§9.
6
6
  */
7
7
  import type { AgMessage, AgArtifact, AgTurnRecord, AgReduceResult, AgEvent, AgMemoryRecord, JsonValue } from "@silverprotocol/core";
8
- import type { ThreadMessageRow, ThreadSnapshotRow } from "./rows.js";
8
+ import type { RowBudget, ThreadMessageRow, ThreadSnapshotRow } from "./rows.js";
9
9
  export interface RowCtx {
10
10
  threadId: string;
11
11
  userId: string;
@@ -37,6 +37,12 @@ export declare function messageNarration(msg: AgMessage): string[];
37
37
  * bytes whether a row was written before the split or after it.
38
38
  */
39
39
  export declare function messageFullText(msg: AgMessage): string;
40
+ /**
41
+ * A stored row's `text` as storage can hand it to a reader: a string, absent, or NULL. The row type
42
+ * says `string | undefined`, but a raw DynamoDB read cast to it can carry a NULL attribute, so the
43
+ * readers that take the row's text accept null and read it as no text.
44
+ */
45
+ export type StoredRowText = string | null | undefined;
40
46
  /** A stored message's text, split: the answer, the narration, and everything (the model's view). */
41
47
  export interface StoredTextParts {
42
48
  answer: string;
@@ -47,13 +53,261 @@ export interface StoredTextParts {
47
53
  * Split a stored row's `content` into answer, narration and the model's full
48
54
  * text, when `content` is a stored `AgMessage` (an object, or the JSON string
49
55
  * an AppSync writer stores). Anything else is null, and a reader falls back to
50
- * the row's `text`. It is read through core's lenient stored-record reader, so
51
- * an unknown block is omitted rather than failing the row.
56
+ * the row's `text`. The message is read by {@link readStoredRowMessage}, so an
57
+ * unknown block is omitted rather than failing the row, and an elided answer
58
+ * reads from the row's `text`. `text` is required so that every caller passes
59
+ * the row's `text` on purpose (`undefined` or null when the row has none).
60
+ */
61
+ export declare function storedTextParts(content: unknown, text: StoredRowText): StoredTextParts | null;
62
+ /** A tool-result block as a fold stores it. */
63
+ export type StoredToolResultBlock = Extract<AgMessage["content"][number], {
64
+ type: "tool-result";
65
+ }>;
66
+ /**
67
+ * A stored tool call as a reader serves it (guuey#2388): its id, the tool's
68
+ * raw wire name (guuey#402), its display title when the producer gave one,
69
+ * and its input, exactly what a host's live tool-call row reads. Built field
70
+ * by field, never core's block: core's call also carries provider and
71
+ * transport fields (`providerMetadata`, whose `wireInput` is a second copy of
72
+ * the input, `_meta`, `signature`, `toolMetadata`, `uiVisibility` and more)
73
+ * that a reader must not serve, and a served view only ever gains fields.
74
+ */
75
+ export interface StoredToolCallView {
76
+ toolCallId: string;
77
+ name: string;
78
+ title?: string;
79
+ input: JsonValue;
80
+ }
81
+ /**
82
+ * The tool calls a stored row's message makes, in block order, as
83
+ * {@link StoredToolCallView}s (guuey#2388). The name rides only the call; a
84
+ * result carries just the id, and a facet may store the result in a message
85
+ * (and so a row) of its own, so a reader that names a result reads the calls
86
+ * of the rows around it (guuey#2487). Empty when `content` is not a stored
87
+ * message.
88
+ */
89
+ export declare function storedToolCallViews(content: unknown, text: StoredRowText): StoredToolCallView[];
90
+ /**
91
+ * The tool results a stored row's message carries that NO card row stands for
92
+ * (guuey#2487): every `tool-result` block that does not carry a UI resource
93
+ * ({@link toolResultCarriesUi}), without its `_meta` (guuey#122), in block
94
+ * order. A host draws from such a result (an in-process tool's
95
+ * `structuredContent`, or its text) live; this is how a reader finds it again
96
+ * after a reload. A UI-carrying result is left out: its card row already
97
+ * carries it. Empty when `content` is not a stored message.
98
+ */
99
+ export declare function storedToolResults(content: unknown, text: StoredRowText): StoredToolResultBlock[];
100
+ /**
101
+ * The call id of EVERY `tool-result` block a stored row's message carries, in
102
+ * block order, whether it carries a UI resource or not (guuey#2487). A reader
103
+ * that names results by their calls uses it to tell where a call's results
104
+ * sit on a page, card-drawn ones included: a call's first result row is
105
+ * written straight after the call row, and a UI-carrying result's card row
106
+ * straight after the result (guuey#2396). Empty when `content` is not a
107
+ * stored message.
108
+ */
109
+ export declare function storedToolResultCallIds(content: unknown, text: StoredRowText): string[];
110
+ /**
111
+ * A row's stored message as every reader of a row reads it ({@link storedTextParts},
112
+ * {@link rowToAgMessage}, `classifyStoredRow`): `content` (a stored `AgMessage`
113
+ * object) through core's lenient stored-record reader, with an ELIDED ANSWER
114
+ * restored from the row's `text`. `undefined` when core cannot materialize the
115
+ * message. Like core's reader, it returns a view, never a record to persist.
116
+ *
117
+ * A stored message may leave its answer out of `content` because the row's
118
+ * `text` carries the same words: an answer block whose `text` is the
119
+ * {@link ELIDED_VALUE_KEY} marker ({@link isElidedAnswerBlock}). Such a message
120
+ * is rebuilt once, before it is read: the row's `text` (every answer block,
121
+ * joined) becomes the text of the first answer block, and every later answer
122
+ * block is left out, since that `text` already holds it. An answer block here
123
+ * is a block core can read as a text block, not narration, that is elided or
124
+ * non-empty. Every other block stays as stored. When the answer blocks stand
125
+ * together, as a reply's do, the rebuilt message reads the same text as the
126
+ * unelided one.
127
+ *
128
+ * Only the marker reads the row's `text`: a message with no answer blocks reads
129
+ * as it always has, with an empty answer, whatever the row's `text` says. On a
130
+ * row with no `text` (absent, NULL or empty) there is nothing to restore: only
131
+ * the elided blocks are left out and every other block reads as stored, so a
132
+ * stored word is never erased. An elided answer alone then reads as an empty
133
+ * answer in {@link storedTextParts} and `classifyStoredRow`, as its unelided
134
+ * twin with an empty answer would; the rebuilt message leaves the block out.
52
135
  */
53
- export declare function storedTextParts(content: unknown): StoredTextParts | null;
136
+ export declare function readStoredRowMessage(content: unknown, text: StoredRowText): AgMessage | undefined;
137
+ /** A stored text element as a stored message carries it, before core reads it. */
138
+ interface StoredTextElement {
139
+ type: "text";
140
+ text: unknown;
141
+ }
54
142
  export declare function agMessageToRow(msg: AgMessage, ctx: RowCtx & {
55
143
  turnRecord?: AgTurnRecord;
56
144
  }): ThreadMessageRow;
145
+ /**
146
+ * The key of the marker a stored value is replaced by when the row would not
147
+ * fit its store's {@link RowBudget}: `{ "com.guuey/elided": { bytes } }`, where
148
+ * `bytes` is the UTF-8 length of the elided value's JSON. It stands in for the
149
+ * value whole (a tool-call `input`, its `providerMetadata.wireInput`, or a
150
+ * tool-result `uiData` the turn's card row also carries), so the block keeps
151
+ * an object where an object was.
152
+ */
153
+ export declare const ELIDED_VALUE_KEY = "com.guuey/elided";
154
+ /**
155
+ * Which value of a block was elided: a tool-call's `input` or
156
+ * `providerMetadata.wireInput`, or a tool-result's `uiData` (an inline UI
157
+ * resource the turn's card row carries whole).
158
+ */
159
+ export type ElidedField = "input" | "wireInput" | "uiData";
160
+ /**
161
+ * Why a tool-call value was elided: the row did not fit the store's row
162
+ * budget (`size`), or the value carried a number the store cannot hold as
163
+ * written (`number`, see {@link RowBudget.numberFits}).
164
+ */
165
+ export type ElisionReason = "size" | "number";
166
+ /** One stored value replaced by the elided marker. */
167
+ export interface StoredElision {
168
+ toolCallId: string;
169
+ /** The tool's name (for a `uiData` elision, the name its card row stores). */
170
+ name: string;
171
+ field: ElidedField;
172
+ reason: ElisionReason;
173
+ /** UTF-8 length of the elided value's JSON. */
174
+ bytes: number;
175
+ }
176
+ /** The kind of row a stored-row refusal was raised for. */
177
+ export type RefusedRowKind = "message" | "card" | "append";
178
+ /**
179
+ * A row cannot fit its store's {@link RowBudget}, even with every value it
180
+ * carries that may be elided elided. Counts only, never row content.
181
+ */
182
+ export declare class StoredRowTooLargeError extends Error {
183
+ readonly bytes: number;
184
+ readonly maxBytes: number;
185
+ readonly rowKind: RefusedRowKind;
186
+ /** The row's index among the turn's message or card rows (0 for an append). */
187
+ readonly index: number;
188
+ readonly name = "StoredRowTooLargeError";
189
+ constructor(bytes: number, maxBytes: number, rowKind: RefusedRowKind,
190
+ /** The row's index among the turn's message or card rows (0 for an append). */
191
+ index: number);
192
+ }
193
+ /**
194
+ * A row carries a number its store cannot hold as written (see
195
+ * {@link RowBudget.numberFits}) outside any tool-call value that could be
196
+ * elided. Never names the number: it may be part of a tool's input or result.
197
+ */
198
+ export declare class StoredRowUnstorableNumberError extends Error {
199
+ readonly rowKind: RefusedRowKind;
200
+ /** The row's index among the turn's message or card rows (0 for an append). */
201
+ readonly index: number;
202
+ readonly name = "StoredRowUnstorableNumberError";
203
+ constructor(rowKind: RefusedRowKind,
204
+ /** The row's index among the turn's message or card rows (0 for an append). */
205
+ index: number);
206
+ }
207
+ /** Either reason a row cannot be stored: too large, or carrying an unstorable number. */
208
+ export type StoredRowRefusal = StoredRowTooLargeError | StoredRowUnstorableNumberError;
209
+ export declare function isStoredRowRefusal(err: unknown): err is StoredRowRefusal;
210
+ /** Whether `value` (any JSON-shaped data) holds a number `fits` refuses, at any depth. */
211
+ export declare function carriesUnfitNumber(value: unknown, fits: (n: number) => boolean): boolean;
212
+ /**
213
+ * Throw when `row` cannot be stored under `budget`: {@link StoredRowUnstorableNumberError}
214
+ * when it carries a number the store cannot hold, {@link StoredRowTooLargeError}
215
+ * when it measures over the budget. No-op without a budget.
216
+ */
217
+ export declare function assertRowFits(row: ThreadMessageRow, budget: RowBudget | undefined, rowKind: RefusedRowKind, index: number): void;
218
+ /** One tool-result `uiData` a card row of the turn carries, with the tool name that row stores. */
219
+ export interface CardUiData {
220
+ name: string;
221
+ uiData: JsonValue;
222
+ }
223
+ /**
224
+ * What a turn's card rows carry, by tool call: for each `toolCallId`, the
225
+ * `uiData` of every tool-result part of a card row that names its producing
226
+ * tool. A message row's tool-result `uiData` equal to one of these is a second
227
+ * copy of what that card row stores.
228
+ */
229
+ export type CardUiDataByCallId = ReadonlyMap<string, readonly CardUiData[]>;
230
+ /** {@link CardUiDataByCallId} over a turn's card rows (rows of another kind are skipped). */
231
+ export declare function cardUiDataByCallId(cardRows: readonly ThreadMessageRow[]): CardUiDataByCallId;
232
+ /**
233
+ * A stored ANSWER block left out because the row's `text` carries the same
234
+ * words: a `text` block whose `text` is the {@link ELIDED_VALUE_KEY} marker
235
+ * (`{ "com.guuey/elided": { bytes } }`, `bytes` the UTF-8 length of the
236
+ * elided string's JSON) in place of the string. A narration block
237
+ * (`phase: "interim"`) is never one: the row's `text` is the answer only, so
238
+ * it holds no narration to read back. Read on the raw stored element, since
239
+ * core's lenient reader leaves such a block out (its `text` is not a string).
240
+ */
241
+ export declare function isElidedAnswerBlock(element: unknown): element is StoredTextElement;
242
+ /**
243
+ * {@link agMessageToRow}, bounded by the store's {@link RowBudget}. Under the
244
+ * budget (or with none) the row is exactly `agMessageToRow(msg, ctx)`.
245
+ *
246
+ * First, when the budget declares `numberFits`, every tool-call value that
247
+ * carries a number the store cannot hold is replaced by the
248
+ * {@link ELIDED_VALUE_KEY} marker (`reason: "number"`; after `JSON.parse` such
249
+ * a number has already lost its digits, so storing it would store a wrong
250
+ * one). A row that still carries one elsewhere throws
251
+ * {@link StoredRowUnstorableNumberError}.
252
+ *
253
+ * Then, over the size budget, values are replaced one at a time
254
+ * (`reason: "size"`) until the row fits. Pure duplicates go first: a
255
+ * tool-result's `uiData` that is an inline UI resource with text, when a card
256
+ * row of the turn (`cards`, see {@link cardUiDataByCallId}) carries the same
257
+ * value for the same call. Then every `providerMetadata.wireInput` (a second
258
+ * copy of the input, carried only when it differs), then every `input`.
259
+ * Within each tier the largest goes first, ties in block order. A row that
260
+ * still does not fit throws {@link StoredRowTooLargeError}.
261
+ */
262
+ export declare function agMessageToBoundedRow(msg: AgMessage, ctx: RowCtx & {
263
+ turnRecord?: AgTurnRecord;
264
+ }, budget: RowBudget | undefined, index: number, cards?: CardUiDataByCallId): {
265
+ row: ThreadMessageRow;
266
+ elided: StoredElision[];
267
+ };
268
+ /**
269
+ * Whether a tool-result block carries a UI resource, and so becomes a card row
270
+ * ({@link uiCardArtifactsFromMessages}) rather than a stored tool result
271
+ * ({@link storedToolResults}). The one predicate both share, so a result is a
272
+ * card or a stored tool result, never both and never neither.
273
+ *
274
+ * It judges the block AS STORED: without its `_meta` (guuey#122), which never
275
+ * persists. The card projection sees the live block and the stored-row reader
276
+ * sees the stored one, so a predicate that read `_meta` would answer the two
277
+ * differently (a `_meta.ui` locator is visible to the first only). Judging the
278
+ * stored view gives one answer on both sides by construction.
279
+ *
280
+ * A block carries UI only on DURABLE evidence, what a card row needs to mount
281
+ * after a reload. As stored:
282
+ * - its `uiData` is an MCP resource payload (`{ uri, text|blob }`, bare or as
283
+ * `{ resource }`), which mounts inline;
284
+ * - its `uiData` or `structuredContent` names a `ui://` locator, which mounts
285
+ * by a fresh read of the uri (`structuredContent` for a producer that
286
+ * withholds `_meta`, guuey#209);
287
+ * - its `uiData` is the {@link ELIDED_VALUE_KEY} marker, which stands in for
288
+ * an inline resource the turn's card row carries whole
289
+ * ({@link agMessageToBoundedRow});
290
+ * - or a `provider-raw` part carries a `ui://` resource.
291
+ *
292
+ * Anything else is a stored tool result, which a host reads by its tool name.
293
+ * That includes plain view data in `uiData`: AgJSON §2.1 routes a result's
294
+ * payload there whenever the producer set any `_meta.ui` sibling, so an
295
+ * object in `uiData` alone is no evidence that a card can mount. When
296
+ * `_meta.ui` named no `ui://` locator, nothing could ever mount it; when it
297
+ * did, the locator rode `_meta` alone and the writer strips it, so the stored
298
+ * row cannot mount it either. A card row for either would draw as an expired
299
+ * chip on every reload. (A writer that persisted that locator would make it a
300
+ * card here, by this same rule.)
301
+ *
302
+ * `_meta` alone never mints a card (the guuey#170 pin). Across a rolling
303
+ * release: a writer before this predicate minted a card row from a `_meta.ui`
304
+ * locator, so a thread written then holds that card row while this predicate
305
+ * reads the same stored result as one with no UI resource. A reader that
306
+ * serves both kinds keeps one item per result by leaving out a stored tool
307
+ * result whose call already has a card row on its read (the public transcript
308
+ * read does).
309
+ */
310
+ export declare function toolResultCarriesUi(block: StoredToolResultBlock): boolean;
57
311
  export declare function uiCardArtifactsFromMessages(messages: AgMessage[]): AgArtifact[];
58
312
  /**
59
313
  * toolCallId → tool name, read off every `tool-call` block in the fold's
@@ -71,6 +325,21 @@ export declare function toolNamesByCallId(messages: AgMessage[]): Map<string, st
71
325
  * never a guess (humanization and fallbacks are the kit's, one owner).
72
326
  */
73
327
  export declare function producingToolName(art: AgArtifact, toolNames: ReadonlyMap<string, string>): string | undefined;
328
+ /**
329
+ * toolCallId → the index of the fold message that carries that call's
330
+ * `tool-result` block (guuey#2396). A card is drawn when its tool-result
331
+ * arrives, so this is where its card row belongs in the turn's stored order.
332
+ * First message wins on a duplicate id, as in {@link toolNamesByCallId}.
333
+ */
334
+ export declare function toolResultMessageIndexByCallId(messages: AgMessage[]): Map<string, number>;
335
+ /**
336
+ * The index of the fold message a card artifact was drawn with: the message
337
+ * holding the tool-result its first tool-result part names (guuey#2396).
338
+ * `undefined` when unresolvable (a first-class `artifact.*` event with no
339
+ * tool-result part, or a call outside this fold): such a card keeps the
340
+ * turn's tail.
341
+ */
342
+ export declare function producingMessageIndex(art: AgArtifact, resultIndices: ReadonlyMap<string, number>): number | undefined;
74
343
  export declare function agArtifactToCardRow(art: AgArtifact, ctx: RowCtx, toolName?: string): ThreadMessageRow;
75
344
  /**
76
345
  * Reconstruct an AgMessage from a row. Agent-fold rows store the verbatim
@@ -83,7 +352,8 @@ export declare function agArtifactToCardRow(art: AgArtifact, ctx: RowCtx, toolNa
83
352
  * undefined value in a closed set, is omitted whole and the rest restores in
84
353
  * order; unknown fields pass through untouched. Only a record the reader
85
354
  * cannot materialize (no string `id`, an undefined `role`, no `content`
86
- * array) falls to the text projection.
355
+ * array) falls to the text projection. A stored message whose answer block is
356
+ * elided reads its answer from the row's `text` ({@link readStoredRowMessage}).
87
357
  */
88
358
  export declare function rowToAgMessage(row: ThreadMessageRow): AgMessage;
89
359
  /** Extract the AgArtifact from a kind='card' row, or undefined if malformed. */
@@ -126,6 +396,8 @@ export declare function splitStoredThreadMemory(stored: readonly JsonValue[]): P
126
396
  * turn, so write order stays time order.
127
397
  */
128
398
  export declare function joinStoredThreadMemory(row: Pick<ThreadSnapshotRow, "threadMemory" | "carriedThreadMemory">): JsonValue[];
399
+ /** Structural equality over JSON values (object key order does not matter). */
400
+ export declare function jsonEqual(a: JsonValue | undefined, b: JsonValue | undefined): boolean;
129
401
  /**
130
402
  * The thread records a turn persists: each record of the fold, except that a
131
403
  * record the turn did NOT change is persisted as the PRIOR stored record
@@ -179,4 +451,5 @@ export declare function keepUnchangedThreadMemory(priorView: readonly AgMemoryRe
179
451
  * so it cannot round-trip through a live write — that is expected.)
180
452
  */
181
453
  export declare function seedEventsForReducer(priorState: JsonValue | undefined, priorThreadMemory: AgMemoryRecord[]): AgEvent[];
454
+ export {};
182
455
  //# sourceMappingURL=fold-rows.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fold-rows.d.ts","sourceRoot":"","sources":["../src/fold-rows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fold-rows.d.ts","sourceRoot":"","sources":["../src/fold-rows.ts"],"names":[],"mappings":"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"}