@guuey/threads 0.31.1 → 0.32.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 +81 -0
- package/dist/closed.d.ts +22 -0
- package/dist/closed.d.ts.map +1 -0
- package/dist/closed.js +28 -0
- package/dist/fold-rows.d.ts +278 -5
- package/dist/fold-rows.d.ts.map +1 -1
- package/dist/fold-rows.js +475 -21
- package/dist/http-read.d.ts +47 -0
- package/dist/http-read.d.ts.map +1 -0
- package/dist/http-read.js +274 -0
- package/dist/http.d.ts +37 -34
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +145 -22
- package/dist/in-memory.d.ts +2 -7
- package/dist/in-memory.d.ts.map +1 -1
- package/dist/in-memory.js +27 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -2
- package/dist/item-size.d.ts +56 -0
- package/dist/item-size.d.ts.map +1 -0
- package/dist/item-size.js +125 -0
- package/dist/rows.d.ts +122 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +13 -1
- package/dist/seal.d.ts +63 -0
- package/dist/seal.d.ts.map +1 -0
- package/dist/seal.js +120 -0
- package/dist/snapshot-plan.d.ts +61 -0
- package/dist/snapshot-plan.d.ts.map +1 -0
- package/dist/snapshot-plan.js +71 -0
- package/dist/store.d.ts +130 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +274 -66
- package/dist/stored-row.d.ts +17 -3
- package/dist/stored-row.d.ts.map +1 -1
- package/dist/stored-row.js +2 -21
- package/dist/testing/contract-suite.d.ts +37 -1
- package/dist/testing/contract-suite.d.ts.map +1 -1
- package/dist/testing/contract-suite.js +122 -1
- package/package.json +3 -3
package/dist/in-memory.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InMemoryThreadPersistence — the dev/test binding of
|
|
3
|
+
* {@link ThreadPersistencePort}. Same contract as any real binding (run
|
|
4
|
+
* the suite in `@guuey/threads/testing` to prove yours), including the
|
|
5
|
+
* two conditional-write guards and the null-preview no-touch semantics.
|
|
6
|
+
*/
|
|
7
|
+
import { previewDigest } from "./seal.js";
|
|
8
|
+
import { ThreadClosedError, threadClosure } from "./closed.js";
|
|
1
9
|
export class InMemoryThreadPersistence {
|
|
2
10
|
threads = new Map();
|
|
3
11
|
messages = [];
|
|
@@ -15,15 +23,33 @@ export class InMemoryThreadPersistence {
|
|
|
15
23
|
const t = this.threads.get(threadId);
|
|
16
24
|
if (!t)
|
|
17
25
|
throw new Error(`incrementSeq: thread ${threadId} does not exist`);
|
|
26
|
+
if (threadClosure(t) !== undefined)
|
|
27
|
+
throw new ThreadClosedError(threadId);
|
|
18
28
|
t.lastSeq += 1;
|
|
19
29
|
t.lastMessageAt = atIso;
|
|
20
30
|
t.updatedAt = atIso;
|
|
21
31
|
// null = allocate the seq WITHOUT touching the preview (card rows,
|
|
22
32
|
// text-less agent turns) — mirror of the hosted binding's branch.
|
|
23
|
-
if (preview !== null)
|
|
33
|
+
if (preview !== null) {
|
|
24
34
|
t.lastMessagePreview = preview;
|
|
35
|
+
// The preview's source seq and digest ride the same update, so a reader
|
|
36
|
+
// can tell whether the message the preview came from is sealed (`seal.ts`).
|
|
37
|
+
t.lastMessagePreviewSeq = t.lastSeq;
|
|
38
|
+
t.lastMessagePreviewDigest = previewDigest(preview);
|
|
39
|
+
}
|
|
25
40
|
return t.lastSeq;
|
|
26
41
|
}
|
|
42
|
+
async closeThread(threadId, closure) {
|
|
43
|
+
const t = this.threads.get(threadId);
|
|
44
|
+
if (!t)
|
|
45
|
+
throw new Error(`closeThread: thread ${threadId} does not exist`);
|
|
46
|
+
if (threadClosure(t) !== undefined)
|
|
47
|
+
return "already_closed";
|
|
48
|
+
t.closedCode = closure.code;
|
|
49
|
+
t.closedMessage = closure.message;
|
|
50
|
+
t.closedAt = closure.at;
|
|
51
|
+
return "closed";
|
|
52
|
+
}
|
|
27
53
|
async putMessage(row) {
|
|
28
54
|
if (this.messages.some((m) => m.threadId === row.threadId && m.seq === row.seq)) {
|
|
29
55
|
throw new Error(`putMessage: seq ${row.seq} already exists on ${row.threadId}`);
|
package/dist/index.d.ts
CHANGED
|
@@ -17,10 +17,16 @@
|
|
|
17
17
|
* - `@guuey/threads/testing` — the port contract suite: run it against
|
|
18
18
|
* your binding for the same guarantees the hosted one carries.
|
|
19
19
|
*/
|
|
20
|
-
export type { HandoffEvent, ReportEvent, StoredHistoryMessage, ThreadMessageEvent, ThreadMessageKind, ThreadMessageRole, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, TurnOrigin, ClientClass, } from "./rows.js";
|
|
21
|
-
export {
|
|
20
|
+
export type { HandoffEvent, ReportEvent, SealInterval, StoredHistoryMessage, ThreadMessageEvent, ThreadMessageKind, ThreadMessageRole, RowBudget, ThreadClosure, ThreadMessageRow, ThreadPersistencePort, ThreadRow, ThreadSnapshotRow, TurnOrigin, ClientClass, ThreadInputKind, } from "./rows.js";
|
|
21
|
+
export { THREAD_INPUT_KINDS, readThreadInputKind } from "./rows.js";
|
|
22
|
+
export { DYNAMODB_ITEM_LIMIT_BYTES, DYNAMODB_ROW_BUDGET, DYNAMODB_ROW_BUDGET_BYTES, dynamoItemBytes, dynamoNumberFits, } from "./item-size.js";
|
|
23
|
+
export { ThreadStore, STORED_TURN_TOO_LARGE_CODE, STORED_TURN_UNSTORABLE_NUMBER_CODE, type AppendFoldInput, type AppendFoldUnstorable, type AppendFoldResult, type ElidedToolInput, type AppendMessageInput, type AppendMessageResult, type EnsureThreadInput, } from "./store.js";
|
|
22
24
|
export { InMemoryThreadPersistence } from "./in-memory.js";
|
|
23
|
-
export {
|
|
25
|
+
export { ThreadClosedError, threadClosure } from "./closed.js";
|
|
26
|
+
export { hasSealedPart, isSeqSealed, previewDigest, readableThreadPreview, readSealState, threadSealState, type StoredPreviewSlice, type ThreadSealState, } from "./seal.js";
|
|
27
|
+
export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, type ClassifiedRowInput, isBlankBlock, isInformationless, type StoredRowClass, } from "./stored-row.js";
|
|
24
28
|
export { HttpThreadPersistence, HttpThreadStoreError, type HttpThreadPersistenceOptions, type ThreadScope, } from "./http.js";
|
|
25
|
-
export {
|
|
29
|
+
export { planSnapshot, type PlanSnapshotInput, type PlannedSnapshot, type SnapshotRefusal, type SnapshotRefusalReason, } from "./snapshot-plan.js";
|
|
30
|
+
export { agArtifactToCardRow, agMessageToBoundedRow, agMessageToRow, assertRowFits, cardUiDataByCallId, type CardUiData, type CardUiDataByCallId, ELIDED_VALUE_KEY, isStoredRowRefusal, StoredRowTooLargeError, StoredRowUnstorableNumberError, type ElidedField, type ElisionReason, type RefusedRowKind, type StoredElision, type StoredRowRefusal, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, storedToolCallViews, storedToolResultCallIds, storedToolResults, toolResultCarriesUi, type StoredToolCallView, type StoredToolResultBlock, type StoredRowText, type StoredTextParts, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, type RowCtx, } from "./fold-rows.js";
|
|
31
|
+
export { isViewDirectiveText, latestPaintProps, paintPartsOfStored, QUICK_REPLY_INTENT, quickReplyLabel, readTapLabels, writeTapLabels, type CardPaint, type CardProps, type LatestPaint, type PaintPart, type TapLabels, } from "@guuey/mcp-apps-host/narrowing";
|
|
26
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,yBAAyB,EACzB,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,0BAA0B,EAC1B,kCAAkC,EAClC,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,YAAY,EACZ,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,4BAA4B,EACjC,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,8BAA8B,EAC9B,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,MAAM,GACZ,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,cAAc,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,gCAAgC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { THREAD_INPUT_KINDS, readThreadInputKind } from "./rows.js";
|
|
2
|
+
export { DYNAMODB_ITEM_LIMIT_BYTES, DYNAMODB_ROW_BUDGET, DYNAMODB_ROW_BUDGET_BYTES, dynamoItemBytes, dynamoNumberFits, } from "./item-size.js";
|
|
3
|
+
export { ThreadStore, STORED_TURN_TOO_LARGE_CODE, STORED_TURN_UNSTORABLE_NUMBER_CODE, } from "./store.js";
|
|
2
4
|
export { InMemoryThreadPersistence } from "./in-memory.js";
|
|
5
|
+
export { ThreadClosedError, threadClosure } from "./closed.js";
|
|
6
|
+
export { hasSealedPart, isSeqSealed, previewDigest, readableThreadPreview, readSealState, threadSealState, } from "./seal.js";
|
|
3
7
|
export { AGENT_ERROR_ROW_SUFFIX, classifyStoredRow, isBlankBlock, isInformationless, } from "./stored-row.js";
|
|
4
8
|
export { HttpThreadPersistence, HttpThreadStoreError, } from "./http.js";
|
|
5
|
-
export {
|
|
9
|
+
export { planSnapshot, } from "./snapshot-plan.js";
|
|
10
|
+
export { agArtifactToCardRow, agMessageToBoundedRow, agMessageToRow, assertRowFits, cardUiDataByCallId, ELIDED_VALUE_KEY, isStoredRowRefusal, StoredRowTooLargeError, StoredRowUnstorableNumberError, cardRowToAgArtifact, messageFullText, messageNarration, messageText, producingToolName, storedTextParts, storedToolCallViews, storedToolResultCallIds, storedToolResults, toolResultCarriesUi, joinStoredThreadMemory, reassembleFold, rowToAgMessage, seedEventsForReducer, splitStoredThreadMemory, toolNamesByCallId, uiCardArtifactsFromMessages, } from "./fold-rows.js";
|
|
11
|
+
// guuey#2031 — the card-tap contract, re-exported for the persistence side (the
|
|
12
|
+
// runtime stamps a user row's tap labels; the read plane forwards them) so a
|
|
13
|
+
// server imports it from the package it already uses. The names live in
|
|
14
|
+
// `@guuey/mcp-apps-host`'s protocol-free subpath: the writer and the reader of a
|
|
15
|
+
// row's `tapLabels`, the one directive predicate, and the one paint reducer the
|
|
16
|
+
// runtime reads a tapped card with, with the quick-reply intent it labels under.
|
|
17
|
+
export { isViewDirectiveText, latestPaintProps, paintPartsOfStored, QUICK_REPLY_INTENT, quickReplyLabel, readTapLabels, writeTapLabels, } from "@guuey/mcp-apps-host/narrowing";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size of a stored row as DynamoDB counts it, so a binding backed by
|
|
3
|
+
* DynamoDB can declare a {@link RowBudget} that matches the store's own item
|
|
4
|
+
* limit (400 KB).
|
|
5
|
+
*
|
|
6
|
+
* The count follows the published rules (DynamoDB Developer Guide, "Item
|
|
7
|
+
* sizes and formats"), walked over the row the way the DocumentClient
|
|
8
|
+
* marshals it (string → S, number → N, boolean → BOOL, null → NULL,
|
|
9
|
+
* array → L, object → M, `undefined` dropped):
|
|
10
|
+
*
|
|
11
|
+
* - a string is its UTF-8 bytes;
|
|
12
|
+
* - a boolean or null is 1 byte;
|
|
13
|
+
* - a number is published as 1 byte per two significant digits, plus 1. As
|
|
14
|
+
* measured against DynamoDB it is 1 exponent byte, plus the digits in pairs
|
|
15
|
+
* aligned on the decimal point (so up to half a pair more than the digits
|
|
16
|
+
* alone), plus 1 more byte when the number is negative: -1.5 is 4 bytes and
|
|
17
|
+
* -123.456 is 6. It is bounded here from above as half of ALL the digits in
|
|
18
|
+
* `String(n)`, rounded up, plus 3, so an exponent form can only over-count;
|
|
19
|
+
* - a List or Map is 3 bytes, plus 1 byte per element, plus each Map key's
|
|
20
|
+
* UTF-8 bytes and each element's own size;
|
|
21
|
+
* - a top-level attribute is its name's UTF-8 bytes plus its value, plus 1.
|
|
22
|
+
*
|
|
23
|
+
* The result is an UPPER bound on the stored size: it may count a little
|
|
24
|
+
* more than DynamoDB does, never less.
|
|
25
|
+
*
|
|
26
|
+
* Why not the length of the row's JSON: JSON under-counts the shapes a chart
|
|
27
|
+
* input is made of. A list of one-digit numbers costs 2 bytes per element as
|
|
28
|
+
* JSON (`1,`) and 3 by the rules above; pairs (`[1,2],`) cost 6 against 10.
|
|
29
|
+
* A row measured as 380 KB of JSON could be about 630 KB in DynamoDB.
|
|
30
|
+
*/
|
|
31
|
+
import type { RowBudget } from "./rows.js";
|
|
32
|
+
/** DynamoDB's maximum item size: 400 KB. */
|
|
33
|
+
export declare const DYNAMODB_ITEM_LIMIT_BYTES = 409600;
|
|
34
|
+
/**
|
|
35
|
+
* The row budget a DynamoDB-backed binding declares: below the item limit,
|
|
36
|
+
* so the seq and timestamp the store assigns at write time, and the bound's
|
|
37
|
+
* own rounding, can never carry a row that fit over the limit.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DYNAMODB_ROW_BUDGET_BYTES = 380000;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a DynamoDB-backed binding can store `n` as written. The
|
|
42
|
+
* DocumentClient refuses a number outside the safe-integer range, and NaN or
|
|
43
|
+
* an infinity, before it sends anything (a JSON number that large has already
|
|
44
|
+
* lost its digits to `JSON.parse`); DynamoDB itself refuses a non-zero
|
|
45
|
+
* magnitude below 1e-130. A row carrying such a number would fail its write
|
|
46
|
+
* whatever its size, so it is planned out like an oversized one.
|
|
47
|
+
*/
|
|
48
|
+
export declare function dynamoNumberFits(n: number): boolean;
|
|
49
|
+
/** The row budget every DynamoDB-backed binding declares. */
|
|
50
|
+
export declare const DYNAMODB_ROW_BUDGET: RowBudget;
|
|
51
|
+
/**
|
|
52
|
+
* The stored size of an item (a `ThreadMessage` row, or any attribute bag the
|
|
53
|
+
* DocumentClient marshals), as an upper bound under DynamoDB's own rules.
|
|
54
|
+
*/
|
|
55
|
+
export declare function dynamoItemBytes(item: object): number;
|
|
56
|
+
//# sourceMappingURL=item-size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"item-size.d.ts","sourceRoot":"","sources":["../src/item-size.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAKjD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAInD;AAED,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,EAAE,SAIjC,CAAC;AAsDF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOpD"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size of a stored row as DynamoDB counts it, so a binding backed by
|
|
3
|
+
* DynamoDB can declare a {@link RowBudget} that matches the store's own item
|
|
4
|
+
* limit (400 KB).
|
|
5
|
+
*
|
|
6
|
+
* The count follows the published rules (DynamoDB Developer Guide, "Item
|
|
7
|
+
* sizes and formats"), walked over the row the way the DocumentClient
|
|
8
|
+
* marshals it (string → S, number → N, boolean → BOOL, null → NULL,
|
|
9
|
+
* array → L, object → M, `undefined` dropped):
|
|
10
|
+
*
|
|
11
|
+
* - a string is its UTF-8 bytes;
|
|
12
|
+
* - a boolean or null is 1 byte;
|
|
13
|
+
* - a number is published as 1 byte per two significant digits, plus 1. As
|
|
14
|
+
* measured against DynamoDB it is 1 exponent byte, plus the digits in pairs
|
|
15
|
+
* aligned on the decimal point (so up to half a pair more than the digits
|
|
16
|
+
* alone), plus 1 more byte when the number is negative: -1.5 is 4 bytes and
|
|
17
|
+
* -123.456 is 6. It is bounded here from above as half of ALL the digits in
|
|
18
|
+
* `String(n)`, rounded up, plus 3, so an exponent form can only over-count;
|
|
19
|
+
* - a List or Map is 3 bytes, plus 1 byte per element, plus each Map key's
|
|
20
|
+
* UTF-8 bytes and each element's own size;
|
|
21
|
+
* - a top-level attribute is its name's UTF-8 bytes plus its value, plus 1.
|
|
22
|
+
*
|
|
23
|
+
* The result is an UPPER bound on the stored size: it may count a little
|
|
24
|
+
* more than DynamoDB does, never less.
|
|
25
|
+
*
|
|
26
|
+
* Why not the length of the row's JSON: JSON under-counts the shapes a chart
|
|
27
|
+
* input is made of. A list of one-digit numbers costs 2 bytes per element as
|
|
28
|
+
* JSON (`1,`) and 3 by the rules above; pairs (`[1,2],`) cost 6 against 10.
|
|
29
|
+
* A row measured as 380 KB of JSON could be about 630 KB in DynamoDB.
|
|
30
|
+
*/
|
|
31
|
+
/** DynamoDB's maximum item size: 400 KB. */
|
|
32
|
+
export const DYNAMODB_ITEM_LIMIT_BYTES = 409_600;
|
|
33
|
+
/**
|
|
34
|
+
* The row budget a DynamoDB-backed binding declares: below the item limit,
|
|
35
|
+
* so the seq and timestamp the store assigns at write time, and the bound's
|
|
36
|
+
* own rounding, can never carry a row that fit over the limit.
|
|
37
|
+
*/
|
|
38
|
+
export const DYNAMODB_ROW_BUDGET_BYTES = 380_000;
|
|
39
|
+
/** The smallest non-zero magnitude DynamoDB stores; below it a write is refused as an underflow. */
|
|
40
|
+
const DYNAMODB_MIN_NUMBER_MAGNITUDE = 1e-130;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a DynamoDB-backed binding can store `n` as written. The
|
|
43
|
+
* DocumentClient refuses a number outside the safe-integer range, and NaN or
|
|
44
|
+
* an infinity, before it sends anything (a JSON number that large has already
|
|
45
|
+
* lost its digits to `JSON.parse`); DynamoDB itself refuses a non-zero
|
|
46
|
+
* magnitude below 1e-130. A row carrying such a number would fail its write
|
|
47
|
+
* whatever its size, so it is planned out like an oversized one.
|
|
48
|
+
*/
|
|
49
|
+
export function dynamoNumberFits(n) {
|
|
50
|
+
if (!Number.isFinite(n))
|
|
51
|
+
return false;
|
|
52
|
+
const magnitude = Math.abs(n);
|
|
53
|
+
return magnitude === 0 || (magnitude >= DYNAMODB_MIN_NUMBER_MAGNITUDE && magnitude <= Number.MAX_SAFE_INTEGER);
|
|
54
|
+
}
|
|
55
|
+
/** The row budget every DynamoDB-backed binding declares. */
|
|
56
|
+
export const DYNAMODB_ROW_BUDGET = {
|
|
57
|
+
maxBytes: DYNAMODB_ROW_BUDGET_BYTES,
|
|
58
|
+
bytesOf: dynamoItemBytes,
|
|
59
|
+
numberFits: dynamoNumberFits,
|
|
60
|
+
};
|
|
61
|
+
const LIST_OR_MAP_OVERHEAD = 3;
|
|
62
|
+
const ELEMENT_OVERHEAD = 1;
|
|
63
|
+
function utf8Bytes(s) {
|
|
64
|
+
return Buffer.byteLength(s, "utf8");
|
|
65
|
+
}
|
|
66
|
+
function numberBytes(n) {
|
|
67
|
+
let digits = 0;
|
|
68
|
+
for (const ch of String(n)) {
|
|
69
|
+
if (ch >= "0" && ch <= "9")
|
|
70
|
+
digits++;
|
|
71
|
+
}
|
|
72
|
+
// + 1 exponent byte, + 1 for a pair split by the decimal point, + 1 for a negative's terminator.
|
|
73
|
+
return Math.ceil(digits / 2) + 3;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The stored size of one attribute VALUE. `undefined` values are dropped by
|
|
77
|
+
* the marshaller, so they cost nothing and their element overhead is not
|
|
78
|
+
* counted either.
|
|
79
|
+
*/
|
|
80
|
+
function valueBytes(value) {
|
|
81
|
+
if (value === null)
|
|
82
|
+
return 1;
|
|
83
|
+
switch (typeof value) {
|
|
84
|
+
case "string":
|
|
85
|
+
return utf8Bytes(value);
|
|
86
|
+
case "number":
|
|
87
|
+
return numberBytes(value);
|
|
88
|
+
case "boolean":
|
|
89
|
+
return 1;
|
|
90
|
+
case "object": {
|
|
91
|
+
let total = LIST_OR_MAP_OVERHEAD;
|
|
92
|
+
if (Array.isArray(value)) {
|
|
93
|
+
for (const el of value) {
|
|
94
|
+
if (el === undefined)
|
|
95
|
+
continue;
|
|
96
|
+
total += ELEMENT_OVERHEAD + valueBytes(el);
|
|
97
|
+
}
|
|
98
|
+
return total;
|
|
99
|
+
}
|
|
100
|
+
for (const [key, el] of Object.entries(value)) {
|
|
101
|
+
if (el === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
total += ELEMENT_OVERHEAD + utf8Bytes(key) + valueBytes(el);
|
|
104
|
+
}
|
|
105
|
+
return total;
|
|
106
|
+
}
|
|
107
|
+
default:
|
|
108
|
+
// undefined (dropped by the marshaller); functions, symbols and bigints
|
|
109
|
+
// never reach a stored row.
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The stored size of an item (a `ThreadMessage` row, or any attribute bag the
|
|
115
|
+
* DocumentClient marshals), as an upper bound under DynamoDB's own rules.
|
|
116
|
+
*/
|
|
117
|
+
export function dynamoItemBytes(item) {
|
|
118
|
+
let total = 0;
|
|
119
|
+
for (const [name, value] of Object.entries(item)) {
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
total += utf8Bytes(name) + valueBytes(value) + 1;
|
|
123
|
+
}
|
|
124
|
+
return total;
|
|
125
|
+
}
|
package/dist/rows.d.ts
CHANGED
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
* thing" are the same mechanical guarantee — the `@guuey/state` pattern.
|
|
11
11
|
*/
|
|
12
12
|
import type { AgMemoryRecord, JsonValue } from "@silverprotocol/core";
|
|
13
|
+
import type { TapLabels } from "@guuey/mcp-apps-host/narrowing";
|
|
14
|
+
/**
|
|
15
|
+
* guuey#2549: what a user turn's content rides on when its text is empty. Mirrors `@guuey/worker`'s
|
|
16
|
+
* `INPUT_KINDS` (this package does not depend on it); nocode-runtime's sync test pins them equal.
|
|
17
|
+
*/
|
|
18
|
+
export declare const THREAD_INPUT_KINDS: readonly ["context-only"];
|
|
19
|
+
export type ThreadInputKind = (typeof THREAD_INPUT_KINDS)[number];
|
|
20
|
+
/**
|
|
21
|
+
* The reader contract for a stored row's `inputKind`: a known kind, or
|
|
22
|
+
* undefined for anything else (absent, malformed, a kind this release does not
|
|
23
|
+
* know). Never throws: a stored row is read across a rolling release.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readThreadInputKind(raw: unknown): ThreadInputKind | undefined;
|
|
13
26
|
export type ThreadMessageRole = "user" | "agent" | "system";
|
|
14
27
|
export type ThreadMessageKind = "text" | "card" | "event";
|
|
15
28
|
/**
|
|
@@ -38,10 +51,65 @@ export interface ThreadRow {
|
|
|
38
51
|
lastSeq: number;
|
|
39
52
|
lastMessageAt: string;
|
|
40
53
|
lastMessagePreview: string;
|
|
54
|
+
/**
|
|
55
|
+
* The seq of the message {@link lastMessagePreview} was taken from, written
|
|
56
|
+
* in the same atomic update. Optional: rows an older writer wrote carry
|
|
57
|
+
* none, and a reader treats that as "source unknown" (`readableThreadPreview`, `seal.ts`).
|
|
58
|
+
*/
|
|
59
|
+
lastMessagePreviewSeq?: number;
|
|
60
|
+
/**
|
|
61
|
+
* `previewDigest(lastMessagePreview)` (`seal.ts`), written in the same update
|
|
62
|
+
* as the preview and its seq. It binds the seq to the text, so a preview an
|
|
63
|
+
* older writer replaced (leaving the previous seq) reads as "source unknown".
|
|
64
|
+
*/
|
|
65
|
+
lastMessagePreviewDigest?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The thread's seal intervals: the messages the app's operator may not
|
|
68
|
+
* read. Absent on every thread no seal was ever written for (a legacy,
|
|
69
|
+
* wholly readable thread). Read only through `readSealState` (`seal.ts`),
|
|
70
|
+
* never trusted as stored.
|
|
71
|
+
*
|
|
72
|
+
* Typed as stored, not as {@link SealInterval}[]: a binding hands the stored
|
|
73
|
+
* attribute on unchanged, and one this release cannot read must still reach
|
|
74
|
+
* `readSealState`, which reads it as wholly sealed (fail closed). A writer
|
|
75
|
+
* writes {@link SealInterval}[]; a reader gets intervals only from
|
|
76
|
+
* `readSealState` / `threadSealState`.
|
|
77
|
+
*/
|
|
78
|
+
sealIntervals?: unknown;
|
|
79
|
+
/**
|
|
80
|
+
* The conversation was ended for a reason (a worker asked for it, e.g. a
|
|
81
|
+
* per-conversation limit): it takes no new message, and a client that sends
|
|
82
|
+
* on it starts a new conversation. All three are written together, once,
|
|
83
|
+
* and never cleared. `closedCode` is the machine-readable reason (a
|
|
84
|
+
* worker's code arrives prefixed `app:`), `closedMessage` what the person
|
|
85
|
+
* reads, `closedAt` when (ISO). Absent on every open thread and on every
|
|
86
|
+
* row written before closing existed. Read through `threadClosure`
|
|
87
|
+
* (`closed.ts`). Separate from {@link status}: an archived thread may also
|
|
88
|
+
* be closed.
|
|
89
|
+
*/
|
|
90
|
+
closedCode?: string;
|
|
91
|
+
closedMessage?: string;
|
|
92
|
+
closedAt?: string;
|
|
41
93
|
threadMode: string;
|
|
42
94
|
createdAt: string;
|
|
43
95
|
updatedAt: string;
|
|
44
96
|
}
|
|
97
|
+
/** Why and when a conversation was ended (see {@link ThreadRow.closedAt}). */
|
|
98
|
+
export interface ThreadClosure {
|
|
99
|
+
code: string;
|
|
100
|
+
message: string;
|
|
101
|
+
/** ISO timestamp. */
|
|
102
|
+
at: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* One sealed stretch of a thread: the messages from `fromSeq` to `toSeq`, both
|
|
106
|
+
* inclusive. No `toSeq` means the interval is still open, so it also covers
|
|
107
|
+
* every message written after `fromSeq`.
|
|
108
|
+
*/
|
|
109
|
+
export interface SealInterval {
|
|
110
|
+
fromSeq: number;
|
|
111
|
+
toSeq?: number;
|
|
112
|
+
}
|
|
45
113
|
/**
|
|
46
114
|
* The human-handoff event payload (guuey#552 §3.2): the MACHINE half of a
|
|
47
115
|
* `kind:'event'` handoff row — the notify Lambda's stream filter reads
|
|
@@ -132,10 +200,25 @@ export interface ThreadMessageRow {
|
|
|
132
200
|
event?: ThreadMessageEvent;
|
|
133
201
|
/** On a user row: how the turn was started. Absent on older rows. */
|
|
134
202
|
turnOrigin?: TurnOrigin;
|
|
203
|
+
/**
|
|
204
|
+
* guuey#2549: set on a user row whose `text` is empty because the turn's content rode on the
|
|
205
|
+
* page context (`'context-only'`). The history replays it through the worker's one substitution,
|
|
206
|
+
* so the model sees the same words on every later turn. Absent on every other row.
|
|
207
|
+
*/
|
|
208
|
+
inputKind?: ThreadInputKind;
|
|
135
209
|
/** On a `card_action` user row: the render session id of the card the gesture answered. */
|
|
136
210
|
answeredCardSessionId?: string;
|
|
137
211
|
/** On a user row: the sending client's class. Absent on older rows. */
|
|
138
212
|
clientClass?: ClientClass;
|
|
213
|
+
/**
|
|
214
|
+
* On a card-action user row (guuey#2031): the tapped controls' own words,
|
|
215
|
+
* one entry per tap the row stands for (`null` for a tap with none), as the
|
|
216
|
+
* sending client resolved them. DISPLAY-ONLY: `text` and `content` stay the
|
|
217
|
+
* directive the doorbell sent, and no model path reads this. Absent on rows
|
|
218
|
+
* written before it existed and on every row whose turn had no labels;
|
|
219
|
+
* readers read it through the reader contract (`readTapLabels`).
|
|
220
|
+
*/
|
|
221
|
+
tapLabels?: TapLabels;
|
|
139
222
|
}
|
|
140
223
|
/** Latest-replace fold snapshot for a thread. */
|
|
141
224
|
export interface ThreadSnapshotRow {
|
|
@@ -169,6 +252,25 @@ export interface StoredHistoryMessage {
|
|
|
169
252
|
text: string | null;
|
|
170
253
|
content: unknown;
|
|
171
254
|
at: string;
|
|
255
|
+
/** guuey#2549: carried from the row (see {@link ThreadMessageRow.inputKind}). */
|
|
256
|
+
inputKind?: ThreadInputKind;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* A bound on the size of one stored `ThreadMessage` row, declared by a
|
|
260
|
+
* binding whose store has an item-size limit. `bytesOf` measures a row the
|
|
261
|
+
* way the store counts it; `maxBytes` is the most a row may measure. The
|
|
262
|
+
* thread's snapshot row is planned against the same budget before a turn
|
|
263
|
+
* writes any row (guuey#2120), so `bytesOf` measures a {@link ThreadSnapshotRow}
|
|
264
|
+
* too (its stored form joins the two memory arrays into one, which is never
|
|
265
|
+
* larger).
|
|
266
|
+
* `numberFits`, when declared, says whether the store can hold a number as
|
|
267
|
+
* written; a row carrying one it cannot is refused like an oversized row.
|
|
268
|
+
* DynamoDB-backed bindings declare `DYNAMODB_ROW_BUDGET` (`item-size.ts`).
|
|
269
|
+
*/
|
|
270
|
+
export interface RowBudget {
|
|
271
|
+
readonly maxBytes: number;
|
|
272
|
+
bytesOf(row: ThreadMessageRow | ThreadSnapshotRow): number;
|
|
273
|
+
numberFits?(n: number): boolean;
|
|
172
274
|
}
|
|
173
275
|
/**
|
|
174
276
|
* The only surface {@link ThreadStore} needs from a backing store. Keep
|
|
@@ -176,6 +278,14 @@ export interface StoredHistoryMessage {
|
|
|
176
278
|
* `@guuey/threads/testing`.
|
|
177
279
|
*/
|
|
178
280
|
export interface ThreadPersistencePort {
|
|
281
|
+
/**
|
|
282
|
+
* The store's bound on one message row, when it has one. {@link ThreadStore}
|
|
283
|
+
* sizes every row of a turn against it before it writes any: an oversized
|
|
284
|
+
* tool-call input is replaced by an explicit marker, and a turn that still
|
|
285
|
+
* cannot fit is stored as one failure row (or refused), never written in
|
|
286
|
+
* part. Absent: no bound, and rows are written exactly as mapped.
|
|
287
|
+
*/
|
|
288
|
+
readonly rowBudget?: RowBudget;
|
|
179
289
|
/** Point-read a Thread by id; `undefined` when the row does not exist. */
|
|
180
290
|
getThread(threadId: string): Promise<ThreadRow | undefined>;
|
|
181
291
|
/** Put a new Thread row (conditional create — must reject an existing id). */
|
|
@@ -185,9 +295,20 @@ export interface ThreadPersistencePort {
|
|
|
185
295
|
* EXISTING Thread and return the NEW `lastSeq`. `preview: null`
|
|
186
296
|
* allocates the seq WITHOUT touching `lastMessagePreview` (card rows and
|
|
187
297
|
* text-less agent turns must not clobber the last real preview);
|
|
188
|
-
* a string preview replaces it.
|
|
298
|
+
* a string preview replaces it. On a closed thread
|
|
299
|
+
* ({@link ThreadRow.closedAt}) it allocates nothing and rejects with
|
|
300
|
+
* `ThreadClosedError`.
|
|
189
301
|
*/
|
|
190
302
|
incrementSeq(threadId: string, preview: string | null, atIso: string): Promise<number>;
|
|
303
|
+
/**
|
|
304
|
+
* Close an EXISTING thread: write the three closure fields
|
|
305
|
+
* ({@link ThreadRow.closedAt}) in one conditional update that holds only
|
|
306
|
+
* while the thread exists and is still open. `already_closed` when it was
|
|
307
|
+
* closed before (the first closure is kept); rejects for a missing thread.
|
|
308
|
+
* Optional: a binding without it cannot close a conversation
|
|
309
|
+
* (`ThreadStore.closeThread` answers `unsupported`).
|
|
310
|
+
*/
|
|
311
|
+
closeThread?(threadId: string, closure: ThreadClosure): Promise<"closed" | "already_closed">;
|
|
191
312
|
/** Put a ThreadMessage row (conditional — must reject a duplicate seq). */
|
|
192
313
|
putMessage(row: ThreadMessageRow): Promise<void>;
|
|
193
314
|
/** Up to `limit` most-recent messages for a thread, returned seq-ASCending. */
|
package/dist/rows.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../src/rows.ts"],"names":[],"mappings":"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"}
|
package/dist/rows.js
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* guuey#2549: what a user turn's content rides on when its text is empty. Mirrors `@guuey/worker`'s
|
|
3
|
+
* `INPUT_KINDS` (this package does not depend on it); nocode-runtime's sync test pins them equal.
|
|
4
|
+
*/
|
|
5
|
+
export const THREAD_INPUT_KINDS = ['context-only'];
|
|
6
|
+
/**
|
|
7
|
+
* The reader contract for a stored row's `inputKind`: a known kind, or
|
|
8
|
+
* undefined for anything else (absent, malformed, a kind this release does not
|
|
9
|
+
* know). Never throws: a stored row is read across a rolling release.
|
|
10
|
+
*/
|
|
11
|
+
export function readThreadInputKind(raw) {
|
|
12
|
+
return THREAD_INPUT_KINDS.find((kind) => kind === raw);
|
|
13
|
+
}
|
package/dist/seal.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SealInterval, ThreadRow } from "./rows.js";
|
|
2
|
+
/** A thread's seal state as a reader sees it, after the stored attribute was checked. */
|
|
3
|
+
export type ThreadSealState =
|
|
4
|
+
/** No seal record: a legacy thread, wholly readable. */
|
|
5
|
+
{
|
|
6
|
+
kind: "none";
|
|
7
|
+
}
|
|
8
|
+
/** A well-formed record: these intervals (possibly empty) decide each message. */
|
|
9
|
+
| {
|
|
10
|
+
kind: "intervals";
|
|
11
|
+
intervals: readonly SealInterval[];
|
|
12
|
+
}
|
|
13
|
+
/** A record that is present but malformed: every message reads as sealed. */
|
|
14
|
+
| {
|
|
15
|
+
kind: "unreadable";
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Check a stored `sealIntervals` attribute. Absent (`undefined` or `null`) is
|
|
19
|
+
* a legacy thread; anything else must be an array of well-formed intervals,
|
|
20
|
+
* or the whole thread reads as sealed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readSealState(stored: unknown): ThreadSealState;
|
|
23
|
+
/** Whether the message at `seq` is sealed under `state`. */
|
|
24
|
+
export declare function isSeqSealed(state: ThreadSealState, seq: number): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether ANY part of the thread is, or was, sealed. A copy built from the
|
|
27
|
+
* whole conversation (a model-written summary, a report) is refused when this
|
|
28
|
+
* is true, since it may carry sealed messages whatever message triggered it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hasSealedPart(state: ThreadSealState): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The digest a writer stores beside a preview (`lastMessagePreviewDigest`), in
|
|
33
|
+
* the same update that sets the preview and its source seq. It binds the seq to
|
|
34
|
+
* the text: a writer that replaces the preview without stamping its seq (one
|
|
35
|
+
* from before the stamp) leaves a digest that no longer matches, so a reader
|
|
36
|
+
* cannot mistake the old seq for the new text's source.
|
|
37
|
+
*/
|
|
38
|
+
export declare function previewDigest(preview: string): string;
|
|
39
|
+
/** The thread-row slice {@link readableThreadPreview} reads, as stored. */
|
|
40
|
+
export interface StoredPreviewSlice {
|
|
41
|
+
lastMessagePreview?: string;
|
|
42
|
+
/** The seq of the message the preview was taken from; absent on rows a writer before it wrote. */
|
|
43
|
+
lastMessagePreviewSeq?: unknown;
|
|
44
|
+
/** {@link previewDigest} of the preview the seq was stamped with; absent on rows a writer before it wrote. */
|
|
45
|
+
lastMessagePreviewDigest?: unknown;
|
|
46
|
+
sealIntervals?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The preview an operator-side reader may show: the stored preview, or `''`
|
|
50
|
+
* when the message it was taken from is sealed. On a thread with any sealed
|
|
51
|
+
* part, a preview whose source the reader cannot place is withheld too: no
|
|
52
|
+
* recorded seq, or a digest absent or not matching the stored text (an older
|
|
53
|
+
* writer replaced the text and left the previous seq beside it). A thread with
|
|
54
|
+
* no sealed part shows its preview as stored.
|
|
55
|
+
*
|
|
56
|
+
* One case reads as placed though it is not: an older writer that stored the
|
|
57
|
+
* very same text again. The text is then that of the stamped, readable
|
|
58
|
+
* message, so nothing new is shown.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readableThreadPreview(row: StoredPreviewSlice): string;
|
|
61
|
+
/** {@link readSealState} over a typed {@link ThreadRow}. */
|
|
62
|
+
export declare function threadSealState(row: Pick<ThreadRow, "sealIntervals">): ThreadSealState;
|
|
63
|
+
//# sourceMappingURL=seal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../src/seal.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzD,yFAAyF;AACzF,MAAM,MAAM,eAAe;AACzB,wDAAwD;AACtD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,kFAAkF;GAChF;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE;AAC3D,6EAA6E;GAC3E;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAiB3B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAU9D;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAMxE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAI7D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kGAAkG;IAClG,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,8GAA8G;IAC9G,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CASrE;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,GAAG,eAAe,CAEtF"}
|