agents 0.26.0 → 0.27.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 +8 -9
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-sdk-OoGkFy3s.js +2444 -0
- package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +1 -1
- package/dist/browser/ai-sdk.js +2 -2
- package/dist/browser/ai-sdk.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
- package/dist/browser-tool-CBqs6hqa.js.map +1 -0
- package/dist/capability-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +144 -0
- package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
- package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
- package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/conversations-DH0ut_8n.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BDC07gYO.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +2 -1
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2529 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
- package/dist/index-Ib_4dwOo.d.ts +200 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-DNrpTu49.js +23 -0
- package/dist/input-schema-DNrpTu49.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-pwTNq9z6.d.ts +134 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
- package/dist/schedules/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-B9pNuxdT.js +2272 -0
- package/dist/sessions-B9pNuxdT.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
- package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/store-DLeqKuj3.d.ts +310 -0
- package/dist/store-vgcPYo8K.js +432 -0
- package/dist/store-vgcPYo8K.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
- package/dist/streams-CITClVer.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
- package/dist/tasks-CkTZNaV1.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
- package/dist/tool-3PA8xjB_.js +421 -0
- package/dist/tool-3PA8xjB_.js.map +1 -0
- package/dist/tool-BPJEbwaX.d.ts +278 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/websearch/index.d.ts +58 -0
- package/dist/websearch/index.js +2 -0
- package/dist/websearch/tools/ai-sdk.d.ts +77 -0
- package/dist/websearch/tools/ai-sdk.js +46 -0
- package/dist/websearch/tools/ai-sdk.js.map +1 -0
- package/dist/websearch/tools/pi.d.ts +82 -0
- package/dist/websearch/tools/pi.js +89 -0
- package/dist/websearch/tools/pi.js.map +1 -0
- package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
- package/dist/websearch/tools/tanstack-ai.js +46 -0
- package/dist/websearch/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
- package/dist/websockets-CmENGb-Z.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +3 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +2 -1
- package/docs/lifecycle.md +40 -0
- package/docs/search-the-web.md +133 -0
- package/package.json +117 -38
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/sessions/chunking.ts","../../src/sessions/attachment-store.ts","../../src/sessions/attachment-ingest.ts","../../src/sessions/compaction-helpers.ts","../../src/sessions/overlays.ts","../../src/sessions/core.ts","../../src/sessions/mirror.ts","../../src/sessions/handle.ts","../../src/sessions/sessions.ts"],"sourcesContent":["/**\n * Row chunking for the Sessions capability.\n *\n * SQLite on a Durable Object caps how many bytes one row may hold, so a\n * message whose serialized JSON exceeds the budget is split across the\n * message row and numbered continuation rows. Reads concatenate the slices\n * back into the original string, so a round-trip is exact and nothing is\n * ever truncated or too large to store.\n *\n * The split is on BYTES, not characters: SQLite's limit is a byte limit and\n * one character can be up to four of them. A boundary is never placed\n * between a high surrogate and its low surrogate either, because a lone\n * surrogate is not valid UTF-8 and would not survive the round-trip.\n * `splitContent(s).join(\"\")` always equals `s`.\n */\n\n/**\n * Serialized byte ceiling for one stored row. A message larger than this is\n * split across continuation rows; the overwhelmingly common message fits in\n * one row and costs exactly one billed row write, as it always has.\n */\nexport const MAX_INLINE_ROW_BYTES = 1536 * 1024;\n\n/**\n * Split a serialized message into row-sized slices, root slice first.\n *\n * Always returns at least one slice, and never an empty slice unless the\n * input itself is empty. Slice 0 lives in the message row; the rest become\n * continuation rows numbered from 1.\n */\nexport function splitContent(\n json: string,\n budget: number = MAX_INLINE_ROW_BYTES\n): string[] {\n if (json.length === 0) return [\"\"];\n\n const slices: string[] = [];\n let start = 0;\n let bytes = 0;\n let index = 0;\n\n while (index < json.length) {\n const code = json.charCodeAt(index);\n let width: number;\n let step = 1;\n if (code < 0x80) {\n width = 1;\n } else if (code < 0x800) {\n width = 2;\n } else if (code >= 0xd800 && code <= 0xdbff && index + 1 < json.length) {\n const low = json.charCodeAt(index + 1);\n if (low >= 0xdc00 && low <= 0xdfff) {\n // A surrogate PAIR is one 4-byte code point and moves as a unit, so\n // a boundary can never land between its halves.\n width = 4;\n step = 2;\n } else {\n width = 3;\n }\n } else {\n width = 3;\n }\n\n // `bytes > 0` keeps a single character that alone exceeds the budget in\n // its own slice rather than emitting an empty one.\n if (bytes > 0 && bytes + width > budget) {\n slices.push(json.slice(start, index));\n start = index;\n bytes = 0;\n }\n bytes += width;\n index += step;\n }\n\n slices.push(json.slice(start));\n return slices;\n}\n","/**\n * Content-addressed byte storage for session attachments.\n *\n * An attachment is a typed media payload that never belongs inside a message\n * row: an image, an audio clip, a PDF. The store holds the raw bytes under\n * their SHA-256 and hands back a content address; the message keeps only a\n * pointer part. Payloads larger than one SQLite row are split across numbered\n * chunk rows, the same way an oversized message is.\n *\n * Everything here is synchronous, so bytes and the message row that points at\n * them commit in ONE transaction. There is no window in which a stored pointer\n * has no bytes behind it, and no cleanup pass for half-written payloads.\n *\n * Content addressing buys idempotency — a replayed append re-derives the same\n * address and stores nothing new. It is not a space-saving claim: two messages\n * that happen to carry the same image share a record, but nothing in the design\n * depends on that being common.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { MAX_INLINE_ROW_BYTES } from \"./chunking\";\nimport type { SessionsIo } from \"./io\";\n\n/**\n * SQLite window for one attachment chunk. Attachments are immutable, so a\n * larger row means fewer billed writes; this leaves headroom below SQLite's\n * 2 MiB ceiling for the row key and record overhead.\n */\nconst ATTACHMENT_CHUNK_BYTES = MAX_INLINE_ROW_BYTES;\n\n/** @internal Bytes plus the type they were declared with. */\nexport interface AttachmentBytes {\n readonly bytes: Uint8Array;\n readonly mediaType: string;\n}\n\n/** The content address of a payload. Pure: no storage access. */\nexport function hashPayload(bytes: Uint8Array): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\n/** A standalone copy of a slice, since `SqlParam` accepts only `ArrayBuffer`. */\nfunction chunkBuffer(\n bytes: Uint8Array,\n start: number,\n end: number\n): ArrayBuffer {\n return bytes.slice(start, end).buffer as ArrayBuffer;\n}\n\nexport class AttachmentStore {\n readonly #io: SessionsIo;\n\n constructor(io: SessionsIo) {\n this.#io = io;\n }\n\n ensureTables(): void {\n this.#io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_attachment_meta (\n hash TEXT PRIMARY KEY,\n bytes INTEGER NOT NULL,\n media_type TEXT NOT NULL,\n chunks INTEGER NOT NULL\n ) WITHOUT ROWID`,\n []\n );\n this.#io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_attachment_chunks (\n hash TEXT NOT NULL,\n idx INTEGER NOT NULL,\n data BLOB NOT NULL,\n PRIMARY KEY (hash, idx)\n ) WITHOUT ROWID`,\n []\n );\n // Reference rows give an attachment its lifetime. There is deliberately no\n // index on `hash`: this table takes a write on every attachment append, and\n // the reachability check that reads it is a scan of a small table, which is\n // far cheaper than maintaining an index on the write path.\n this.#io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_attachment_refs (\n session_id TEXT NOT NULL,\n message_id TEXT NOT NULL,\n hash TEXT NOT NULL,\n PRIMARY KEY (session_id, message_id, hash)\n ) WITHOUT ROWID`,\n []\n );\n }\n\n /**\n * Store one payload under its precomputed address. Idempotent: bytes that\n * are already present cost a single read and no writes.\n *\n * Call inside the caller's transaction so the payload and the message that\n * references it commit together. The address is passed in rather than\n * derived so hashing stays outside the transaction.\n */\n put(payload: AttachmentBytes, hash: string): void {\n const { bytes, mediaType } = payload;\n const existing = this.#io.sql<{ hash: string }>(\n \"SELECT hash FROM cf_agents_session_attachment_meta WHERE hash = ?\",\n [hash]\n );\n if (existing.length > 0) return;\n\n let chunks = 0;\n for (let start = 0; start < bytes.length; start += ATTACHMENT_CHUNK_BYTES) {\n const end = Math.min(start + ATTACHMENT_CHUNK_BYTES, bytes.length);\n this.#io.sqlWrite(\n \"INSERT INTO cf_agents_session_attachment_chunks (hash, idx, data) VALUES (?, ?, ?)\",\n [hash, chunks, chunkBuffer(bytes, start, end)]\n );\n chunks++;\n }\n this.#io.sqlWrite(\n `INSERT INTO cf_agents_session_attachment_meta (hash, bytes, media_type, chunks)\n VALUES (?, ?, ?, ?)`,\n [hash, bytes.length, mediaType, chunks]\n );\n }\n\n /** Read one payload back, or `undefined` when the address is unknown. */\n get(hash: string): AttachmentBytes | undefined {\n const [meta] = this.#io.sql<{\n bytes: number;\n media_type: string;\n chunks: number;\n }>(\n \"SELECT bytes, media_type, chunks FROM cf_agents_session_attachment_meta WHERE hash = ?\",\n [hash]\n );\n if (!meta) return undefined;\n\n const out = new Uint8Array(meta.bytes);\n if (meta.chunks > 0) {\n const rows = this.#io.sql<{ idx: number; data: ArrayBuffer }>(\n \"SELECT idx, data FROM cf_agents_session_attachment_chunks WHERE hash = ? ORDER BY idx\",\n [hash]\n );\n let offset = 0;\n for (const row of rows) {\n const slice = new Uint8Array(row.data);\n out.set(slice, offset);\n offset += slice.length;\n }\n }\n return { mediaType: meta.media_type, bytes: out };\n }\n\n /** Record that one message references these payloads. */\n addRefs(\n sessionId: string,\n messageId: string,\n hashes: readonly string[]\n ): void {\n for (const hash of hashes) {\n this.#io.sqlWrite(\n `INSERT OR IGNORE INTO cf_agents_session_attachment_refs\n (session_id, message_id, hash) VALUES (?, ?, ?)`,\n [sessionId, messageId, hash]\n );\n }\n }\n\n /**\n * Point one message at exactly `hashes`, adding and dropping references to\n * match, and collect whatever that orphaned. Call after the new payloads are\n * stored, so a hash the message still uses is never briefly unreferenced.\n */\n replaceRefs(\n sessionId: string,\n messageId: string,\n hashes: readonly string[]\n ): void {\n const current = this.#io.sql<{ hash: string }>(\n `SELECT hash FROM cf_agents_session_attachment_refs\n WHERE session_id = ? AND message_id = ?`,\n [sessionId, messageId]\n );\n const wanted = new Set(hashes);\n const held = new Set(current.map((row) => row.hash));\n const dropped: string[] = [];\n for (const hash of held) {\n if (wanted.has(hash)) continue;\n this.#io.sqlWrite(\n `DELETE FROM cf_agents_session_attachment_refs\n WHERE session_id = ? AND message_id = ? AND hash = ?`,\n [sessionId, messageId, hash]\n );\n dropped.push(hash);\n }\n this.addRefs(\n sessionId,\n messageId,\n hashes.filter((hash) => !held.has(hash))\n );\n if (dropped.length > 0) this.#collect(dropped);\n }\n\n /**\n * Drop the references held by the given messages and collect any payload\n * that no longer has a reader.\n */\n releaseMessages(sessionId: string, messageIds: readonly string[]): void {\n if (messageIds.length === 0) return;\n const ids = JSON.stringify(messageIds);\n const orphanCandidates = this.#io.sql<{ hash: string }>(\n `SELECT DISTINCT hash FROM cf_agents_session_attachment_refs\n WHERE session_id = ? AND message_id IN (SELECT value FROM json_each(?))`,\n [sessionId, ids]\n );\n if (orphanCandidates.length === 0) return;\n this.#io.sqlWrite(\n `DELETE FROM cf_agents_session_attachment_refs\n WHERE session_id = ? AND message_id IN (SELECT value FROM json_each(?))`,\n [sessionId, ids]\n );\n this.#collect(orphanCandidates.map((row) => row.hash));\n }\n\n /** Drop every reference held by one session and collect what it orphaned. */\n releaseSession(sessionId: string): void {\n const candidates = this.#io.sql<{ hash: string }>(\n \"SELECT DISTINCT hash FROM cf_agents_session_attachment_refs WHERE session_id = ?\",\n [sessionId]\n );\n if (candidates.length === 0) return;\n this.#io.sqlWrite(\n \"DELETE FROM cf_agents_session_attachment_refs WHERE session_id = ?\",\n [sessionId]\n );\n this.#collect(candidates.map((row) => row.hash));\n }\n\n /** Delete payloads that no reference points at any more. */\n #collect(hashes: readonly string[]): void {\n for (const hash of hashes) {\n const stillReferenced = this.#io.sql<{ hash: string }>(\n \"SELECT hash FROM cf_agents_session_attachment_refs WHERE hash = ? LIMIT 1\",\n [hash]\n );\n if (stillReferenced.length > 0) continue;\n this.#io.sqlWrite(\n \"DELETE FROM cf_agents_session_attachment_chunks WHERE hash = ?\",\n [hash]\n );\n this.#io.sqlWrite(\n \"DELETE FROM cf_agents_session_attachment_meta WHERE hash = ?\",\n [hash]\n );\n }\n }\n}\n","/**\n * Which parts of a message become attachments, and how they come back.\n *\n * The rule is typed, not size-based: a part that DECLARES a non-text media\n * type and carries its payload inline is an attachment, whatever its size.\n * Text, reasoning and plain tool output are never extracted — they stay in the\n * row and, if they are too large for one, chunk across continuation rows.\n *\n * That distinction is the whole point. Sizing the rule off bytes would make a\n * message's stored shape depend on how big an image happened to be, which is\n * how the previous design ended up as a rescue mechanism that competed with\n * row chunking. Extraction here is uniform, so a reader never has to ask why\n * one image is a pointer and another is inline.\n *\n * Extraction is lossless: bytes move to the attachment store and a read puts\n * them back verbatim. Nothing that shapes what a model SEES belongs here — a\n * cap on tool output discards content and so lives on the read path, in\n * `agents/context`, where it can change without having destroyed anything.\n */\n\nimport { hashPayload, type AttachmentBytes } from \"./attachment-store\";\nimport type { SessionMessage, SessionMessagePart } from \"./types\";\n\n/** Pointer scheme written into a stored part in place of its payload. */\nconst ATTACHMENT_URL_PREFIX = \"attachment:sha256:\";\n\n/** Hostile or deeply nested tool output stops here rather than recursing forever. */\nconst MAX_WALK_DEPTH = 8;\n\n/** Build the pointer for a content address. */\nfunction attachmentUrl(hash: string): string {\n return `${ATTACHMENT_URL_PREFIX}${hash}`;\n}\n\n/** The content address in a pointer, or `null` when the value is not one. */\nfunction parseAttachmentUrl(url: unknown): string | null {\n if (typeof url !== \"string\" || !url.startsWith(ATTACHMENT_URL_PREFIX)) {\n return null;\n }\n const hash = url.slice(ATTACHMENT_URL_PREFIX.length);\n return /^[0-9a-f]{64}$/.test(hash) ? hash : null;\n}\n\ninterface ParsedDataUrl {\n mediaType: string;\n payload: string;\n}\n\n/** Parse a base64 `data:` URL. Non-base64 data URLs are left alone. */\nfunction parseDataUrl(url: string): ParsedDataUrl | null {\n if (!url.startsWith(\"data:\")) return null;\n const comma = url.indexOf(\",\");\n if (comma < 0) return null;\n const header = url.slice(5, comma);\n if (!header.endsWith(\";base64\")) return null;\n const mediaType =\n header.slice(0, -\";base64\".length) || \"application/octet-stream\";\n return { mediaType, payload: url.slice(comma + 1) };\n}\n\n/**\n * Text payloads stay in the message. They chunk perfectly well, they are what\n * FTS indexes, and moving them out would put prose behind a pointer for no gain.\n */\nfunction isTextMediaType(mediaType: string): boolean {\n return mediaType.startsWith(\"text/\");\n}\n\nfunction decodeBase64(payload: string): Uint8Array | null {\n const compact = /[\\t\\n\\f\\r ]/.test(payload)\n ? payload.replace(/[\\t\\n\\f\\r ]/g, \"\")\n : payload;\n if (compact.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(compact))\n return null;\n let binary: string;\n try {\n binary = atob(compact);\n } catch {\n return null;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\nfunction encodeBase64(bytes: Uint8Array): string {\n // Chunked so a large payload never blows the argument limit of `apply`.\n const step = 0x8000;\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += step) {\n binary += String.fromCharCode(...bytes.subarray(i, i + step));\n }\n return btoa(binary);\n}\n\n/** Rebuild the exact `data:` URL a payload was extracted from. */\nfunction dataUrl(mediaType: string, bytes: Uint8Array): string {\n return `data:${mediaType};base64,${encodeBase64(bytes)}`;\n}\n\n/**\n * A value carrying inline media: anything that declares a `mediaType` and\n * holds its bytes in a `url` data URL or a base64 `data` string. Covers AI SDK\n * file parts and the media entries tool results return, without either shape\n * being named here.\n */\nfunction inlineMediaOf(\n value: Record<string, unknown>\n): { field: \"url\" | \"data\"; mediaType: string; bytes: Uint8Array } | null {\n const declared = typeof value.mediaType === \"string\" ? value.mediaType : null;\n\n const url = value.url;\n if (typeof url === \"string\") {\n const parsed = parseDataUrl(url);\n if (!parsed) return null;\n const mediaType = declared ?? parsed.mediaType;\n if (isTextMediaType(mediaType)) return null;\n const bytes = decodeBase64(parsed.payload);\n return bytes ? { field: \"url\", mediaType, bytes } : null;\n }\n\n // A bare `data` string is only media when something declared it as such.\n if (\n declared &&\n typeof value.data === \"string\" &&\n !isTextMediaType(declared)\n ) {\n const bytes = decodeBase64(value.data);\n return bytes ? { field: \"data\", mediaType: declared, bytes } : null;\n }\n return null;\n}\n\n/** One payload lifted out of a message, already addressed. */\nexport interface PendingAttachment {\n readonly hash: string;\n readonly payload: AttachmentBytes;\n}\n\n/** What one extraction pass produced. */\nexport interface ExtractionResult {\n /** The message to store: identical when it carried no inline media. */\n message: SessionMessage;\n /** Payloads to write, deduplicated by address, in encounter order. */\n attachments: PendingAttachment[];\n /**\n * Every address a read of the stored message restores: the payloads\n * extracted on this pass and any pointer the message already carried. A\n * message can be written back with its pointers in place, and its\n * references must follow what the row says, not what this pass extracted.\n */\n references: string[];\n}\n\n/**\n * Replace inline media with pointers, collecting the payloads to store.\n *\n * Returns the original message by reference when nothing was extracted, so the\n * overwhelmingly common text-only write allocates nothing and pays only a walk.\n */\nexport function extractAttachments(message: SessionMessage): ExtractionResult {\n const attachments: PendingAttachment[] = [];\n const seen = new Set<string>();\n\n const walk = (value: unknown, depth: number): unknown => {\n if (depth > MAX_WALK_DEPTH || value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n let changed = false;\n const next = value.map((entry) => {\n const walked = walk(entry, depth + 1);\n if (walked !== entry) changed = true;\n return walked;\n });\n return changed ? next : value;\n }\n\n const record = value as Record<string, unknown>;\n const media = inlineMediaOf(record);\n if (media) {\n // Hashing is pure CPU, so the address is known here and the write\n // transaction never has to hash anything.\n const hash = hashPayload(media.bytes);\n if (!seen.has(hash)) {\n seen.add(hash);\n attachments.push({\n hash,\n payload: { bytes: media.bytes, mediaType: media.mediaType }\n });\n }\n // The pointer replaces the payload IN PLACE, in whichever field held it.\n // Keeping the field means a resolve restores the original shape exactly,\n // rather than rewriting a `data` entry into a `url` one.\n return {\n ...record,\n mediaType: media.mediaType,\n [media.field]: attachmentUrl(hash)\n };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(record)) {\n const walked = walk(entry, depth + 1);\n if (walked !== entry) changed = true;\n next[key] = walked;\n }\n return changed ? next : value;\n };\n\n if (message.parts.length === 0) {\n return { message, attachments, references: [] };\n }\n const parts = walk(message.parts, 0) as SessionMessagePart[];\n const references = [...pointersOf(parts)];\n if (parts === message.parts) return { message, attachments, references };\n return { message: { ...message, parts }, attachments, references };\n}\n\n/**\n * The attachment addresses a read of this stored form restores. It walks\n * exactly as {@link resolveAttachments} does, so a message holds a reference\n * for every pointer a read would put back and for nothing else.\n */\nfunction pointersOf(\n value: unknown,\n into = new Set<string>(),\n depth = 0\n): Set<string> {\n if (depth > MAX_WALK_DEPTH || value === null || typeof value !== \"object\") {\n return into;\n }\n if (Array.isArray(value)) {\n for (const entry of value) pointersOf(entry, into, depth + 1);\n return into;\n }\n const record = value as Record<string, unknown>;\n const hash =\n parseAttachmentUrl(record.url) ?? parseAttachmentUrl(record.data);\n if (hash) {\n into.add(hash);\n return into;\n }\n for (const entry of Object.values(record)) pointersOf(entry, into, depth + 1);\n return into;\n}\n\n/**\n * Put payloads back inline, undoing extraction exactly.\n *\n * A pointer whose bytes cannot be loaded is left as it is: an unresolvable\n * pointer is a truthful record that the reference survived its payload, which\n * only a bug could produce, and inventing a placeholder would hide it.\n */\nexport function resolveAttachments(\n message: SessionMessage,\n load: (hash: string) => { mediaType: string; bytes: Uint8Array } | undefined\n): SessionMessage {\n const walk = (value: unknown, depth: number): unknown => {\n if (depth > MAX_WALK_DEPTH || value === null || typeof value !== \"object\") {\n return value;\n }\n if (Array.isArray(value)) {\n let changed = false;\n const next = value.map((entry) => {\n const walked = walk(entry, depth + 1);\n if (walked !== entry) changed = true;\n return walked;\n });\n return changed ? next : value;\n }\n\n const record = value as Record<string, unknown>;\n const urlHash = parseAttachmentUrl(record.url);\n if (urlHash) {\n const loaded = load(urlHash);\n if (!loaded) return value;\n return { ...record, url: dataUrl(loaded.mediaType, loaded.bytes) };\n }\n const dataHash = parseAttachmentUrl(record.data);\n if (dataHash) {\n const loaded = load(dataHash);\n if (!loaded) return value;\n // A `data` field held bare base64, not a data URL, so it is restored bare.\n return { ...record, data: encodeBase64(loaded.bytes) };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(record)) {\n const walked = walk(entry, depth + 1);\n if (walked !== entry) changed = true;\n next[key] = walked;\n }\n return changed ? next : value;\n };\n\n const parts = walk(message.parts, 0) as SessionMessagePart[];\n return parts === message.parts ? message : { ...message, parts };\n}\n","/**\n * Compaction Helpers\n *\n * Utilities for full compaction (LLM-based summarization).\n * Used by the reference compaction implementation and available\n * for custom CompactFunction implementations.\n */\n\nimport type { SessionMessage } from \"./types\";\nimport { estimateMessageTokens } from \"./tokens\";\n\n// ── Compaction ID constants ─────────────────────────────────────────\n\n/** Prefix for all compaction messages (overlays and summaries) */\nexport const COMPACTION_PREFIX = \"compaction_\";\n\n/** Head messages kept verbatim so the conversation's opening survives. */\nconst PROTECT_HEAD = 3;\n/** Tail messages kept verbatim regardless of the token budget. */\nconst MIN_TAIL_MESSAGES = 2;\n\n/** Check if a message is a compaction message */\nexport function isCompactionMessage(msg: SessionMessage): boolean {\n return msg.id.startsWith(COMPACTION_PREFIX);\n}\n\n// ── Tool Pair Alignment ──────────────────────────────────────────────\n\n/**\n * Check if a message contains tool invocations.\n */\nfunction hasToolCalls(msg: SessionMessage): boolean {\n return msg.parts.some(\n (p) => p.type.startsWith(\"tool-\") || p.type === \"dynamic-tool\"\n );\n}\n\n/**\n * Get tool call IDs from a message's parts.\n */\nfunction getToolCallIds(msg: SessionMessage): Set<string> {\n const ids = new Set<string>();\n for (const part of msg.parts) {\n if (\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"toolCallId\" in part\n ) {\n ids.add((part as { toolCallId: string }).toolCallId);\n }\n }\n return ids;\n}\n\n/**\n * Check if a message is a tool result referencing a specific call ID.\n */\nfunction isToolResultFor(msg: SessionMessage, callIds: Set<string>): boolean {\n return msg.parts.some(\n (p) =>\n (p.type.startsWith(\"tool-\") || p.type === \"dynamic-tool\") &&\n \"toolCallId\" in p &&\n callIds.has((p as { toolCallId: string }).toolCallId)\n );\n}\n\n/**\n * Align a boundary index forward to avoid splitting tool call/result groups.\n * If the boundary falls between an assistant message with tool calls and its\n * tool results, move it forward past the results.\n */\nexport function alignBoundaryForward(\n messages: SessionMessage[],\n idx: number\n): number {\n if (idx <= 0 || idx >= messages.length) return idx;\n\n // Check if the message before the boundary has tool calls\n const prev = messages[idx - 1];\n if (prev.role === \"assistant\" && hasToolCalls(prev)) {\n const callIds = getToolCallIds(prev);\n // Skip forward past any tool results for these calls\n while (idx < messages.length && isToolResultFor(messages[idx], callIds)) {\n idx++;\n }\n }\n\n return idx;\n}\n\n/**\n * Align a boundary index backward to avoid splitting tool call/result groups.\n * If the boundary falls in the middle of tool results, move it backward to\n * include the assistant message that made the calls.\n */\nexport function alignBoundaryBackward(\n messages: SessionMessage[],\n idx: number\n): number {\n if (idx <= 0 || idx >= messages.length) return idx;\n\n // If the message at idx is a tool result, walk backward to find the call\n while (idx > 0) {\n const msg = messages[idx];\n if (msg.role === \"assistant\" && hasToolCalls(msg)) {\n break; // This is a tool call message — include it\n }\n // Check if this looks like a tool result (assistant message following another)\n const prev = messages[idx - 1];\n if (prev.role === \"assistant\" && hasToolCalls(prev)) {\n const callIds = getToolCallIds(prev);\n if (isToolResultFor(msg, callIds)) {\n idx--; // Move back to include the call\n continue;\n }\n }\n break;\n }\n\n return idx;\n}\n\n// ── Token-Budget Tail Protection ─────────────────────────────────────\n\n/**\n * Find the compression end boundary using a token budget for the tail.\n * Walks backward from the end, accumulating tokens until budget is reached.\n * Returns the index where compression should stop (everything from this\n * index onward is protected).\n *\n * @param messages All messages\n * @param headEnd Index where the protected head ends (compression starts here)\n * @param tailTokenBudget Maximum tokens to keep in the tail\n * @param minTailMessages Minimum messages to protect in the tail (fallback)\n */\nexport function findTailCutByTokens(\n messages: SessionMessage[],\n headEnd: number,\n tailTokenBudget = 20000,\n minTailMessages = 2\n): number {\n const n = messages.length;\n let accumulated = 0;\n let tokenCut = n;\n\n for (let i = n - 1; i >= headEnd; i--) {\n const msgTokens = estimateMessageTokens([messages[i]]);\n\n if (accumulated + msgTokens > tailTokenBudget && tokenCut < n) {\n // Budget exceeded and we already have at least one tail message\n break;\n }\n accumulated += msgTokens;\n tokenCut = i;\n }\n\n // Protect whichever is larger: token-based tail or minTailMessages\n const minCut = n - minTailMessages;\n const cutIdx = minCut >= headEnd ? Math.min(tokenCut, minCut) : tokenCut;\n\n // Align to avoid splitting tool groups\n return alignBoundaryBackward(messages, cutIdx);\n}\n\nexport function computeSummaryBudget(messages: SessionMessage[]): number {\n const contentTokens = estimateMessageTokens(messages);\n // Summary is ~20% of the content being compressed.\n // The summary replaces the compressed middle, so it's sized relative\n // to what it's replacing — not the tail budget (they occupy different\n // slots in the context window).\n const budget = Math.floor(contentTokens * 0.2);\n return Math.max(100, budget);\n}\n\n// ── Structured Summary Prompt ────────────────────────────────────────\n\n/**\n * Build a prompt for LLM summarization of compressed messages.\n *\n * @param messages Messages to summarize\n * @param previousSummary Previous summary for iterative updates (or null for first compaction)\n * @param budget Target token count for the summary\n */\nexport function buildSummaryPrompt(\n messages: SessionMessage[],\n previousSummary: string | null,\n budget: number\n): string {\n const content = messages\n .map((msg) => {\n const textParts = msg.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\\n\");\n\n const toolParts = msg.parts\n .filter((p) => p.type.startsWith(\"tool-\") || p.type === \"dynamic-tool\")\n .map((p) => {\n const tp = p as {\n toolName?: string;\n input?: unknown;\n output?: unknown;\n };\n const parts = [`[Tool: ${tp.toolName ?? \"unknown\"}]`];\n if (tp.input)\n parts.push(`Input: ${JSON.stringify(tp.input).slice(0, 500)}`);\n if (tp.output)\n parts.push(`Output: ${JSON.stringify(tp.output).slice(0, 500)}`);\n return parts.join(\"\\n\");\n })\n .join(\"\\n\");\n\n return `[${msg.role}]\\n${textParts}${toolParts ? \"\\n\" + toolParts : \"\"}`;\n })\n .join(\"\\n\\n---\\n\\n\");\n\n if (previousSummary) {\n return `You are updating a conversation summary. A previous summary exists below. New conversation turns have occurred since then and need to be incorporated.\n\nPREVIOUS SUMMARY:\n${previousSummary}\n\nNEW TURNS TO INCORPORATE:\n${content}\n\nUpdate the summary. PRESERVE existing information that is still relevant. ADD new information. Remove information only if it is clearly obsolete.\n\n## Topic\n[What the conversation is about]\n\n## Key Points\n[Important information, decisions, and conclusions from the conversation]\n\n## Current State\n[Where things stand now — what has been done, what is in progress]\n\n## Open Items\n[Unresolved questions, pending tasks, or next steps discussed]\n\nTarget ~${budget} tokens. Be factual — only include information that was explicitly discussed in the conversation. Do NOT invent file paths, commands, or details that were not mentioned. Write only the summary body.`;\n }\n\n return `Create a concise summary of this conversation that preserves the important information for future context.\n\nCONVERSATION TO SUMMARIZE:\n${content}\n\nUse this structure:\n\n## Topic\n[What the conversation is about]\n\n## Key Points\n[Important information, decisions, and conclusions from the conversation]\n\n## Current State\n[Where things stand now — what has been done, what is in progress]\n\n## Open Items\n[Unresolved questions, pending tasks, or next steps discussed]\n\nTarget ~${budget} tokens. Be factual — only include information that was explicitly discussed in the conversation. Do NOT invent file paths, commands, or details that were not mentioned. Write only the summary body.`;\n}\n\n// ── Reference Compaction Implementation ──────────────────────────────\n\n/**\n * Result of a compaction function — describes the overlay to store.\n */\nexport interface CompactResult {\n /** First message ID in the compacted range */\n fromMessageId: string;\n /** Last message ID in the compacted range */\n toMessageId: string;\n /** Summary text to store as the overlay */\n summary: string;\n}\n\nexport interface CompactOptions {\n /** Calls the model to summarize; receives a prompt, returns its text. */\n summarize: (prompt: string) => Promise<string>;\n /**\n * Token budget for the recent tail kept verbatim. Older messages above the\n * protected head are summarized into one overlay. Default 20,000.\n */\n keepRecentTokens?: number;\n}\n\n/**\n * Reference compaction implementation.\n *\n * Implements the full hermes-style compaction algorithm:\n * 1. Protect head messages (first N)\n * 2. Protect tail by token budget (walk backward)\n * 3. Align boundaries to tool call groups\n * 4. Summarize middle section with LLM (structured format)\n * 5. Iterative summary updates on subsequent compactions\n *\n * @example\n * ```typescript\n * import { createCompactFunction } from \"agents/sessions\";\n *\n * sessions\n * .session()\n * .onCompaction(\n * createCompactFunction({\n * summarize: (prompt) => generateText({ model, prompt }).then((r) => r.text)\n * })\n * )\n * .compactAfter(100_000);\n * ```\n */\nexport function createCompactFunction(opts: CompactOptions) {\n const keepRecentTokens = opts.keepRecentTokens ?? 20_000;\n\n return async (messages: SessionMessage[]): Promise<CompactResult | null> => {\n if (messages.length <= PROTECT_HEAD + MIN_TAIL_MESSAGES) return null;\n\n // 1. Find compression boundaries\n const compressStart = alignBoundaryForward(messages, PROTECT_HEAD);\n const compressEnd = findTailCutByTokens(\n messages,\n compressStart,\n keepRecentTokens,\n MIN_TAIL_MESSAGES\n );\n\n if (compressEnd <= compressStart) {\n return null;\n }\n\n // Filter out compaction overlay messages — they have virtual IDs\n // and should not be included in the summary prompt or used as range IDs\n const middleMessages = messages\n .slice(compressStart, compressEnd)\n .filter((m) => !isCompactionMessage(m));\n\n if (middleMessages.length === 0) return null;\n\n // 2. Generate summary — extract previous summary from compaction overlays\n const existingCompaction = messages.find(isCompactionMessage);\n const previousSummary = existingCompaction\n ? existingCompaction.parts\n .filter((p) => p.type === \"text\")\n .map((p) => (p as { text: string }).text)\n .join(\"\\n\")\n : null;\n\n const budget = computeSummaryBudget(middleMessages);\n const prompt = buildSummaryPrompt(middleMessages, previousSummary, budget);\n const summary = await opts.summarize(prompt);\n\n if (!summary.trim()) return null;\n\n return {\n fromMessageId: middleMessages[0].id,\n toMessageId: middleMessages[middleMessages.length - 1].id,\n summary\n };\n };\n}\n","/**\n * Compaction overlay planning shared by the read path and the stats\n * derivation. Reproduces the selection semantics of the legacy\n * `applyCompactions` exactly: walk the path root → leaf; at each position the\n * newest overlay that starts here and ends at-or-after here on this branch\n * wins; its span is skipped and later overlaps inside the span never apply.\n */\n\nimport { COMPACTION_PREFIX } from \"./compaction-helpers\";\nimport type { SessionMessage, StoredCompaction } from \"./types\";\n\n/** One planned overlay span over path indexes (inclusive). */\nexport interface OverlaySpan {\n startIndex: number;\n endIndex: number;\n compaction: StoredCompaction;\n}\n\n/** Plan overlay spans over an ordered list of path message ids. */\nexport function planOverlays(\n pathIds: readonly string[],\n compactions: readonly StoredCompaction[]\n): OverlaySpan[] {\n if (compactions.length === 0) return [];\n const indexById = new Map(pathIds.map((id, index) => [id, index]));\n const spans: OverlaySpan[] = [];\n let i = 0;\n while (i < pathIds.length) {\n // Sibling branches can have compactions with the same starting message.\n // Consider only ranges ending on this branch, then let the latest valid\n // compaction supersede earlier ranges on the same branch.\n const matching = compactions.filter(\n (compaction) =>\n compaction.fromMessageId === pathIds[i] &&\n (indexById.get(compaction.toMessageId) ?? -1) >= i\n );\n const compaction = matching.at(-1);\n if (compaction) {\n const endIndex = indexById.get(compaction.toMessageId) ?? -1;\n if (endIndex >= i) {\n spans.push({ startIndex: i, endIndex, compaction });\n i = endIndex + 1;\n continue;\n }\n }\n i++;\n }\n return spans;\n}\n\n/** The synthetic message an overlay span renders as. */\nexport function overlayMessage(compaction: StoredCompaction): SessionMessage {\n return {\n id: `${COMPACTION_PREFIX}${compaction.id}`,\n role: \"assistant\",\n parts: [{ type: \"text\", text: compaction.summary }],\n createdAt: new Date()\n };\n}\n","/**\n * @internal Storage engine behind the Sessions capability. One instance per\n * capability, owning the `cf_agents_session_*` tables. All methods assume the\n * caller has settled startup ordering (`lifecycle.ready()` for public API).\n *\n * Write economics: rows written cost ~1000× rows read on DO SQLite. Every\n * table is WITHOUT ROWID with no secondary index, so one row write bills one\n * row. State is derived from existing rows, never kept in counter rows, and\n * an unchanged update writes nothing. The only in-memory state is the tail\n * of each session (its leaf id and next `seq`), read once per object\n * lifetime because finding it means scanning the session's rows.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { extractAttachments, resolveAttachments } from \"./attachment-ingest\";\nimport { AttachmentStore } from \"./attachment-store\";\nimport { splitContent } from \"./chunking\";\nimport { COMPACTION_PREFIX } from \"./compaction-helpers\";\nimport type { SessionsIo } from \"./io\";\nimport { overlayMessage, planOverlays, type OverlaySpan } from \"./overlays\";\nimport {\n estimateAttachmentTokens,\n estimatedDataUrlBytes,\n estimateMessageTokens,\n estimateStringTokens\n} from \"./tokens\";\nimport type {\n HistoryReadOptions,\n RecentHistoryResult,\n SearchResult,\n SessionChangeEvent,\n SessionChangeListener,\n SessionMessage,\n SessionRowStat,\n SessionsOptions,\n StoredCompaction\n} from \"./types\";\n\n/**\n * Bounds for each content-hydration query on a history path. In workerd the\n * SQLite allocator shares the isolate's memory budget with the JS heap, so\n * oversized transient result sets surface as SQLITE_NOMEM (#1710). Chunks\n * are bounded by BOTH row count and cumulative stored bytes.\n */\nconst HISTORY_CONTENT_CHUNK_SIZE = 50;\nconst HISTORY_CONTENT_CHUNK_BYTES = 4 * 1024 * 1024;\n/**\n * Rows per content window on a newest-first read. Such a read walks the path\n * by id alone — no per-row byte subqueries, so no byte-bounded chunking — and\n * is typically stopped by its consumer within the first few messages, so a\n * small fixed window keeps both the rows read and the memory held low.\n */\nconst NEWEST_FIRST_WINDOW_ROWS = 8;\n\n/**\n * Deepest path a history read follows: the root row is depth 0, so a read\n * returns at most this many rows plus one. A longer branch shows its most\n * recent rows only, and `getRecentHistory` reports that as truncated.\n */\nconst MAX_PATH_DEPTH = 10_000;\n\n/**\n * Hidden overlay rows: a stored `compaction_<id>` row whose `<id>` names one\n * of the session's own compaction records, so it duplicates an overlay the\n * session renders itself. Writes refuse overlays, but sessions written before\n * that guard (#1984) can hold one, usually with later rows parented to it.\n * Reads skip such a row and walk through it, so its children take its place.\n * A `compaction_` row with no matching record, such as a summary imported\n * from another session's history, is the only copy of that context and stays\n * visible.\n */\nfunction hasOverlayPrefix(id: string): boolean {\n return id.startsWith(COMPACTION_PREFIX);\n}\n\n/**\n * SQL twin of the hidden-overlay test for the row id in `idColumn` of the\n * session in `sessionExpr`. The record lookup runs only for prefixed ids.\n * Both must be table-qualified: inside the lookup an unqualified `id` or\n * `session_id` binds to the compactions table, not the row being tested.\n */\nfunction hiddenOverlayRowSql(idColumn: string, sessionExpr: string): string {\n const length = COMPACTION_PREFIX.length;\n return `(substr(${idColumn}, 1, ${length}) = '${COMPACTION_PREFIX}'\n AND EXISTS (SELECT 1 FROM cf_agents_session_compactions oc\n WHERE oc.session_id = ${sessionExpr}\n AND oc.id = substr(${idColumn}, ${length + 1})))`;\n}\n\n/** What a hydration window needs from a path row: its id, and its stored size when the window is byte-bounded. */\ntype PathRow = { id: string; bytes: number };\n\n/** The newest row of a session: what an append attaches to and numbers from. */\ntype Tail = { leafId: string | null; nextSeq: number };\n\n/**\n * The memoised context-size estimate for a session's active path: the leaf it\n * was computed for, the ids on that path whose own estimate counts (rows under\n * a compaction overlay are replaced by the summary and do not), and the total.\n * A tail append and an update of a counted row adjust it in place; every other\n * write (branch append, delete, clear, compaction, import) drops it, and the\n * next `tokenEstimate` re-derives it from one path walk.\n */\ntype PathTokens = {\n leafId: string | null;\n counted: Set<string>;\n total: number;\n /**\n * Stored rows the walk visited, hidden overlay rows included, since the\n * path cap counts those: the memo is only extended while under the cap.\n */\n depth: number;\n};\n\nexport type UpdateOutcome = \"missing\" | \"unchanged\" | \"updated\";\n\n/**\n * Digest of a message's stored form, stamped on the row by every write, so an\n * update can decide \"unchanged\" without reading the stored content back.\n */\nfunction contentDigest(json: string): string {\n return createHash(\"sha256\").update(json, \"utf8\").digest(\"hex\");\n}\n\nexport class SessionsCore {\n readonly io: SessionsIo;\n readonly #reservedMetadataKeys: readonly string[];\n readonly #listeners = new Set<SessionChangeListener>();\n readonly #tails = new Map<string, Tail>();\n readonly #pathTokens = new Map<string, PathTokens>();\n readonly #attachments: AttachmentStore;\n #tablesEnsured = false;\n /** True once the FTS index exists; it is built on the first `search()`. */\n #fts = false;\n\n constructor(options: SessionsOptions, io: SessionsIo) {\n this.io = io;\n this.#attachments = new AttachmentStore(io);\n this.#reservedMetadataKeys = options.reservedMetadataKeys ?? [];\n }\n\n // ── Schema ───────────────────────────────────────────────────────────────\n\n ensureTables(): void {\n if (this.#tablesEnsured) return;\n this.io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_messages (\n session_id TEXT NOT NULL,\n id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n parent_id TEXT,\n type TEXT NOT NULL DEFAULT 'message',\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n content_chunks INTEGER NOT NULL DEFAULT 0,\n token_estimate INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n content_hash TEXT,\n PRIMARY KEY (session_id, id)\n ) WITHOUT ROWID`,\n []\n );\n this.#addMessageContentHashColumn();\n this.io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_message_chunks (\n session_id TEXT NOT NULL,\n id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n content TEXT NOT NULL,\n PRIMARY KEY (session_id, id, idx)\n ) WITHOUT ROWID`,\n []\n );\n this.io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_compactions (\n session_id TEXT NOT NULL,\n id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n summary TEXT NOT NULL,\n from_message_id TEXT NOT NULL,\n to_message_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (session_id, id)\n ) WITHOUT ROWID`,\n []\n );\n this.io.sqlWrite(\n `CREATE TABLE IF NOT EXISTS cf_agents_session_config (\n session_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n PRIMARY KEY (session_id, key)\n ) WITHOUT ROWID`,\n []\n );\n this.#attachments.ensureTables();\n this.#fts = this.#tableExists(\"cf_agents_session_fts\");\n this.#tablesEnsured = true;\n }\n\n /**\n * Add the digest column to a message table that predates it. Nullable, so\n * no existing row is rewritten; a `null` digest falls back to reading the\n * row back once.\n */\n #addMessageContentHashColumn(): void {\n const columns = this.io\n .sql<{\n name: string;\n }>(\"SELECT name FROM pragma_table_info('cf_agents_session_messages')\", [])\n .map((row) => row.name);\n if (columns.includes(\"content_hash\")) return;\n this.io.sqlWrite(\n \"ALTER TABLE cf_agents_session_messages ADD COLUMN content_hash TEXT\",\n []\n );\n }\n\n #tableExists(name: string): boolean {\n return (\n this.io.sql<{ name: string }>(\n \"SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?\",\n [name]\n ).length > 0\n );\n }\n\n /**\n * Build the FTS index on demand. Maintaining it costs an extra billed row\n * on every append, so it exists only on objects that have actually\n * searched; the first search pays a one-time SQL backfill instead.\n */\n #ensureFts(): void {\n if (this.#fts) return;\n this.io.sqlWrite(\n `CREATE VIRTUAL TABLE cf_agents_session_fts\n USING fts5(id UNINDEXED, session_id UNINDEXED, role UNINDEXED, content, tokenize='porter unicode61')`,\n []\n );\n this.#fts = true;\n this.#backfillMissingFtsRows();\n }\n\n /** Index rows that predate indexing, in SQL, without loading JSON into JS. */\n #backfillMissingFtsRows(): void {\n this.io.sqlWrite(\n `INSERT INTO cf_agents_session_fts (id, session_id, role, content)\n SELECT m.id, m.session_id, m.role,\n group_concat(json_extract(part.value, '$.text'), ' ')\n FROM cf_agents_session_messages AS m\n JOIN json_each(\n CASE WHEN json_valid(m.content) THEN m.content ELSE '{\"parts\":[]}' END,\n '$.parts'\n ) AS part\n WHERE json_extract(part.value, '$.type') = 'text'\n AND COALESCE(json_extract(part.value, '$.text'), '') <> ''\n AND NOT EXISTS (\n SELECT 1 FROM cf_agents_session_fts AS existing\n WHERE existing.id = m.id AND existing.session_id = m.session_id\n )\n GROUP BY m.id, m.session_id, m.role`,\n []\n );\n }\n\n /**\n * Lift the legacy `assistant_*` message and compaction tables.\n *\n * The copy is pure SQL, so SQLite streams it rather than materializing rows\n * in the isolate. Each source is then verified row by row against its\n * destination and DROPPED: keeping tombstones would leave every upgraded\n * object holding its history twice inside the same 10 GB. A table whose\n * verification fails is left in place with a `session:migration:incomplete`\n * event, and the method returns false so the caller leaves the schema\n * version unstamped and retries on a later start. `assistant_config`\n * belongs to Think, which lifts and drops it itself.\n */\n migrateLegacy(): boolean {\n this.#pathTokens.clear();\n let complete = true;\n const drop = (name: string): void => {\n if (this.#tableExists(name)) this.io.sqlWrite(`DROP TABLE ${name}`, []);\n };\n /**\n * Drop a lifted source only once every one of its rows has a copy holding\n * the same payload. Matching on the key alone would accept a destination\n * row that merely occupies the key.\n */\n const dropWhenCopied = (\n source: string,\n destination: string,\n payload: string\n ): void => {\n const [counts] = this.io.sql<{ source: number; copied: number }>(\n `SELECT\n (SELECT COUNT(*) FROM ${source}) AS source,\n (SELECT COUNT(*) FROM ${source} AS legacy\n JOIN ${destination} AS lifted\n ON lifted.session_id = legacy.session_id\n AND lifted.id = legacy.id\n AND lifted.${payload} = legacy.${payload}) AS copied`,\n []\n );\n if (counts && counts.source === counts.copied) {\n drop(source);\n return;\n }\n complete = false;\n this.io.emit(\"session:migration:incomplete\", {\n table: source,\n source: counts?.source ?? 0,\n copied: counts?.copied ?? 0\n });\n };\n\n if (this.#tableExists(\"assistant_messages\")) {\n this.io.sqlWrite(\n `INSERT OR IGNORE INTO cf_agents_session_messages\n (session_id, id, seq, parent_id, role, content, token_estimate, created_at)\n SELECT session_id, id,\n ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at ASC, rowid ASC),\n parent_id, role, content,\n CAST(LENGTH(CAST(content AS BLOB)) / 4 AS INTEGER),\n COALESCE(CAST(strftime('%s', created_at) AS INTEGER), 0) * 1000\n FROM assistant_messages`,\n []\n );\n if (this.#fts) this.#backfillMissingFtsRows();\n dropWhenCopied(\n \"assistant_messages\",\n \"cf_agents_session_messages\",\n \"content\"\n );\n }\n if (this.#tableExists(\"assistant_compactions\")) {\n this.io.sqlWrite(\n `INSERT OR IGNORE INTO cf_agents_session_compactions\n (session_id, id, seq, summary, from_message_id, to_message_id, created_at)\n SELECT session_id, id,\n ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at ASC, rowid ASC),\n summary, from_message_id, to_message_id,\n COALESCE(CAST(strftime('%s', created_at) AS INTEGER), 0) * 1000\n FROM assistant_compactions`,\n []\n );\n dropWhenCopied(\n \"assistant_compactions\",\n \"cf_agents_session_compactions\",\n \"summary\"\n );\n }\n // Neither carries data the new schema needs: session summaries derive\n // from message rows, and the index is rebuilt from message text on the\n // first search.\n drop(\"assistant_sessions\");\n drop(\"assistant_fts\");\n return complete;\n }\n\n // ── Change feed ──────────────────────────────────────────────────────────\n\n subscribe(listener: SessionChangeListener): () => void {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n }\n\n /**\n * Dispatch after a durable write. A listener that throws must not turn a\n * committed write into a rejected call, so failures are reported through\n * telemetry and dispatch continues.\n */\n async notify(event: SessionChangeEvent): Promise<void> {\n for (const listener of this.#listeners) {\n try {\n await listener(event);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n console.warn(`[Sessions] change listener failed: ${detail}`);\n this.io.emit(\"session:error\", {\n sessionId: event.sessionId,\n event: event.type,\n error: detail\n });\n }\n }\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────\n\n /**\n * Continuation slices for the given ids, keyed by id and already joined in\n * `idx` order. One query, and only for ids the caller knows have them.\n */\n #continuations(\n sessionId: string,\n ids: readonly string[]\n ): Map<string, string> {\n const joined = new Map<string, string>();\n if (ids.length === 0) return joined;\n const rows = this.io.sql<{ id: string; content: string }>(\n `SELECT id, content FROM cf_agents_session_message_chunks\n WHERE session_id = ? AND id IN (SELECT value FROM json_each(?))\n ORDER BY id ASC, idx ASC`,\n [sessionId, JSON.stringify([...ids])]\n );\n for (const row of rows) {\n joined.set(row.id, (joined.get(row.id) ?? \"\") + row.content);\n }\n return joined;\n }\n\n /** Reassemble one stored row, reading continuations only when it has any. */\n #content(sessionId: string, id: string): string | null {\n const rows = this.io.sql<{ content: string; content_chunks: number }>(\n \"SELECT content, content_chunks FROM cf_agents_session_messages WHERE session_id = ? AND id = ?\",\n [sessionId, id]\n );\n if (rows.length === 0) return null;\n const row = rows[0];\n if (row.content_chunks === 0) return row.content;\n return row.content + (this.#continuations(sessionId, [id]).get(id) ?? \"\");\n }\n\n #hasParent(sessionId: string, id: string): boolean {\n const [row] = this.io.sql<{ parent_id: string | null }>(\n \"SELECT parent_id FROM cf_agents_session_messages WHERE session_id = ? AND id = ?\",\n [sessionId, id]\n );\n return row?.parent_id != null;\n }\n\n exists(sessionId: string, id: string): boolean {\n return (\n this.io.sql<{ id: string }>(\n \"SELECT id FROM cf_agents_session_messages WHERE session_id = ? AND id = ?\",\n [sessionId, id]\n ).length > 0\n );\n }\n\n getMessage(sessionId: string, id: string): SessionMessage | null {\n const content = this.#content(sessionId, id);\n const parsed = content === null ? null : this.#parse(content);\n return parsed && this.#inline(parsed);\n }\n\n /**\n * The session's newest row. Children insert after their parents, so the\n * max-seq row is provably childless: it is the active leaf, and the next\n * append numbers from it. Read once per object lifetime, since the table\n * is keyed by id and finding the max means scanning the session's rows.\n */\n #tail(sessionId: string): Tail {\n const cached = this.#tails.get(sessionId);\n if (cached) return cached;\n const [row] = this.io.sql<{ id: string; seq: number }>(\n \"SELECT id, seq FROM cf_agents_session_messages WHERE session_id = ? ORDER BY seq DESC LIMIT 1\",\n [sessionId]\n );\n const tail: Tail = row\n ? { leafId: row.id, nextSeq: row.seq + 1 }\n : { leafId: null, nextSeq: 1 };\n this.#tails.set(sessionId, tail);\n return tail;\n }\n\n /**\n * Drop the in-memory tail and token-total caches for a session. For a\n * caller whose enclosing transaction rolled back after an append or update\n * ran inside it: the rows are gone but the caches already moved. The next\n * read re-derives both from storage.\n */\n forgetCaches(sessionId: string): void {\n this.#tails.delete(sessionId);\n this.#pathTokens.delete(sessionId);\n }\n\n latestLeafId(sessionId: string): string | null {\n return this.#tail(sessionId).leafId;\n }\n\n #resolveLeafId(sessionId: string, leafId?: string | null): string | null {\n if (leafId) return this.exists(sessionId, leafId) ? leafId : null;\n return this.latestLeafId(sessionId);\n }\n\n /**\n * Which of `ids` are hidden overlay rows. Reads the session's compaction\n * records only when some id carries the prefix.\n */\n #hiddenOverlayIds(sessionId: string, ids: readonly string[]): Set<string> {\n if (!ids.some(hasOverlayPrefix)) return new Set();\n const overlays = new Set(\n this.getCompactions(sessionId).map(\n (compaction) => `${COMPACTION_PREFIX}${compaction.id}`\n )\n );\n return new Set(ids.filter((id) => overlays.has(id)));\n }\n\n getLatestLeaf(sessionId: string): SessionMessage | null {\n let leafId = this.latestLeafId(sessionId);\n while (\n leafId !== null &&\n this.#hiddenOverlayIds(sessionId, [leafId]).has(leafId)\n ) {\n const [row] = this.io.sql<{ parent_id: string | null }>(\n \"SELECT parent_id FROM cf_agents_session_messages WHERE session_id = ? AND id = ?\",\n [sessionId, leafId]\n );\n leafId = row?.parent_id ?? null;\n }\n return leafId ? this.getMessage(sessionId, leafId) : null;\n }\n\n /** Children of a row in `seq` order, hidden overlay rows replaced by theirs. */\n #childRows(\n sessionId: string,\n parentId: string\n ): Array<{\n id: string;\n seq: number;\n content: string;\n content_chunks: number;\n }> {\n const rows = this.io.sql<{\n id: string;\n seq: number;\n content: string;\n content_chunks: number;\n }>(\n `SELECT id, seq, content, content_chunks FROM cf_agents_session_messages\n WHERE session_id = ? AND parent_id = ? ORDER BY seq ASC`,\n [sessionId, parentId]\n );\n const hidden = this.#hiddenOverlayIds(\n sessionId,\n rows.map((row) => row.id)\n );\n if (hidden.size === 0) return rows;\n return rows\n .flatMap((row) =>\n hidden.has(row.id) ? this.#childRows(sessionId, row.id) : [row]\n )\n .sort((a, b) => a.seq - b.seq);\n }\n\n getBranches(sessionId: string, messageId: string): SessionMessage[] {\n const rows = this.#childRows(sessionId, messageId);\n const continued = this.#continuations(\n sessionId,\n rows.filter((row) => row.content_chunks > 0).map((row) => row.id)\n );\n const result: SessionMessage[] = [];\n for (const row of rows) {\n const parsed = this.#parse(\n row.content_chunks === 0\n ? row.content\n : row.content + (continued.get(row.id) ?? \"\")\n );\n if (parsed) result.push(this.#inline(parsed));\n }\n return result;\n }\n\n /**\n * The active branch path as content-free rows, root → leaf. Recurses over\n * (id, parent_id) only — carrying content through the recursive queue\n * materializes the transcript several times inside SQLite (#1710).\n * `bytes` counts the message row, its continuation rows, AND the payloads\n * it points at, charged at the size they take once inlined, so a byte\n * budget over these rows bounds real hydrated memory.\n */\n pathRowStats(sessionId: string, leafId?: string | null): SessionRowStat[] {\n return this.#pathRows(sessionId, leafId).rows;\n }\n\n /**\n * `pathRowStats` plus what the walk itself saw: the path cap counts stored\n * rows, hidden overlay rows included, so whether the walk was cut short\n * is judged from the unfiltered path.\n */\n #pathRows(\n sessionId: string,\n leafId?: string | null\n ): { rows: SessionRowStat[]; walked: number; oldestId: string | null } {\n const leaf = this.#resolveLeafId(sessionId, leafId);\n if (!leaf) return { rows: [], walked: 0, oldestId: null };\n const walked = this.io.sql<SessionRowStat & { hidden: number }>(\n `WITH RECURSIVE path(id, parent_id, depth) AS (\n SELECT id, parent_id, 0 FROM cf_agents_session_messages\n WHERE session_id = ? AND id = ?\n UNION ALL\n SELECT m.id, m.parent_id, p.depth + 1 FROM cf_agents_session_messages m\n JOIN path p ON m.id = p.parent_id\n WHERE m.session_id = ? AND p.depth < ${MAX_PATH_DEPTH}\n )\n SELECT path.id AS id, am.role AS role,\n LENGTH(CAST(am.content AS BLOB)) + CASE WHEN am.content_chunks = 0 THEN 0\n ELSE COALESCE((\n SELECT SUM(LENGTH(CAST(c.content AS BLOB)))\n FROM cf_agents_session_message_chunks c\n WHERE c.session_id = am.session_id AND c.id = am.id\n ), 0) END\n + COALESCE((\n SELECT SUM((meta.bytes + 2) / 3 * 4)\n FROM cf_agents_session_attachment_refs r\n JOIN cf_agents_session_attachment_meta meta ON meta.hash = r.hash\n WHERE r.session_id = am.session_id AND r.message_id = am.id\n ), 0) AS bytes,\n am.token_estimate AS tokenEstimate,\n ${hiddenOverlayRowSql(\"path.id\", \"am.session_id\")} AS hidden\n FROM path JOIN cf_agents_session_messages am\n ON am.session_id = ? AND am.id = path.id\n ORDER BY path.depth DESC`,\n [sessionId, leaf, sessionId, sessionId]\n );\n const rows: SessionRowStat[] = [];\n for (const { hidden, ...row } of walked) {\n if (!hidden) rows.push(row);\n }\n return {\n rows,\n walked: walked.length,\n oldestId: walked[0]?.id ?? null\n };\n }\n\n /**\n * The active branch path as ids alone, root → leaf. The cheapest walk the\n * tree allows — one row per step, following `parent_id` by primary key —\n * for readers that will hydrate only a few rows and do not need the sizes\n * `pathRowStats` charges per row.\n */\n #pathIds(sessionId: string, leafId?: string | null): string[] {\n const leaf = this.#resolveLeafId(sessionId, leafId);\n if (!leaf) return [];\n return this.io\n .sql<{ id: string }>(\n `WITH RECURSIVE path(id, parent_id, depth) AS (\n SELECT id, parent_id, 0 FROM cf_agents_session_messages\n WHERE session_id = ? AND id = ?\n UNION ALL\n SELECT m.id, m.parent_id, p.depth + 1 FROM cf_agents_session_messages m\n JOIN path p ON m.id = p.parent_id\n WHERE m.session_id = ? AND p.depth < ${MAX_PATH_DEPTH}\n )\n SELECT path.id AS id FROM path\n WHERE NOT ${hiddenOverlayRowSql(\"path.id\", \"?\")}\n ORDER BY depth DESC`,\n [sessionId, leaf, sessionId, sessionId]\n )\n .map((row) => row.id);\n }\n\n /**\n * Split path rows into bounded hydration queries: at most `maxRows` rows,\n * and — when the rows carry sizes — at most `HISTORY_CONTENT_CHUNK_BYTES`\n * of stored content, with a single oversized row always standing alone.\n */\n *#boundedStatsChunks(\n rows: readonly PathRow[],\n maxRows = HISTORY_CONTENT_CHUNK_SIZE\n ): Generator<readonly PathRow[], void, undefined> {\n let start = 0;\n while (start < rows.length) {\n let end = start;\n let bytes = 0;\n while (end < rows.length && end - start < maxRows) {\n const nextBytes = rows[end].bytes;\n if (end > start && bytes + nextBytes > HISTORY_CONTENT_CHUNK_BYTES) {\n break;\n }\n bytes += nextBytes;\n end++;\n }\n yield rows.slice(start, end);\n start = end;\n }\n }\n\n /**\n * Fetch and parse one already-bounded content window. The common window\n * has no continuation rows at all, so the second query is issued only for\n * the ids that actually carry them and never runs otherwise.\n */\n #contentByStats(\n sessionId: string,\n rows: readonly PathRow[]\n ): Map<string, SessionMessage> {\n const result = new Map<string, SessionMessage>();\n if (rows.length === 0) return result;\n const fetched = this.io.sql<{\n id: string;\n content: string;\n content_chunks: number;\n }>(\n `SELECT id, content, content_chunks FROM cf_agents_session_messages\n WHERE session_id = ? AND id IN (SELECT value FROM json_each(?))`,\n [sessionId, JSON.stringify(rows.map((row) => row.id))]\n );\n const continued = this.#continuations(\n sessionId,\n fetched.filter((row) => row.content_chunks > 0).map((row) => row.id)\n );\n for (const row of fetched) {\n const parsed = this.#parse(\n row.content_chunks === 0\n ? row.content\n : row.content + (continued.get(row.id) ?? \"\")\n );\n if (parsed) result.set(row.id, this.#inline(parsed));\n }\n return result;\n }\n\n /**\n * Stream a known path window without retaining earlier content chunks.\n *\n * The path is first cut into segments — a compaction overlay, or a run of\n * raw rows between overlays — in root → leaf order. `newestFirst` walks\n * those segments, and the rows inside each, from the leaf instead, in\n * small fixed windows, so the first content fetched is the newest and a\n * consumer that stops early never touches older rows.\n */\n async *#streamStats(\n sessionId: string,\n stats: readonly PathRow[],\n signal?: AbortSignal,\n newestFirst = false,\n plannedSpans?: readonly OverlaySpan[]\n ): AsyncGenerator<SessionMessage, void, undefined> {\n const spans =\n plannedSpans ??\n planOverlays(\n stats.map((row) => row.id),\n this.getCompactions(sessionId)\n );\n const spanByStart = new Map(spans.map((span) => [span.startIndex, span]));\n\n type Segment = { overlay: StoredCompaction } | { rows: readonly PathRow[] };\n const segments: Segment[] = [];\n let index = 0;\n while (index < stats.length) {\n const span = spanByStart.get(index);\n if (span) {\n segments.push({ overlay: span.compaction });\n index = span.endIndex + 1;\n continue;\n }\n let runEnd = index + 1;\n while (runEnd < stats.length && !spanByStart.has(runEnd)) runEnd++;\n segments.push({ rows: stats.slice(index, runEnd) });\n index = runEnd;\n }\n if (newestFirst) segments.reverse();\n\n for (const segment of segments) {\n if (signal?.aborted) {\n throw signal.reason ?? new Error(\"History read aborted\");\n }\n if (\"overlay\" in segment) {\n yield overlayMessage(segment.overlay);\n continue;\n }\n const rows = newestFirst ? [...segment.rows].reverse() : segment.rows;\n const windowRows = newestFirst\n ? NEWEST_FIRST_WINDOW_ROWS\n : HISTORY_CONTENT_CHUNK_SIZE;\n for (const chunk of this.#boundedStatsChunks(rows, windowRows)) {\n const content = this.#contentByStats(sessionId, chunk);\n for (const row of chunk) {\n const parsed = content.get(row.id);\n if (parsed) yield parsed;\n }\n if (signal?.aborted) {\n throw signal.reason ?? new Error(\"History read aborted\");\n }\n }\n }\n }\n\n /**\n * Stream the path ending at `leafId` (default: active leaf), root → leaf\n * (or leaf → root with `newestFirst`), compaction overlays collapsed. Peak\n * memory is one bounded content window — never the whole transcript.\n */\n async *streamHistory(\n sessionId: string,\n options: HistoryReadOptions\n ): AsyncGenerator<SessionMessage, void, undefined> {\n if (options.newestFirst === true) {\n yield* this.#walkFromLeaf(sessionId, options.leafId, options.signal);\n return;\n }\n const stats = this.pathRowStats(sessionId, options.leafId);\n if (stats.length === 0) return;\n yield* this.#streamStats(sessionId, stats, options.signal);\n }\n\n /**\n * The path leaf → root as a chain of point reads: each row names its\n * parent, so the next read is known before the current message is\n * yielded, and a consumer that stops early has paid for exactly the rows it\n * saw.\n *\n * Compaction overlays are honored without planning them up front. An\n * overlay that applies to this branch ends at a row the walk reaches\n * before any row it covers, so the raw walk is exact until it lands on\n * some compaction's `toMessageId`. Only then is the remaining prefix read\n * as ids and planned root → leaf — the order overlay selection is defined\n * in — and streamed leaf-first with the overlays collapsed. A lookup that\n * stops in the messages after the last compaction never pays for that.\n */\n async *#walkFromLeaf(\n sessionId: string,\n leafId: string | null | undefined,\n signal?: AbortSignal\n ): AsyncGenerator<SessionMessage, void, undefined> {\n const compactions = this.getCompactions(sessionId);\n const spanEnds = new Set(compactions.map((c) => c.toMessageId));\n const hiddenIds = new Set(\n compactions.map((c) => `${COMPACTION_PREFIX}${c.id}`)\n );\n let next = this.#resolveLeafId(sessionId, leafId);\n let depth = 0;\n while (next !== null && depth <= MAX_PATH_DEPTH) {\n if (signal?.aborted) {\n throw signal.reason ?? new Error(\"History read aborted\");\n }\n if (spanEnds.has(next)) {\n yield* this.#streamOverlaidPrefix(\n sessionId,\n leafId,\n next,\n compactions,\n signal\n );\n return;\n }\n const [row] = this.io.sql<{\n parent_id: string | null;\n content: string;\n content_chunks: number;\n }>(\n `SELECT parent_id, content, content_chunks FROM cf_agents_session_messages\n WHERE session_id = ? AND id = ?`,\n [sessionId, next]\n );\n if (!row) return;\n if (!hiddenIds.has(next)) {\n const json =\n row.content_chunks === 0\n ? row.content\n : row.content +\n (this.#continuations(sessionId, [next]).get(next) ?? \"\");\n const parsed = this.#parse(json);\n if (parsed) yield this.#inline(parsed);\n }\n next = row.parent_id;\n depth++;\n }\n }\n\n /**\n * The path from `fromId` down to the root, leaf-first with overlays\n * collapsed. Spans are planned over the WHOLE path (selection is\n * root → leaf and a chosen span suppresses overlaps inside it), then only\n * those ending at or before `fromId` apply: `fromId` is some compaction's\n * end, and a chosen span reaching past it would have ended at a row the\n * raw walk visited first.\n */\n async *#streamOverlaidPrefix(\n sessionId: string,\n leafId: string | null | undefined,\n fromId: string,\n compactions: readonly StoredCompaction[],\n signal?: AbortSignal\n ): AsyncGenerator<SessionMessage, void, undefined> {\n const ids = this.#pathIds(sessionId, leafId);\n const end = ids.indexOf(fromId);\n if (end === -1) return;\n const spans = planOverlays(ids, compactions).filter(\n (span) => span.endIndex <= end\n );\n yield* this.#streamStats(\n sessionId,\n ids.slice(0, end + 1).map((id) => ({ id, bytes: 0 })),\n signal,\n true,\n spans\n );\n }\n\n async getHistory(\n sessionId: string,\n options: HistoryReadOptions\n ): Promise<SessionMessage[]> {\n const messages: SessionMessage[] = [];\n for await (const message of this.streamHistory(sessionId, options)) {\n messages.push(message);\n }\n return messages;\n }\n\n /**\n * Byte-budgeted read of the most recent messages on the active branch\n * path — the longest suffix whose stored size fits `maxContentBytes`.\n *\n * There is no message-count floor: one used to exist, and it admitted\n * rows regardless of size, so a window of media-heavy messages could\n * hydrate far past the limit meant to bound it. The newest message is\n * always returned even if it alone exceeds the budget, since returning\n * nothing is worse. Overlays whose anchors fall outside the window are\n * skipped, showing the raw recent messages.\n */\n async getRecentHistory(\n sessionId: string,\n maxContentBytes: number,\n leafId?: string | null\n ): Promise<RecentHistoryResult> {\n const { rows: stats, walked, oldestId } = this.#pathRows(sessionId, leafId);\n if (stats.length === 0) {\n return { messages: [], truncated: false, totalContentBytes: 0 };\n }\n const totalContentBytes = stats.reduce((sum, row) => sum + row.bytes, 0);\n let start = stats.length - 1;\n let used = stats[start].bytes;\n while (start > 0) {\n const next = stats[start - 1].bytes;\n if (used + next > maxContentBytes) break;\n start--;\n used += next;\n }\n\n const messages: SessionMessage[] = [];\n for await (const message of this.#streamStats(\n sessionId,\n stats.slice(start)\n )) {\n messages.push(message);\n }\n // The path cap hides older rows exactly as the budget does. A read that\n // filled the cap is truncated only if the oldest row it walked still\n // has a parent; a branch of exactly the cap's length is complete.\n const capped =\n walked > MAX_PATH_DEPTH &&\n oldestId !== null &&\n this.#hasParent(sessionId, oldestId);\n return { messages, truncated: start > 0 || capped, totalContentBytes };\n }\n\n /**\n * Heuristic token estimate for the active path with compaction overlays\n * applied: stamped per-row estimates, minus compacted spans, plus their\n * summaries. Derived from content-free rows on each call; it gates cheap\n * triggers only, and model-reported usage stays authoritative.\n */\n tokenEstimate(sessionId: string): number {\n const leafId = this.latestLeafId(sessionId);\n const memo = this.#pathTokens.get(sessionId);\n if (memo && memo.leafId === leafId)\n return Math.max(0, Math.ceil(memo.total));\n\n const { rows: stats, walked } = this.#pathRows(sessionId);\n const counted = new Set(stats.map((row) => row.id));\n let tokens = stats.reduce((sum, row) => sum + row.tokenEstimate, 0);\n for (const span of planOverlays(\n stats.map((row) => row.id),\n this.getCompactions(sessionId)\n )) {\n for (let i = span.startIndex; i <= span.endIndex; i++) {\n tokens -= stats[i].tokenEstimate;\n counted.delete(stats[i].id);\n }\n tokens += estimateStringTokens(span.compaction.summary);\n }\n this.#pathTokens.set(sessionId, {\n leafId,\n counted,\n total: tokens,\n depth: walked\n });\n return Math.max(0, Math.ceil(tokens));\n }\n\n // ── Writes ───────────────────────────────────────────────────────────────\n\n stripReservedMetadata(message: SessionMessage): SessionMessage {\n if (\n this.#reservedMetadataKeys.length === 0 ||\n typeof message.metadata !== \"object\" ||\n message.metadata === null ||\n Array.isArray(message.metadata)\n ) {\n return message;\n }\n const metadata: Record<string, unknown> = { ...message.metadata };\n let changed = false;\n for (const key of this.#reservedMetadataKeys) {\n if (key in metadata) {\n delete metadata[key];\n changed = true;\n }\n }\n if (!changed) return message;\n if (Object.keys(metadata).length > 0) return { ...message, metadata };\n const { metadata: _dropped, ...withoutMetadata } = message;\n return withoutMetadata;\n }\n\n /**\n * Stamped row estimate: the part heuristic over the message as written,\n * plus a weight per inline file payload so media never counts as zero.\n */\n estimateRowTokens(message: SessionMessage): number {\n let tokens = estimateMessageTokens([message]);\n for (const part of message.parts) {\n if (part.type !== \"file\") continue;\n if (typeof part.url === \"string\" && part.url.startsWith(\"data:\")) {\n tokens += estimateAttachmentTokens(\n part.mediaType ?? \"application/octet-stream\",\n estimatedDataUrlBytes(part.url)\n );\n }\n }\n return tokens;\n }\n\n /**\n * Write one message's slices: the row itself plus its continuation rows.\n * Callers run this inside their own transaction, so a message and its\n * continuations always commit together.\n */\n #writeContinuations(\n sessionId: string,\n id: string,\n slices: readonly string[]\n ): void {\n for (let idx = 1; idx < slices.length; idx++) {\n this.io.sqlWrite(\n `INSERT OR REPLACE INTO cf_agents_session_message_chunks\n (session_id, id, idx, content) VALUES (?, ?, ?, ?)`,\n [sessionId, id, idx, slices[idx]]\n );\n }\n }\n\n /**\n * Durable append. The caller has already sanitized the message. Message,\n * continuation, attachment, and FTS rows commit in one synchronous SQLite\n * transaction. Returns the stored message: the input itself when it was\n * inserted (extraction is lossless, so the two are identical), or the row\n * already holding the id when it was not.\n */\n append(\n sessionId: string,\n message: SessionMessage,\n parentId: string | null | undefined,\n tokenEstimate: number\n ): { inserted: boolean; message: SessionMessage } {\n // A repeated append is answered from storage; the common path (a fresh\n // id) costs a key-only probe rather than a content read.\n if (this.exists(sessionId, message.id)) {\n const existing = this.getMessage(sessionId, message.id);\n if (existing) return { inserted: false, message: existing };\n }\n\n // `undefined` attaches to the tail and needs no validation read. A\n // caller-supplied id is untrusted and falls back to a root append when\n // it does not belong to this session.\n const tail = this.#tail(sessionId);\n let parent: string | null;\n if (parentId === undefined) {\n parent = tail.leafId;\n } else {\n parent = parentId && this.exists(sessionId, parentId) ? parentId : null;\n }\n\n // Inline media leaves the message before it is serialized, so the row\n // holds a pointer and never the payload. Addresses are computed here, out\n // of the transaction; the transaction only writes.\n const {\n message: staged,\n attachments,\n references\n } = extractAttachments(message);\n const json = JSON.stringify(staged);\n const slices = splitContent(json);\n const seq = tail.nextSeq;\n this.io.transaction(() => {\n for (const attachment of attachments) {\n this.#attachments.put(attachment.payload, attachment.hash);\n }\n this.io.sqlWrite(\n `INSERT INTO cf_agents_session_messages\n (session_id, id, seq, parent_id, role, content, content_chunks, token_estimate, created_at, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n sessionId,\n message.id,\n seq,\n parent,\n message.role,\n slices[0],\n slices.length - 1,\n tokenEstimate,\n Date.now(),\n contentDigest(json)\n ]\n );\n this.#writeContinuations(sessionId, message.id, slices);\n this.#attachments.addRefs(sessionId, message.id, references);\n this.#indexFts(sessionId, staged, false);\n });\n\n // The freshly inserted row is the most recent childless node, so it is\n // now the latest leaf — true even for an explicit-parent branch append.\n this.#tails.set(sessionId, { leafId: message.id, nextSeq: seq + 1 });\n const memo = this.#pathTokens.get(sessionId);\n if (memo) {\n if (memo.leafId === parent && memo.depth <= MAX_PATH_DEPTH) {\n // The row extends the memoised path: count it, no re-walk.\n memo.leafId = message.id;\n memo.counted.add(message.id);\n memo.total += tokenEstimate;\n memo.depth += 1;\n } else {\n // A branch, or a path at the cap: the walk would drop its oldest\n // row, which the memo cannot see. Re-derive on the next read.\n this.#pathTokens.delete(sessionId);\n }\n }\n this.io.emit(\"session:message:appended\", {\n sessionId,\n messageId: message.id,\n tokenEstimate\n });\n return { inserted: true, message };\n }\n\n /**\n * Durable update of an existing row. An identical row writes nothing: no\n * row, no continuation, no FTS, no event. The no-op guard compares the\n * FULL content, through the digest the last write stamped, so neither the\n * payload nor a continuation row is read. A row without a digest compares\n * the reassembled content once and is stamped.\n */\n update(\n sessionId: string,\n message: SessionMessage,\n tokenEstimate: number\n ): UpdateOutcome {\n // Key-side columns only; the payload stays in SQLite.\n const oldRows = this.io.sql<{\n content_chunks: number;\n token_estimate: number;\n content_hash: string | null;\n }>(\n \"SELECT content_chunks, token_estimate, content_hash FROM cf_agents_session_messages WHERE session_id = ? AND id = ?\",\n [sessionId, message.id]\n );\n if (oldRows.length === 0) return \"missing\";\n const old = oldRows[0];\n // Compare in stored form: a re-sent identical image extracts to the same\n // address, so an unchanged update still writes nothing.\n const {\n message: staged,\n attachments,\n references\n } = extractAttachments(message);\n const json = JSON.stringify(staged);\n const digest = contentDigest(json);\n if (old.content_hash !== null) {\n if (old.content_hash === digest) return \"unchanged\";\n } else if (this.#content(sessionId, message.id) === json) {\n // Byte-identical: stamp the digest so this row stops paying the\n // read-back. Still `unchanged`: no continuation, FTS, or event.\n this.io.sqlWrite(\n \"UPDATE cf_agents_session_messages SET content_hash = ? WHERE session_id = ? AND id = ?\",\n [digest, sessionId, message.id]\n );\n return \"unchanged\";\n }\n\n const slices = splitContent(json);\n this.io.transaction(() => {\n for (const attachment of attachments) {\n this.#attachments.put(attachment.payload, attachment.hash);\n }\n this.io.sqlWrite(\n `UPDATE cf_agents_session_messages\n SET role = ?, content = ?, content_chunks = ?, token_estimate = ?, content_hash = ?\n WHERE session_id = ? AND id = ?`,\n [\n message.role,\n slices[0],\n slices.length - 1,\n tokenEstimate,\n digest,\n sessionId,\n message.id\n ]\n );\n // A message that shrank leaves surplus continuations behind; they go\n // in the same transaction as the row that stopped referencing them.\n if (old.content_chunks > slices.length - 1) {\n this.io.sqlWrite(\n `DELETE FROM cf_agents_session_message_chunks\n WHERE session_id = ? AND id = ? AND idx > ?`,\n [sessionId, message.id, slices.length - 1]\n );\n }\n this.#writeContinuations(sessionId, message.id, slices);\n // Payloads are stored before references move, so a hash this message\n // still uses is never momentarily unreferenced and collected.\n this.#attachments.replaceRefs(sessionId, message.id, references);\n this.#indexFts(sessionId, staged, true);\n });\n const memo = this.#pathTokens.get(sessionId);\n if (memo?.counted.has(message.id)) {\n memo.total += tokenEstimate - (old.token_estimate ?? 0);\n }\n this.io.emit(\"session:message:updated\", {\n sessionId,\n messageId: message.id\n });\n return \"updated\";\n }\n\n /**\n * Delete rows, SPLICING children to their grandparent so a mid-chain\n * delete never decapitates older history. Only surviving boundary children\n * are rewired: a prefix delete writes one boundary child, not one child\n * per deleted message.\n */\n deleteMessages(sessionId: string, messageIds: string[]): void {\n const uniqueIds = [...new Set(messageIds)];\n if (uniqueIds.length === 0) return;\n const ids = JSON.stringify(uniqueIds);\n\n this.io.transaction(() => {\n this.io.sqlWrite(\n `WITH RECURSIVE\n deleted(id) AS (SELECT value FROM json_each(?)),\n rewire(child_id, ancestor_id, depth) AS (\n SELECT child.id, child.parent_id, 0\n FROM cf_agents_session_messages AS child\n JOIN deleted ON deleted.id = child.parent_id\n WHERE child.session_id = ?\n AND child.id NOT IN (SELECT id FROM deleted)\n UNION ALL\n SELECT rewire.child_id, parent.parent_id, rewire.depth + 1\n FROM rewire\n JOIN cf_agents_session_messages AS parent\n ON parent.id = rewire.ancestor_id\n JOIN deleted ON deleted.id = parent.id\n WHERE parent.session_id = ? AND rewire.depth < 10000\n ),\n nearest(child_id, ancestor_id) AS (\n SELECT child_id, ancestor_id FROM rewire\n WHERE ancestor_id IS NULL\n OR ancestor_id NOT IN (SELECT id FROM deleted)\n )\n UPDATE cf_agents_session_messages\n SET parent_id = (\n SELECT nearest.ancestor_id FROM nearest\n WHERE nearest.child_id = cf_agents_session_messages.id\n )\n WHERE session_id = ?\n AND id IN (SELECT child_id FROM nearest)`,\n [ids, sessionId, sessionId, sessionId]\n );\n this.io.sqlWrite(\n `DELETE FROM cf_agents_session_messages\n WHERE session_id = ? AND id IN (SELECT value FROM json_each(?))`,\n [sessionId, ids]\n );\n this.io.sqlWrite(\n `DELETE FROM cf_agents_session_message_chunks\n WHERE session_id = ? AND id IN (SELECT value FROM json_each(?))`,\n [sessionId, ids]\n );\n this.#attachments.releaseMessages(sessionId, uniqueIds);\n if (this.#fts) {\n this.io.sqlWrite(\n `DELETE FROM cf_agents_session_fts\n WHERE session_id = ? AND id IN (SELECT value FROM json_each(?))`,\n [sessionId, ids]\n );\n }\n });\n // The leaf may be among the deleted rows; re-derive on the next append.\n this.#tails.delete(sessionId);\n this.#pathTokens.delete(sessionId);\n this.io.emit(\"session:messages:deleted\", {\n sessionId,\n count: uniqueIds.length\n });\n }\n\n clearMessages(sessionId: string): void {\n this.io.transaction(() => {\n this.io.sqlWrite(\n \"DELETE FROM cf_agents_session_messages WHERE session_id = ?\",\n [sessionId]\n );\n this.io.sqlWrite(\n \"DELETE FROM cf_agents_session_message_chunks WHERE session_id = ?\",\n [sessionId]\n );\n this.io.sqlWrite(\n \"DELETE FROM cf_agents_session_compactions WHERE session_id = ?\",\n [sessionId]\n );\n this.#attachments.releaseSession(sessionId);\n if (this.#fts) {\n this.io.sqlWrite(\n \"DELETE FROM cf_agents_session_fts WHERE session_id = ?\",\n [sessionId]\n );\n }\n });\n this.#tails.set(sessionId, { leafId: null, nextSeq: 1 });\n this.#pathTokens.delete(sessionId);\n this.io.emit(\"session:cleared\", { sessionId });\n }\n\n // ── Compaction storage ───────────────────────────────────────────────────\n\n addCompaction(\n sessionId: string,\n summary: string,\n fromMessageId: string,\n toMessageId: string\n ): StoredCompaction {\n const id = crypto.randomUUID();\n const now = Date.now();\n const seq =\n this.io.sql<{ seq: number }>(\n \"SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM cf_agents_session_compactions WHERE session_id = ?\",\n [sessionId]\n )[0]?.seq ?? 1;\n this.io.sqlWrite(\n `INSERT INTO cf_agents_session_compactions\n (session_id, id, seq, summary, from_message_id, to_message_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [sessionId, id, seq, summary, fromMessageId, toMessageId, now]\n );\n // The new overlay changes which rows count; re-derive on the next read.\n this.#pathTokens.delete(sessionId);\n this.io.emit(\"session:compacted\", { sessionId, compactionId: id });\n return {\n id,\n summary,\n fromMessageId,\n toMessageId,\n createdAt: new Date(now).toISOString()\n };\n }\n\n getCompactions(sessionId: string): StoredCompaction[] {\n return this.io\n .sql<{\n id: string;\n summary: string;\n from_message_id: string;\n to_message_id: string;\n created_at: number;\n }>(\n `SELECT id, summary, from_message_id, to_message_id, created_at\n FROM cf_agents_session_compactions\n WHERE session_id = ? ORDER BY seq ASC`,\n [sessionId]\n )\n .map((row) => ({\n id: row.id,\n summary: row.summary,\n fromMessageId: row.from_message_id,\n toMessageId: row.to_message_id,\n createdAt: new Date(row.created_at).toISOString()\n }));\n }\n\n // ── Search ───────────────────────────────────────────────────────────────\n\n search(sessionId: string, query: string, limit: number): SearchResult[] {\n this.#ensureFts();\n // Quote the query as a literal phrase, escaping embedded double quotes,\n // so user input cannot inject FTS5 syntax.\n const sanitized = `\"${query.replace(/\"/g, '\"\"')}\"`;\n return this.io\n .sql<{ id: string; role: string; content: string }>(\n `SELECT f.id, f.role, f.content FROM cf_agents_session_fts f\n INNER JOIN cf_agents_session_messages m\n ON m.session_id = f.session_id AND m.id = f.id\n WHERE cf_agents_session_fts MATCH ? AND f.session_id = ?\n AND NOT ${hiddenOverlayRowSql(\"f.id\", \"f.session_id\")}\n ORDER BY rank LIMIT ?`,\n [sanitized, sessionId, limit]\n )\n .map((row) => ({ id: row.id, role: row.role, content: row.content }));\n }\n\n /** Maintain the FTS row when the index exists; an unchanged text writes nothing. */\n #indexFts(\n sessionId: string,\n message: SessionMessage,\n replace: boolean\n ): void {\n if (!this.#fts) return;\n const text = message.parts\n .filter((part) => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\" \");\n if (replace) {\n const existing = this.io.sql<{ content: string }>(\n \"SELECT content FROM cf_agents_session_fts WHERE id = ? AND session_id = ?\",\n [message.id, sessionId]\n );\n if (existing.length > 0) {\n if (existing[0].content === text) return;\n this.io.sqlWrite(\n \"DELETE FROM cf_agents_session_fts WHERE id = ? AND session_id = ?\",\n [message.id, sessionId]\n );\n }\n }\n if (text) {\n this.io.sqlWrite(\n \"INSERT INTO cf_agents_session_fts (id, session_id, role, content) VALUES (?, ?, ?, ?)\",\n [message.id, sessionId, message.role, text]\n );\n }\n }\n\n // ── Import ───────────────────────────────────────────────────────────────\n\n /**\n * Import one historical message verbatim (migrations, cross-DO moves):\n * explicit parent and timestamp, stamped estimate, no change-feed events.\n */\n /** Returns `false` when the id already exists and nothing was written. */\n importMessage(\n sessionId: string,\n message: SessionMessage,\n options: { parentId: string | null; createdAt: number }\n ): boolean {\n const {\n message: staged,\n attachments,\n references\n } = extractAttachments(message);\n const json = JSON.stringify(staged);\n const slices = splitContent(json);\n const tail = this.#tail(sessionId);\n let inserted = 0;\n this.io.transaction(() => {\n for (const attachment of attachments) {\n this.#attachments.put(attachment.payload, attachment.hash);\n }\n inserted = this.io.sqlWrite(\n `INSERT OR IGNORE INTO cf_agents_session_messages\n (session_id, id, seq, parent_id, role, content, content_chunks, token_estimate, created_at, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n sessionId,\n message.id,\n tail.nextSeq,\n options.parentId,\n message.role,\n slices[0],\n slices.length - 1,\n this.estimateRowTokens(message),\n options.createdAt,\n contentDigest(json)\n ]\n );\n if (inserted === 0) return;\n this.#writeContinuations(sessionId, message.id, slices);\n this.#attachments.addRefs(sessionId, message.id, references);\n this.#indexFts(sessionId, staged, false);\n });\n if (inserted === 0) return false;\n this.#tails.set(sessionId, {\n leafId: message.id,\n nextSeq: tail.nextSeq + 1\n });\n this.#pathTokens.delete(sessionId);\n return true;\n }\n\n // ── Parsing ──────────────────────────────────────────────────────────────\n\n /** Put attachment payloads back inline, so a read returns what was written. */\n #inline(message: SessionMessage): SessionMessage {\n return resolveAttachments(message, (hash) => this.#attachments.get(hash));\n }\n\n #parse(json: string): SessionMessage | null {\n try {\n const message = JSON.parse(json);\n if (\n typeof message?.id === \"string\" &&\n typeof message?.role === \"string\" &&\n Array.isArray(message?.parts)\n ) {\n return message;\n }\n } catch {\n /* skip unparseable rows, matching legacy behavior */\n }\n return null;\n }\n}\n","import type { SessionChangeEvent, SessionMessage } from \"./types\";\n\n/**\n * How a host keeps an in-memory transcript in step with one session's change\n * feed. The mirror applies the reduction every such cache needs; the hooks\n * carry the decisions only the host can make.\n */\nexport interface SessionMirrorOptions<M extends { id: string }> {\n /**\n * The cached transcript. Read on every event, so a host that reassigns its\n * array (a public `messages` field, say) is followed rather than\n * shadowed.\n */\n get(): M[];\n /** Install a new array: after a delete, or emptied by a clear. */\n set(messages: M[]): void;\n /** The cached form of a stored message. Defaults to the stored message. */\n transform?(message: SessionMessage): M;\n /**\n * Handle an event instead of the default reduction, returning `true` when\n * it did. A host whose cache cannot express a change in place — a branch\n * append, a compaction — re-reads storage here.\n */\n intercept?(event: SessionChangeEvent): boolean | Promise<boolean>;\n /**\n * After the default reduction wrote `message` for an append or update.\n * `previous` is the cached entry it replaced, if there was one.\n */\n onApplied?(\n event: Extract<SessionChangeEvent, { type: \"append\" | \"update\" }>,\n message: M,\n previous: M | undefined\n ): void;\n}\n\n/**\n * The default reduction, per event:\n *\n * - `append` of a new row: replace the cached entry with its id, or push it.\n * A duplicate append (`inserted: false`) changed nothing.\n * - `update`: replace the cached entry with its id; a row the cache does not\n * hold stays out.\n * - `delete`: drop the removed ids. `clear`: empty the cache.\n * - `import`, `compact`, `compaction`: nothing, since no in-place patch\n * expresses them. A host that must react intercepts them.\n */\nexport function mirrorSessionChanges<M extends { id: string }>(\n subscribe: (\n listener: (event: SessionChangeEvent) => Promise<void>\n ) => () => void,\n sessionId: string,\n options: SessionMirrorOptions<M>\n): () => void {\n const transform =\n options.transform ?? ((message: SessionMessage) => message as unknown as M);\n return subscribe(async (event) => {\n if (event.sessionId !== sessionId) return;\n if (await options.intercept?.(event)) return;\n switch (event.type) {\n case \"append\":\n case \"update\": {\n if (event.type === \"append\" && !event.inserted) return;\n const cache = options.get();\n const index = cache.findIndex((m) => m.id === event.message.id);\n if (index === -1 && event.type === \"update\") return;\n const message = transform(event.message);\n const previous = index === -1 ? undefined : cache[index];\n if (index === -1) cache.push(message);\n else cache[index] = message;\n options.onApplied?.(event, message, previous);\n return;\n }\n case \"delete\": {\n const removed = new Set(event.messageIds);\n options.set(options.get().filter((m) => !removed.has(m.id)));\n return;\n }\n case \"clear\":\n options.set([]);\n return;\n default:\n return;\n }\n });\n}\n","/**\n * Per-session handle returned by `Sessions.session()`. Carries the\n * session-scoped compaction policy and orchestrates every public write:\n * sanitize → strip reserved client metadata → durable write → change feed.\n *\n * The handle stores messages. Prompt assembly (context blocks, frozen\n * prompts, skills) lives in `agents/context` and composes with this handle\n * rather than living inside it.\n */\n\nimport type { CompactResult } from \"./compaction-helpers\";\nimport { COMPACTION_PREFIX, isCompactionMessage } from \"./compaction-helpers\";\nimport type { SessionsCore } from \"./core\";\nimport { mirrorSessionChanges, type SessionMirrorOptions } from \"./mirror\";\nimport { byteLength, sanitizeMessage } from \"./sanitize\";\nimport type {\n AppendOptions,\n AppendResult,\n HistoryBatchReadOptions,\n HistoryReadOptions,\n RecentHistoryResult,\n SearchResult,\n SessionMessage,\n SessionRowStat,\n StoredCompaction,\n WriteOptions\n} from \"./types\";\n\n/** Summarizes a branch into an overlay. */\nexport type CompactionFunction = (\n messages: SessionMessage[]\n) => Promise<CompactResult | null>;\n\nexport class Session {\n readonly sessionId: string;\n readonly #core: SessionsCore;\n readonly #ready: () => Promise<void>;\n\n #compactionFn: CompactionFunction | null = null;\n #tokenThreshold: number | undefined;\n\n /** @internal Constructed by the Sessions capability only. */\n constructor(\n sessionId: string,\n core: SessionsCore,\n ready: () => Promise<void>\n ) {\n this.sessionId = sessionId;\n this.#core = core;\n this.#ready = ready;\n }\n\n // ── Builder ──────────────────────────────────────────────────────────────\n\n /** Register the function `compact()` calls to summarize a branch. */\n onCompaction(fn: CompactionFunction): this {\n this.#compactionFn = fn;\n return this;\n }\n\n /**\n * Auto-compact after an append once the estimated token count crosses the\n * threshold. Requires `onCompaction()`. The estimate is derived from the\n * stamped per-row estimates, never from the transcript.\n */\n compactAfter(tokenThreshold: number): this {\n this.#tokenThreshold = tokenThreshold;\n return this;\n }\n\n // ── Change feed ──────────────────────────────────────────────────────────\n\n /**\n * Keep an in-memory transcript in step with this session's change feed,\n * so write sites never hand-patch it. Returns the unsubscribe function.\n */\n mirror<M extends { id: string }>(\n options: SessionMirrorOptions<M>\n ): () => void {\n return mirrorSessionChanges(\n (listener) => this.#core.subscribe(listener),\n this.sessionId,\n options\n );\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────\n\n /**\n * Stream the active branch path root → leaf (leaf → root with\n * `newestFirst`) with compaction overlays applied. Peak memory is one\n * bounded content window, never the whole transcript, and a consumer that\n * breaks out early leaves the rows it never reached unread.\n */\n async *history(\n options: HistoryReadOptions = {}\n ): AsyncGenerator<SessionMessage, void, undefined> {\n await this.#ready();\n yield* this.#core.streamHistory(this.sessionId, options);\n }\n\n /**\n * Stream history in bounded non-empty batches. Both message count and\n * serialized bytes bound each batch; a single large message is yielded by\n * itself.\n */\n async *historyBatches(\n options: HistoryBatchReadOptions = {}\n ): AsyncGenerator<SessionMessage[], void, undefined> {\n const batchSize = Math.max(1, Math.floor(options.batchSize ?? 50));\n const maxBatchBytes = Math.max(\n 1,\n Math.floor(options.maxBatchBytes ?? 4 * 1024 * 1024)\n );\n let batch: SessionMessage[] = [];\n let batchBytes = 0;\n\n for await (const message of this.history(options)) {\n const bytes = byteLength(JSON.stringify(message));\n if (\n batch.length > 0 &&\n (batch.length >= batchSize || batchBytes + bytes > maxBatchBytes)\n ) {\n yield batch;\n batch = [];\n batchBytes = 0;\n }\n batch.push(message);\n batchBytes += bytes;\n if (batch.length >= batchSize || batchBytes >= maxBatchBytes) {\n yield batch;\n batch = [];\n batchBytes = 0;\n }\n }\n if (batch.length > 0) yield batch;\n }\n\n /**\n * Materialize the whole selected path. Prefer `history()` or\n * `getRecentHistory()` inside a Durable Object: this holds every message\n * of the branch in memory at once.\n */\n async getHistory(\n options: HistoryReadOptions = {}\n ): Promise<SessionMessage[]> {\n await this.#ready();\n return this.#core.getHistory(this.sessionId, options);\n }\n\n /**\n * Byte-budgeted read of the most recent messages on the active branch path\n * (always at least the leaf). The budget counts each row, its continuation\n * rows, and the payloads it points at, so it bounds hydrated memory (#1710).\n */\n async getRecentHistory(\n maxContentBytes: number,\n options: Pick<HistoryReadOptions, \"leafId\"> = {}\n ): Promise<RecentHistoryResult> {\n await this.#ready();\n return this.#core.getRecentHistory(\n this.sessionId,\n maxContentBytes,\n options.leafId\n );\n }\n\n /**\n * Per-row stored sizes (row plus continuation rows and attachments) and\n * stamped token estimates for the active branch path (root → leaf)\n * WITHOUT loading message content.\n */\n async getHistoryRowStats(leafId?: string | null): Promise<SessionRowStat[]> {\n await this.#ready();\n return this.#core.pathRowStats(this.sessionId, leafId);\n }\n\n async getMessage(id: string): Promise<SessionMessage | null> {\n await this.#ready();\n return this.#core.getMessage(this.sessionId, id);\n }\n\n async getLatestLeaf(): Promise<SessionMessage | null> {\n await this.#ready();\n return this.#core.getLatestLeaf(this.sessionId);\n }\n\n async getBranches(messageId: string): Promise<SessionMessage[]> {\n await this.#ready();\n return this.#core.getBranches(this.sessionId, messageId);\n }\n\n /**\n * Full-text search over this session's text parts. The index is built on\n * the first call and maintained from then on.\n */\n async search(\n query: string,\n options?: { limit?: number }\n ): Promise<SearchResult[]> {\n await this.#ready();\n return this.#core.search(this.sessionId, query, options?.limit ?? 20);\n }\n\n // ── Writes ───────────────────────────────────────────────────────────────\n\n /**\n * Append one message. Idempotent on id: a repeated append returns the row\n * already stored and dispatches an `append` event with `inserted: false`.\n *\n * A compaction overlay (a `compaction_` id) is derived on read and is never\n * stored. Clients post their transcript back after every compaction, so\n * this and every other write drops one silently: nothing is written and no\n * event is dispatched.\n */\n async appendMessage(\n message: SessionMessage,\n options: AppendOptions = {}\n ): Promise<AppendResult> {\n await this.#ready();\n if (isCompactionMessage(message)) return { inserted: false, message };\n const prepared = this.#prepare(message, options.source);\n const result = this.#core.append(\n this.sessionId,\n prepared.message,\n options.parentId,\n prepared.tokenEstimate\n );\n await this.#core.notify({\n type: \"append\",\n sessionId: this.sessionId,\n message: result.message,\n parentId: options.parentId,\n inserted: result.inserted\n });\n if (result.inserted) await this.#maybeAutoCompact();\n return result;\n }\n\n /**\n * Update one stored row. Returns the stored form, or `null` when the id is\n * not in this session. An unchanged message writes nothing and dispatches\n * no event. A compaction overlay is never a stored row, so it reports\n * `null`.\n */\n async updateMessage(\n message: SessionMessage,\n options: WriteOptions = {}\n ): Promise<SessionMessage | null> {\n await this.#ready();\n if (isCompactionMessage(message)) return null;\n const prepared = this.#prepare(message, options.source);\n const outcome = this.#core.update(\n this.sessionId,\n prepared.message,\n prepared.tokenEstimate\n );\n if (outcome === \"missing\") return null;\n if (outcome === \"updated\") {\n await this.#core.notify({\n type: \"update\",\n sessionId: this.sessionId,\n message: prepared.message\n });\n }\n return prepared.message;\n }\n\n /**\n * @internal Synchronous write aperture for same-isolate first-party\n * machinery that must commit a message inside a caller-owned SQLite\n * transaction — the stream cutover, where the finished message and the\n * discard of its stream rows land together. Bypasses readiness (the\n * caller owns startup ordering) and defers everything that follows a\n * write — the change-feed dispatch and auto-compaction: call the returned\n * `after()` once the transaction commits, or subscribers (the host's\n * message mirror) never hear about the write. If the transaction rolls\n * back after an `upsert` ran inside it, call `abandon()`: the write is\n * gone but the in-memory tail and token-total caches already moved.\n * Will break without notice; never use from application code.\n */\n __DO_NOT_USE_WILL_BREAK__sync(): {\n upsert(\n message: SessionMessage,\n options?: AppendOptions\n ): { result: AppendResult; after: () => Promise<void> };\n abandon(): void;\n } {\n return {\n abandon: () => this.#core.forgetCaches(this.sessionId),\n upsert: (message, options = {}) => {\n if (isCompactionMessage(message)) {\n return {\n result: { inserted: false, message },\n after: () => Promise.resolve()\n };\n }\n const prepared = this.#prepare(message, options.source);\n if (!this.#core.exists(this.sessionId, message.id)) {\n const result = this.#core.append(\n this.sessionId,\n prepared.message,\n options.parentId,\n prepared.tokenEstimate\n );\n return {\n result,\n after: async () => {\n await this.#core.notify({\n type: \"append\",\n sessionId: this.sessionId,\n message: result.message,\n parentId: options.parentId,\n inserted: result.inserted\n });\n if (result.inserted) await this.#maybeAutoCompact();\n }\n };\n }\n const outcome = this.#core.update(\n this.sessionId,\n prepared.message,\n prepared.tokenEstimate\n );\n return {\n result: { inserted: false, message: prepared.message },\n after: () =>\n outcome === \"updated\"\n ? this.#core.notify({\n type: \"update\",\n sessionId: this.sessionId,\n message: prepared.message\n })\n : Promise.resolve()\n };\n }\n };\n }\n\n async upsertMessage(\n message: SessionMessage,\n options: AppendOptions = {}\n ): Promise<AppendResult> {\n await this.#ready();\n if (isCompactionMessage(message)) return { inserted: false, message };\n if (!this.#core.exists(this.sessionId, message.id)) {\n return this.appendMessage(message, options);\n }\n const stored = await this.updateMessage(message, {\n source: options.source\n });\n return { inserted: false, message: stored ?? message };\n }\n\n /**\n * Import one historical message verbatim (migrations, cross-object moves):\n * explicit parent and timestamp. A row actually written dispatches an\n * `import` change event so a host cache can mark itself stale; it is not\n * an `append`, so a cache does not patch itself per imported row, and an\n * id that already exists writes nothing and dispatches nothing.\n *\n * Unlike the other writes, a `compaction_` id is imported as given: a\n * summary copied from another session's `history()` is the only record of\n * the context it replaced, and later rows may be parented to it. History\n * reads hide such a row only when it duplicates one of this session's own\n * compaction overlays.\n */\n async importMessage(\n message: SessionMessage,\n options: { parentId: string | null; createdAt: number }\n ): Promise<void> {\n await this.#ready();\n const inserted = this.#core.importMessage(this.sessionId, message, options);\n if (!inserted) return;\n await this.#core.notify({\n type: \"import\",\n sessionId: this.sessionId,\n message,\n parentId: options.parentId\n });\n }\n\n async deleteMessages(messageIds: string[]): Promise<void> {\n await this.#ready();\n this.#core.deleteMessages(this.sessionId, messageIds);\n await this.#core.notify({\n type: \"delete\",\n sessionId: this.sessionId,\n messageIds\n });\n }\n\n async clearMessages(): Promise<void> {\n await this.#ready();\n this.#core.clearMessages(this.sessionId);\n await this.#core.notify({ type: \"clear\", sessionId: this.sessionId });\n }\n\n // ── Compaction ───────────────────────────────────────────────────────────\n\n /**\n * Store an overlay directly. Dispatches a `compaction` change event: the\n * rows are untouched, but what a path read returns has changed.\n */\n async addCompaction(\n summary: string,\n fromMessageId: string,\n toMessageId: string\n ): Promise<StoredCompaction> {\n await this.#ready();\n const compaction = this.#core.addCompaction(\n this.sessionId,\n summary,\n fromMessageId,\n toMessageId\n );\n await this.#core.notify({\n type: \"compaction\",\n sessionId: this.sessionId,\n compaction\n });\n return compaction;\n }\n\n async getCompactions(): Promise<StoredCompaction[]> {\n await this.#ready();\n return this.#core.getCompactions(this.sessionId);\n }\n\n /**\n * Run the registered compaction function and store the result as an\n * overlay. When `leafId` is given, compact that root-to-leaf branch instead\n * of the active branch. Requires `onCompaction()`. A compaction function\n * that throws is reported through the `session:error` capability event and\n * the call returns `null`.\n */\n async compact(leafId?: string | null): Promise<CompactResult | null> {\n await this.#ready();\n const fn = this.#compactionFn;\n if (!fn) {\n throw new Error(\n \"No compaction function registered. Call onCompaction() first.\"\n );\n }\n const history = await this.#core.getHistory(this.sessionId, { leafId });\n\n let result: CompactResult | null;\n try {\n result = await fn(history);\n } catch (error) {\n this.#core.io.emit(\"session:error\", {\n sessionId: this.sessionId,\n error: error instanceof Error ? error.message : String(error)\n });\n return null;\n }\n if (!result) return null;\n\n const historyIds = new Set(history.map((message) => message.id));\n if (!historyIds.has(result.toMessageId)) return null;\n\n // Iterative compaction extends only an overlay visible on this branch.\n const existing = this.#core\n .getCompactions(this.sessionId)\n .filter(\n (compaction) =>\n historyIds.has(`${COMPACTION_PREFIX}${compaction.id}`) ||\n (historyIds.has(compaction.fromMessageId) &&\n historyIds.has(compaction.toMessageId))\n );\n const fromId =\n existing.length > 0 ? existing[0].fromMessageId : result.fromMessageId;\n\n this.#core.addCompaction(\n this.sessionId,\n result.summary,\n fromId,\n result.toMessageId\n );\n await this.#core.notify({ type: \"compact\", sessionId: this.sessionId });\n return { ...result, fromMessageId: fromId };\n }\n\n // ── Internal ─────────────────────────────────────────────────────────────\n\n /**\n * The shared write pipeline: sanitize provider metadata and strip reserved\n * metadata on client-source input. Content is never truncated and never\n * too large: a message that exceeds the row budget is split across\n * continuation rows by the durable write.\n */\n #prepare(\n message: SessionMessage,\n source: \"client\" | \"server\" | undefined\n ): { message: SessionMessage; tokenEstimate: number } {\n let prepared = sanitizeMessage(message);\n if (source === \"client\") {\n prepared = this.#core.stripReservedMetadata(prepared);\n }\n return {\n message: prepared,\n tokenEstimate: this.#core.estimateRowTokens(prepared)\n };\n }\n\n /** Gate on the derived estimate, then compact. Failures are non-fatal. */\n async #maybeAutoCompact(): Promise<void> {\n const threshold = this.#tokenThreshold;\n if (threshold == null || !this.#compactionFn) return;\n if (this.#core.tokenEstimate(this.sessionId) <= threshold) return;\n try {\n await this.compact();\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n console.warn(`[Sessions] auto-compaction failed: ${detail}`);\n this.#core.io.emit(\"session:error\", {\n sessionId: this.sessionId,\n error: detail\n });\n }\n }\n}\n","/**\n * Durable conversation history for Lifecycle Objects. `Sessions` owns the\n * `cf_agents_session_*` tables: tree-structured messages (branch\n * regeneration, latest-leaf paths), compaction overlays, and full-text\n * search whose index is built on first use. A message larger than one\n * SQLite row is split across continuation rows and reassembled on read, so\n * nothing is ever too large to store.\n *\n * Sessions consumes only the standard capability services — storage and\n * events. It needs no alarm, so it also works on facets (facets have\n * isolated SQLite but no independent alarm slot).\n *\n * @experimental The API surface may change before stabilizing.\n */\n\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport { SqlError } from \"../sql-error\";\nimport { SessionsCore } from \"./core\";\nimport { Session } from \"./handle\";\nimport type { SqlParam } from \"./io\";\nimport type { SessionChangeListener, SessionsOptions } from \"./types\";\n\nconst SESSIONS_SCHEMA_VERSION_KEY = \"cf_agents:sessions_schema_version\";\nconst CURRENT_SESSIONS_SCHEMA_VERSION = 1;\n\n/**\n * Durable conversation history for a Lifecycle Object.\n *\n * `session()` returns a per-session handle for reads (streamed, byte\n * budgeted), writes (sanitized, media offloaded), branch navigation, and\n * compaction overlays. `subscribe()` is the change feed a cache-owning host\n * mirrors.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class Sessions extends LifecycleCapability {\n readonly #core: SessionsCore;\n readonly #handles = new Map<string, Session>();\n\n constructor(options: SessionsOptions = {}) {\n super(\"sessions\");\n // Every service is reached lazily, so a handle or a subscription may be\n // taken before `Lifecycle.use()` installs the capability.\n const exec = (query: string, params: SqlParam[]) => {\n try {\n return this.lifecycle.storage.sql.exec(query, ...params);\n } catch (cause) {\n throw new SqlError(query, cause);\n }\n };\n this.#core = new SessionsCore(options, {\n // SAFETY: Sessions queries select from its own schema; T describes the\n // projected columns of the accompanying query text.\n sql: <T>(query: string, params: SqlParam[]) =>\n [...exec(query, params)] as T[],\n sqlWrite: (query, params) => exec(query, params).rowsWritten,\n transaction: (fn) => this.lifecycle.storage.transactionSync(fn),\n emit: (type, payload) => this.lifecycle.events.emit(type, payload)\n });\n }\n\n // ── Lifecycle capability hooks ───────────────────────────────────────────\n\n /** Migrate session storage during Lifecycle startup. */\n async onStart(): Promise<void> {\n const storage = this.lifecycle.storage;\n const version =\n (await storage.get<number>(SESSIONS_SCHEMA_VERSION_KEY)) ?? 0;\n this.#core.ensureTables();\n if (version < CURRENT_SESSIONS_SCHEMA_VERSION) {\n // Stamp the version only when every legacy source was fully lifted. A\n // partial migration that recorded success would never be retried, and\n // the rows it left behind would stay unreachable through the new API\n // forever. The lift is idempotent, so retrying costs reads and nothing\n // else.\n if (this.#core.migrateLegacy()) {\n await storage.put(\n SESSIONS_SCHEMA_VERSION_KEY,\n CURRENT_SESSIONS_SCHEMA_VERSION\n );\n }\n }\n }\n\n // ── Surface ──────────────────────────────────────────────────────────────\n\n /**\n * The handle for one session. The default (empty) id is the primary path:\n * in the one-object-per-conversation model a Durable Object holds exactly\n * one session. Handles are cached, so per-session configuration (the\n * compaction trigger) survives repeated calls.\n */\n session(sessionId = \"\"): Session {\n const existing = this.#handles.get(sessionId);\n if (existing) return existing;\n const handle = new Session(sessionId, this.#core, () =>\n this.lifecycle.ready()\n );\n this.#handles.set(sessionId, handle);\n return handle;\n }\n\n /**\n * Subscribe to the change feed: ordered dispatch after every durable\n * write, with the stored message. The host cache mirror lives here;\n * telemetry additionally flows through capability events.\n */\n subscribe(listener: SessionChangeListener): () => void {\n return this.#core.subscribe(listener);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"streams-DW-UCOUc.js","names":[],"sources":["../src/streams/errors.ts","../src/streams/streams.ts"],"sourcesContent":["/**\n * Error classes for the Streams capability. Each carries a stable `name` so\n * hosts and tests can classify failures without depending on message text.\n */\n\n/**\n * Thrown when `open()` targets a terminal stream, or an append reaches a\n * stream that settled (or was deleted) after the writer was created.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class StreamClosedError extends Error {\n /** The stream that no longer accepts writes. */\n readonly streamId: string;\n\n constructor(streamId: string, detail: string) {\n super(`Stream \"${streamId}\" is closed: ${detail}`);\n this.name = \"StreamClosedError\";\n this.streamId = streamId;\n }\n}\n\n/**\n * Thrown when `read()` targets a stream that was never opened (or was\n * deleted). `status()` returns `null` instead, for existence probes.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class StreamNotFoundError extends Error {\n readonly streamId: string;\n\n constructor(streamId: string) {\n super(\n `Stream \"${streamId}\" does not exist. Open it before reading, or use ` +\n `status() to probe for existence.`\n );\n this.name = \"StreamNotFoundError\";\n this.streamId = streamId;\n }\n}\n\n/**\n * Thrown when a chunk or metadata value is not JSON-serializable or exceeds\n * the configured size limit.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class StreamSerializationError extends Error {\n constructor(context: string, detail: string) {\n super(`Cannot serialize ${context}: ${detail}`);\n this.name = \"StreamSerializationError\";\n }\n}\n","/**\n * Durable incremental output for Lifecycle Objects. `Streams` owns the\n * `cf_agents_streams` and `cf_agents_stream_blocks` tables: an ordered,\n * durable chunk log per stream with a monotonic cursor, replay-then-tail\n * reads, and terminal status that doubles as recovery evidence for the\n * Tasks capability (which composes through checkpointed cursors, never\n * imports).\n *\n * Streams consumes only the standard capability services — storage and\n * events. It needs no alarm, so it also works on facets. Live fanout is\n * in-isolate: a Durable Object executes in one isolate at a time, so every\n * concurrent reader shares the producer's isolate; readers that outlive an\n * isolate replay from their cursor on reconnect.\n */\n\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport { SqlError } from \"../sql-error\";\nimport {\n StreamClosedError,\n StreamNotFoundError,\n StreamSerializationError\n} from \"./errors\";\nimport type {\n StreamChunk,\n StreamChunkRow,\n StreamJson,\n StreamListOptions,\n StreamOpenOptions,\n StreamReadBatchesOptions,\n StreamReadOptions,\n StreamRow,\n StreamSettleOptions,\n StreamState,\n StreamStatus,\n StreamWriter\n} from \"./types\";\n\nconst STREAM_SCHEMA_VERSION_KEY = \"cf_agents:streams_schema_version\";\nconst CURRENT_STREAM_SCHEMA_VERSION = 2;\n\n/**\n * A block row grows by UPDATE until its body reaches this many characters,\n * then the next append opens a new block. Sized well under the 2 MiB row\n * limit so a 1 MiB chunk always fits in a fresh block, and small enough\n * that a replay page parses one block at a time.\n */\nconst BLOCK_MAX_CHARS = 256 * 1024;\n/** Rows read per page while folding one stream's v1 chunk rows into blocks. */\nconst LEGACY_FOLD_PAGE_ROWS = 500;\n\n/** Default ceiling for one serialized chunk (1 MiB). */\nexport const DEFAULT_MAX_CHUNK_BYTES = 1_048_576;\n\nconst MAX_STREAM_ID_LENGTH = 256;\nconst READ_BATCH_SIZE = 100;\nconst DEFAULT_LIST_LIMIT = 100;\n\nconst utf8 = new TextEncoder();\n\n/**\n * Policy for a Streams capability.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport interface StreamsOptions {\n /** Ceiling for one serialized chunk. Default: 1 MiB. */\n readonly maxChunkBytes?: number;\n}\n\n/**\n * @internal Synchronous operations returned by\n * {@link Streams.__DO_NOT_USE_WILL_BREAK__sync}. For same-isolate first-party\n * machinery only (the chat `ResumableStream` adapter); every method bypasses\n * `lifecycle.ready()`, so the caller owns startup ordering.\n */\nexport interface StreamsSyncInternal {\n /** Idempotent DDL — safe to call before the Lifecycle starts. */\n ensureTables(): void;\n getStream(streamId: string): StreamRow | undefined;\n /** Insert a live stream row (no idempotency — caller checks first). */\n insertStream(\n streamId: string,\n tag: string | null,\n metadata: Record<string, StreamJson> | undefined\n ): void;\n /** Replace a stream row's metadata, whatever its state. */\n setMetadata(streamId: string, metadata: Record<string, StreamJson>): void;\n /** The read-fenced append: one chunk insert at the log tail, reader wakeup. */\n append(streamId: string, chunk: StreamJson): number;\n /**\n * The newest chunk's timestamp, or null for an empty log. One PK-served\n * read — the per-append liveness signal retention sweeps verify against\n * (a live row's `updated_at` is set at open and not bumped by appends).\n */\n lastChunkAt(streamId: string): number | null;\n /**\n * Segments durably appended so far: the chunk log's tail, read in the\n * calling synchronous block. Zero for an unknown stream.\n */\n cursor(streamId: string): number;\n /**\n * Observe every deletion of a stream's rows — the public `delete()`, the\n * aperture's own deletes, and a cutover's discard — with the row and its\n * cursor as they were just before removal, in the same synchronous block\n * (and, for a cutover, the same transaction). Hooks must be synchronous\n * and must not await: the cutover runs them inside `transactionSync`.\n * The chat adapter uses this to keep its recovery progress marker exact\n * however a chat row leaves the table. Returns the unsubscribe: an owner\n * constructed again (a host whose startup retried) must drop its earlier\n * hook, or a deletion is observed once per construction.\n */\n onDelete(hook: (row: StreamRow, cursor: number) => void): () => void;\n /**\n * Idempotent settlement with events and reader wakeup. With `options`,\n * the settle, the caller's `commit` writes and the log discard run in\n * one SQLite transaction (see {@link StreamSettleOptions}). Returns\n * whether the stream transitioned; on a repeat or a deleted stream the\n * `commit` callback does not run.\n */\n settle(\n streamId: string,\n state: \"completed\" | \"errored\",\n reason: string | null,\n options?: StreamSettleOptions\n ): boolean;\n /** Delete a stream and its chunks regardless of state. */\n deleteUnchecked(streamId: string): void;\n /** Delete many streams and their chunks regardless of state, silently. */\n deleteMany(streamIds: string[]): void;\n /**\n * One page of a stream's chunk log from `fromSeq` (inclusive), ordered by\n * seq. Paged rather than read-it-all so replaying a large stream holds\n * one page of segment bodies in memory, not the whole turn.\n */\n readChunks(\n streamId: string,\n fromSeq: number,\n limit: number\n ): StreamChunkRow[];\n /** Every stream row, newest first (created_at, then insertion order). */\n listRows(): StreamRow[];\n /**\n * Every row carrying a tag, newest first, optionally narrowed to one\n * state. Tags are non-unique and the table is shared across producers,\n * so callers apply their own ownership filter (e.g. chat's metadata\n * marker) rather than trusting the newest row.\n */\n rowsByTag(tag: string, state?: StreamState): StreamRow[];\n /**\n * Import one historical stream row verbatim (migrations, test seeding):\n * explicit timestamps and count, no events, no wakeups.\n */\n importStream(row: {\n streamId: string;\n state: StreamState;\n tag: string | null;\n metadata: Record<string, StreamJson> | undefined;\n chunkCount: number;\n createdAt: number;\n updatedAt: number;\n closedAt: number | null;\n }): void;\n /**\n * Import one historical chunk at the log's tail: one INSERT, nothing else.\n * The stream row is not touched — importers pass the final `chunkCount`\n * and `updatedAt` to {@link importStream}, so the row is exact at rest\n * without a per-chunk row write.\n */\n importChunk(streamId: string, chunk: StreamJson, createdAt: number): void;\n}\n\n/**\n * Durable incremental output for a Lifecycle Object.\n *\n * `open()` a stream, `append()` chunks (synchronous durable writes that wake\n * live readers), and settle it with `close()` or `error()`. `read()` replays\n * persisted chunks from a cursor and then tails live appends; `status()`\n * reports the state and cursor — the recovery evidence a Task's `recover`\n * callback consults after its producer was interrupted.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class Streams extends LifecycleCapability {\n readonly #maxChunkBytes: number;\n /** Wakeup callbacks for readers tailing a live stream, per stream. */\n readonly #wakeups = new Map<string, Set<() => void>>();\n /**\n * Whether the schema v1 `cf_agents_stream_chunks` table still exists.\n * Its rows are folded into blocks one stream at a time, on first touch\n * (see {@link #foldLegacyChunks}); the table is dropped once empty.\n */\n #legacyChunkTable = false;\n\n constructor(options: StreamsOptions = {}) {\n super(\"streams\");\n this.#maxChunkBytes = options.maxChunkBytes ?? DEFAULT_MAX_CHUNK_BYTES;\n }\n\n // ── Lifecycle capability hooks ───────────────────────────────────────────\n\n /** Migrate stream storage during Lifecycle startup. */\n async onStart(): Promise<void> {\n const storage = this.lifecycle.storage;\n const version = (await storage.get<number>(STREAM_SCHEMA_VERSION_KEY)) ?? 0;\n if (version < CURRENT_STREAM_SCHEMA_VERSION) {\n this.#ensureTables();\n await storage.put(\n STREAM_SCHEMA_VERSION_KEY,\n CURRENT_STREAM_SCHEMA_VERSION\n );\n }\n if (version >= 1) this.#legacyChunkTable = this.#hasLegacyChunkTable();\n }\n\n // ── Producer surface ─────────────────────────────────────────────────────\n\n /**\n * Open a stream for writing. Idempotent on the id: reopening a live stream\n * returns a writer positioned at its current cursor; reopening a terminal\n * stream throws {@link StreamClosedError}.\n */\n async open(\n streamId: string,\n options: StreamOpenOptions = {}\n ): Promise<StreamWriter> {\n await this.lifecycle.ready();\n this.#validateStreamId(streamId);\n\n const existing = this.#getStream(streamId);\n if (existing) {\n if (existing.state !== \"streaming\") {\n throw new StreamClosedError(\n streamId,\n `already settled as ${existing.state}`\n );\n }\n // The tag is part of the stream's identity, fixed at creation: a\n // reopen naming a different tag is a config conflict, not a resume.\n if (\n options.tag !== undefined &&\n options.tag !== (existing.tag ?? undefined)\n ) {\n throw new Error(\n `Stream \"${streamId}\" is already open with tag ${JSON.stringify(existing.tag)}; refusing reopen with tag ${JSON.stringify(options.tag)}`\n );\n }\n return this.#writer(streamId);\n }\n\n const metadataJson = this.#serialize(\n options.metadata,\n `metadata for stream \"${streamId}\"`\n );\n const now = Date.now();\n this.#sql`\n INSERT INTO cf_agents_streams\n (stream_id, state, tag, metadata, chunk_count, created_at, updated_at)\n VALUES\n (${streamId}, 'streaming', ${options.tag ?? null}, ${metadataJson}, 0, ${now}, ${now})\n `;\n this.#emit(\"stream:opened\", { streamId });\n return this.#writer(streamId);\n }\n\n // ── Consumer surface ─────────────────────────────────────────────────────\n\n /**\n * Replay persisted chunks from `from` (inclusive), then tail live appends\n * until the stream settles. Ends when the stream reaches a terminal state\n * and every durable chunk has been yielded; a read of an `errored` stream\n * still yields its chunks and then simply ends — consult {@link status}\n * for the terminal outcome. Aborting `options.signal` throws its reason.\n */\n async *read(\n streamId: string,\n options: StreamReadOptions = {}\n ): AsyncGenerator<StreamChunk, void, undefined> {\n const signal = options.signal;\n for await (const batch of this.readBatches(streamId, options)) {\n for (const item of batch) {\n if (signal?.aborted) throw signal.reason ?? new Error(\"Read aborted\");\n yield item;\n }\n }\n }\n\n /**\n * Batched form of {@link read}: yields non-empty arrays of consecutive\n * chunks instead of one chunk at a time. Replay yields up to\n * `options.batchSize` chunks per array; a live tail yields everything\n * that accumulated since the last wakeup as one array — so a consumer\n * paying per write (an SSE flush, an RPC hop, a history append) pays\n * once per backlog, not once per chunk. Same lifecycle as {@link read}:\n * ends when the stream settles and every durable chunk has been\n * yielded; aborting `options.signal` throws its reason.\n */\n async *readBatches(\n streamId: string,\n options: StreamReadBatchesOptions = {}\n ): AsyncGenerator<StreamChunk[], void, undefined> {\n await this.lifecycle.ready();\n const signal = options.signal;\n const batchSize = Math.max(\n 1,\n Math.floor(options.batchSize ?? READ_BATCH_SIZE)\n );\n let next = Math.max(0, options.from ?? 0);\n let signaledUpToDate = false;\n\n if (this.#state(streamId) === undefined) {\n throw new StreamNotFoundError(streamId);\n }\n\n for (;;) {\n if (signal?.aborted) throw signal.reason ?? new Error(\"Read aborted\");\n\n const rows = this.#readChunks(streamId, next, batchSize);\n if (rows.length > 0) {\n next = rows[rows.length - 1].seq + 1;\n yield rows.map((row) => ({\n seq: row.seq,\n chunk: JSON.parse(row.chunk) as StreamJson\n }));\n }\n if (rows.length === batchSize) continue;\n\n // A short batch means every durable chunk up to this instant has been\n // yielded — the reader has reached the tail (caught up ≠ ended: a live\n // stream keeps tailing from here).\n if (!signaledUpToDate) {\n signaledUpToDate = true;\n options.onUpToDate?.();\n // The callback is application code and can synchronously append to\n // this same stream — a wake that would fire before any waiter is\n // registered. Re-poll instead of sleeping so that append is never a\n // lost wakeup; everything from the next query to waiter\n // registration is synchronous, so no other append can slip past.\n continue;\n }\n\n const state = this.#state(streamId);\n if (state === undefined) return; // deleted mid-read: nothing further\n if (state !== \"streaming\") {\n // Terminal. Appends happen-before settlement (the append fence\n // rejects a settled stream), so a poll that returned zero rows in\n // this same synchronous block proves every durable chunk has been\n // yielded; a non-empty short batch yielded (a suspension point),\n // so re-poll to drain the remainder first.\n if (rows.length === 0) return;\n continue;\n }\n\n // Live tail: wait for the next append or settlement, then re-poll.\n // Wakeups carry no data, so there is nothing to buffer or dedupe.\n await this.#waitForWakeup(streamId, signal);\n }\n }\n\n /** Read one stream's state and cursor, or null when it does not exist. */\n async status(streamId: string): Promise<StreamStatus | null> {\n await this.lifecycle.ready();\n const row = this.#getStream(streamId);\n return row ? this.#rowToStatus(row) : null;\n }\n\n /** List streams, newest first. */\n async list(options: StreamListOptions = {}): Promise<StreamStatus[]> {\n await this.lifecycle.ready();\n const states = Array.isArray(options.state)\n ? options.state\n : options.state !== undefined\n ? [options.state]\n : [];\n let query = \"SELECT * FROM cf_agents_streams WHERE 1 = 1\";\n const params: (string | number)[] = [];\n if (states.length > 0) {\n query += ` AND state IN (${states.map(() => \"?\").join(\", \")})`;\n params.push(...states);\n }\n if (options.tag !== undefined) {\n query += \" AND tag = ?\";\n params.push(options.tag);\n }\n query += \" ORDER BY created_at DESC, stream_id DESC LIMIT ?\";\n params.push(options.limit ?? DEFAULT_LIST_LIMIT);\n let rows: unknown[];\n try {\n rows = this.lifecycle.storage.sql.exec(query, ...params).toArray();\n } catch (cause) {\n throw new SqlError(query, cause);\n }\n // SAFETY: the query selects * from Streams' own schema.\n return (rows as StreamRow[]).map((row) => this.#rowToStatus(row));\n }\n\n /**\n * Delete one terminal stream and its chunk log.\n *\n * @returns True when a terminal stream was deleted; false when none\n * exists. Throws on a live stream — settle it first.\n */\n async delete(streamId: string): Promise<boolean> {\n await this.lifecycle.ready();\n const row = this.#getStream(streamId);\n if (!row) return false;\n if (row.state === \"streaming\") {\n throw new Error(\n `Cannot delete live stream \"${streamId}\"; close() or error() it first`\n );\n }\n this.#deleteRows(streamId);\n this.#emit(\"stream:deleted\", { streamId });\n return true;\n }\n\n // ── Internal sync aperture ───────────────────────────────────────────────\n\n /**\n * @internal Synchronous storage operations for same-isolate first-party\n * machinery — today the chat `ResumableStream` adapter, whose whole public\n * surface is synchronous and constructed before the Lifecycle starts.\n * Bypasses `lifecycle.ready()`: the caller owns startup ordering. The\n * invariant-bearing writes (append fence, settlement, wakeups, events) go\n * through the same private methods as the public API, so live readers and\n * diagnostics observe aperture writes exactly like capability writes. Will\n * break without notice; never use from application code.\n */\n __DO_NOT_USE_WILL_BREAK__sync(): StreamsSyncInternal {\n return {\n ensureTables: () => this.#ensureTables(),\n getStream: (streamId) => this.#getStream(streamId),\n insertStream: (streamId, tag, metadata) => {\n this.#validateStreamId(streamId);\n const metadataJson = this.#serialize(\n metadata,\n `metadata for stream \"${streamId}\"`\n );\n const now = Date.now();\n this.#sql`\n INSERT INTO cf_agents_streams\n (stream_id, state, tag, metadata, chunk_count, created_at, updated_at)\n VALUES\n (${streamId}, 'streaming', ${tag}, ${metadataJson}, 0, ${now}, ${now})\n `;\n this.#emit(\"stream:opened\", { streamId });\n },\n setMetadata: (streamId, metadata) => {\n const metadataJson = this.#serialize(\n metadata,\n `metadata for stream \"${streamId}\"`\n );\n this.#sql`\n UPDATE cf_agents_streams SET metadata = ${metadataJson}\n WHERE stream_id = ${streamId}\n `;\n },\n append: (streamId, chunk) => this.#append(streamId, chunk),\n lastChunkAt: (streamId) => this.#tail(streamId).lastChunkAt,\n cursor: (streamId) => this.#tail(streamId).nextSeq,\n onDelete: (hook) => {\n this.#deleteHooks.push(hook);\n return () => {\n const index = this.#deleteHooks.indexOf(hook);\n if (index !== -1) this.#deleteHooks.splice(index, 1);\n };\n },\n settle: (streamId, state, reason, options) =>\n this.#settle(streamId, state, reason, options),\n deleteUnchecked: (streamId) => {\n const removed = this.#deleteRows(streamId);\n if (removed > 0) this.#emit(\"stream:deleted\", { streamId });\n // Unchecked deletes can remove a live stream: wake tailing readers\n // so they observe the deletion instead of pending forever.\n this.#wake(streamId);\n },\n deleteMany: (streamIds) => {\n for (const streamId of streamIds) {\n this.#deleteRows(streamId);\n this.#wake(streamId);\n }\n },\n readChunks: (streamId, fromSeq, limit) =>\n this.#readChunks(streamId, fromSeq, limit),\n listRows: () => this.#sql<StreamRow>`\n SELECT * FROM cf_agents_streams ORDER BY created_at DESC, rowid DESC\n `,\n rowsByTag: (tag, state) =>\n state\n ? this.#sql<StreamRow>`\n SELECT * FROM cf_agents_streams\n WHERE tag = ${tag} AND state = ${state}\n ORDER BY created_at DESC, rowid DESC\n `\n : this.#sql<StreamRow>`\n SELECT * FROM cf_agents_streams\n WHERE tag = ${tag}\n ORDER BY created_at DESC, rowid DESC\n `,\n importStream: (row) => {\n this.#validateStreamId(row.streamId);\n const metadataJson = this.#serialize(\n row.metadata,\n `metadata for stream \"${row.streamId}\"`\n );\n this.#sql`\n INSERT INTO cf_agents_streams\n (stream_id, state, tag, metadata, chunk_count,\n created_at, updated_at, closed_at)\n VALUES\n (${row.streamId}, ${row.state}, ${row.tag}, ${metadataJson},\n ${row.chunkCount}, ${row.createdAt}, ${row.updatedAt},\n ${row.closedAt})\n `;\n },\n importChunk: (streamId, chunk, createdAt) => {\n const chunkJson = this.#serialize(\n chunk,\n `chunk for stream \"${streamId}\"`\n );\n if (chunkJson === null) return;\n this.#writeChunk(streamId, chunkJson, createdAt);\n }\n };\n }\n\n // ── Writer ───────────────────────────────────────────────────────────────\n\n #writer(streamId: string): StreamWriter {\n const capability = this;\n return {\n streamId,\n get cursor(): number {\n return capability.#tail(streamId).nextSeq;\n },\n append: (chunk) => this.#append(streamId, chunk),\n close: (options) => this.#settle(streamId, \"completed\", null, options),\n error: (reason, options) =>\n this.#settle(streamId, \"errored\", reason ?? null, options)\n };\n }\n\n #append(streamId: string, chunk: StreamJson): number {\n // Serialization runs BEFORE the fence read: JSON.stringify can execute\n // user toJSON() methods, which may synchronously re-enter this stream.\n const chunkJson = this.#serialize(chunk, `chunk for stream \"${streamId}\"`);\n if (chunkJson === null) {\n throw new StreamSerializationError(\n `chunk for stream \"${streamId}\"`,\n \"chunks must not be undefined\"\n );\n }\n // The fence is a read, and its atomicity lives in the isolate's\n // threading model: a Durable Object executes one synchronous block at\n // a time, so the state check, tail read, and block write below cannot\n // interleave with a settle, delete, or another append. Nothing between\n // here and the write may await or call user code — either would\n // reintroduce the lost-update races the old guarded-UPDATE fence\n // prevented, at the cost of one stream-row write per append.\n const state = this.#state(streamId);\n if (state !== \"streaming\") {\n throw new StreamClosedError(\n streamId,\n state !== undefined ? `already settled as ${state}` : \"it was deleted\"\n );\n }\n const seq = this.#writeChunk(streamId, chunkJson, Date.now());\n this.#wake(streamId);\n return seq;\n }\n\n /**\n * Append one serialized chunk to the stream's open block, or open a new\n * block when the current one is full. Either way it is one billed row:\n * an UPDATE that grows the block body, or the INSERT of the next block.\n * Blocks are what make cleanup cheap — a stream of thousands of chunks\n * is a handful of rows to delete at cutover, not thousands.\n */\n #writeChunk(streamId: string, chunkJson: string, at: number): number {\n const tail = this.#blockTail(streamId);\n const seq = tail?.seq_to ?? 0;\n if (tail && tail.len + chunkJson.length + 1 <= BLOCK_MAX_CHARS) {\n this.#sql`\n UPDATE cf_agents_stream_blocks\n SET body = body || ',' || ${chunkJson}, seq_to = ${seq + 1}, updated_at = ${at}\n WHERE stream_id = ${streamId} AND block = ${tail.block}\n `;\n } else {\n this.#sql`\n INSERT INTO cf_agents_stream_blocks\n (stream_id, block, seq_from, seq_to, body, created_at, updated_at)\n VALUES\n (${streamId}, ${(tail?.block ?? -1) + 1}, ${seq}, ${seq + 1}, ${chunkJson}, ${at}, ${at})\n `;\n }\n return seq;\n }\n\n /**\n * One page of chunks from `fromSeq`, in seq order, parsing one block at\n * a time. A block body is the chunks' JSON texts joined by commas, so\n * `[${body}]` parses straight back into the chunk values.\n */\n #readChunks(\n streamId: string,\n fromSeq: number,\n limit: number\n ): StreamChunkRow[] {\n this.#foldLegacyChunks(streamId);\n const rows: StreamChunkRow[] = [];\n let block = -1;\n while (rows.length < limit) {\n const next = this.#sql<{\n block: number;\n seq_from: number;\n seq_to: number;\n body: string;\n updated_at: number;\n }>`\n SELECT block, seq_from, seq_to, body, updated_at\n FROM cf_agents_stream_blocks\n WHERE stream_id = ${streamId} AND block > ${block} AND seq_to > ${fromSeq}\n ORDER BY block ASC\n LIMIT 1\n `[0];\n if (!next) break;\n block = next.block;\n const chunks = JSON.parse(`[${next.body}]`) as StreamJson[];\n for (\n let seq = Math.max(fromSeq, next.seq_from);\n seq < next.seq_to && rows.length < limit;\n seq++\n ) {\n rows.push({\n stream_id: streamId,\n seq,\n chunk: JSON.stringify(chunks[seq - next.seq_from]),\n created_at: next.updated_at\n });\n }\n }\n return rows;\n }\n\n /** Delete a stream's blocks and row. Returns rows removed from the row table. */\n readonly #deleteHooks: Array<(row: StreamRow, cursor: number) => void> = [];\n\n /**\n * Remove a stream's row and log. Deletion hooks see the row and its\n * cursor first, so an owner can account for the segments before they are\n * gone; this is the single point every delete path passes through.\n */\n #deleteRows(streamId: string): number {\n if (this.#deleteHooks.length > 0) {\n const row = this.#getStream(streamId);\n if (row) {\n const cursor = this.#tail(streamId).nextSeq;\n for (const hook of this.#deleteHooks) hook(row, cursor);\n }\n }\n if (this.#legacyChunkTable) {\n // Unfolded v1 rows die with the stream; no point folding them first.\n this.#sql`DELETE FROM cf_agents_stream_chunks WHERE stream_id = ${streamId}`;\n this.#dropLegacyChunkTableIfEmpty();\n }\n this.#sql`DELETE FROM cf_agents_stream_blocks WHERE stream_id = ${streamId}`;\n return this.#sqlWrite(\"DELETE FROM cf_agents_streams WHERE stream_id = ?\", [\n streamId\n ]);\n }\n\n /**\n * @returns Whether this call transitioned the stream (a repeat, or a\n * deleted stream, is a no-op and returns false: the caller's `commit`\n * does not run and nothing is discarded).\n */\n #settle(\n streamId: string,\n state: Extract<StreamState, \"completed\" | \"errored\">,\n reason: string | null,\n options?: StreamSettleOptions\n ): boolean {\n if (!options?.commit && !options?.discard) {\n const settled = this.#settleRow(streamId, state, reason);\n if (settled) this.#emitSettled(streamId, state, reason);\n // Idempotent for recovery callers; readers re-poll and observe the\n // terminal state either way.\n this.#wake(streamId);\n return settled;\n }\n // The cutover: settle, the caller's writes (a session message), and\n // the discard of this stream's rows commit together or not at all. A\n // throwing `commit` rolls everything back and leaves the stream live.\n // Events and wakeups are side effects outside SQLite, so they fire only\n // once the transaction has returned.\n let settled = false;\n let deleted = false;\n const hadLegacy = this.#legacyChunkTable;\n try {\n this.lifecycle.storage.transactionSync(() => {\n settled = this.#settleRow(streamId, state, reason);\n if (!settled) return;\n options.commit?.();\n if (options.discard) deleted = this.#deleteRows(streamId) > 0;\n });\n } finally {\n // The settle's tail read may have folded this stream's v1 rows and,\n // if they were the last, dropped the legacy table and cleared the\n // flag — inside the transaction. A rollback restores the table but\n // not the flag, so re-derive it from the schema rather than trust a\n // bit written by a transaction that may not have committed.\n if (hadLegacy && !this.#legacyChunkTable) {\n this.#legacyChunkTable = this.#hasLegacyChunkTable();\n }\n }\n if (settled) this.#emitSettled(streamId, state, reason);\n if (deleted) this.#emit(\"stream:deleted\", { streamId });\n this.#wake(streamId);\n return settled;\n }\n\n /**\n * The one guarded UPDATE that ends a stream. Settlement is the moment\n * the stream row becomes exact at rest: the same write stamps the final\n * cursor, read from the chunk log's tail in the same synchronous block.\n * While the stream was live, appends wrote only the chunk log — the\n * row's chunk_count and updated_at were not maintained per append.\n * @returns Whether the row transitioned from `streaming`.\n */\n #settleRow(\n streamId: string,\n state: Extract<StreamState, \"completed\" | \"errored\">,\n reason: string | null\n ): boolean {\n const finalCursor = this.#tail(streamId).nextSeq;\n const settled = this.#sqlWrite(\n `UPDATE cf_agents_streams\n SET state = ?, error_message = ?, closed_at = ?, updated_at = ?,\n chunk_count = ?\n WHERE stream_id = ? AND state = 'streaming'`,\n [state, reason, Date.now(), Date.now(), finalCursor, streamId]\n );\n return settled > 0;\n }\n\n #emitSettled(\n streamId: string,\n state: Extract<StreamState, \"completed\" | \"errored\">,\n reason: string | null\n ): void {\n this.#emit(state === \"completed\" ? \"stream:closed\" : \"stream:errored\", {\n streamId,\n ...(reason !== null ? { reason } : {})\n });\n }\n\n // ── Live fanout ──────────────────────────────────────────────────────────\n\n #wake(streamId: string): void {\n const waiters = this.#wakeups.get(streamId);\n if (!waiters) return;\n this.#wakeups.delete(streamId);\n for (const wake of waiters) wake();\n }\n\n #waitForWakeup(streamId: string, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const waiters = this.#wakeups.get(streamId) ?? new Set();\n this.#wakeups.set(streamId, waiters);\n const wake = () => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n };\n const onAbort = () => {\n waiters.delete(wake);\n // The last aborted waiter removes the map entry too, so abandoned\n // streams do not accumulate empty sets for the isolate's lifetime.\n if (waiters.size === 0 && this.#wakeups.get(streamId) === waiters) {\n this.#wakeups.delete(streamId);\n }\n // Resolve rather than reject: the read loop re-checks the signal\n // first and throws its reason from generator context.\n resolve();\n };\n waiters.add(wake);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n // ── Storage ──────────────────────────────────────────────────────────────\n\n #validateStreamId(streamId: string): void {\n if (typeof streamId !== \"string\" || streamId.length === 0) {\n throw new Error(\"Stream ids must be non-empty strings\");\n }\n if (streamId.length > MAX_STREAM_ID_LENGTH) {\n throw new Error(`Stream id exceeds ${MAX_STREAM_ID_LENGTH} characters`);\n }\n }\n\n #serialize(value: unknown, context: string): string | null {\n if (value === undefined) return null;\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch (error) {\n throw new StreamSerializationError(\n context,\n error instanceof Error ? error.message : String(error)\n );\n }\n if (json === undefined) {\n throw new StreamSerializationError(\n context,\n `value of type ${typeof value} has no JSON representation`\n );\n }\n const bytes = utf8.encode(json).byteLength;\n if (bytes > this.#maxChunkBytes) {\n throw new StreamSerializationError(\n context,\n `serialized size ${bytes} bytes exceeds the ${this.#maxChunkBytes}-byte limit`\n );\n }\n return json;\n }\n\n #sql<T = Record<string, string | number | boolean | null>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[] {\n const query = strings.reduce(\n (result, part, index) =>\n result + part + (index < values.length ? \"?\" : \"\"),\n \"\"\n );\n try {\n // SAFETY: Streams queries select from its own schema; T describes the\n // projected columns of the accompanying query text.\n return [...this.lifecycle.storage.sql.exec(query, ...values)] as T[];\n } catch (cause) {\n throw new SqlError(query, cause);\n }\n }\n\n #sqlWrite(query: string, params: (string | number | null)[]): number {\n try {\n return this.lifecycle.storage.sql.exec(query, ...params).rowsWritten;\n } catch (cause) {\n throw new SqlError(query, cause);\n }\n }\n\n #getStream(streamId: string): StreamRow | undefined {\n const rows = this.#sql<StreamRow>`\n SELECT * FROM cf_agents_streams WHERE stream_id = ${streamId}\n `;\n return rows[0];\n }\n\n /**\n * One stream's state alone — the narrow read for the append fence and the\n * reader loop's liveness checks, which need neither the metadata column\n * nor the (live-stale) counters of the full row.\n */\n #state(streamId: string): StreamState | undefined {\n const rows = this.#sql<{ state: StreamState }>`\n SELECT state FROM cf_agents_streams WHERE stream_id = ${streamId}\n `;\n return rows[0]?.state;\n }\n\n /**\n * The chunk log's tail: the next append sequence and the newest chunk's\n * timestamp. One PK-served read (`ORDER BY seq DESC LIMIT 1`), and the\n * single derivation point for every cursor and per-append-liveness\n * consumer — while a stream is live, the chunk log is authoritative and\n * the stream row's `chunk_count`/`updated_at` are not maintained.\n */\n #tail(streamId: string): { nextSeq: number; lastChunkAt: number | null } {\n const tail = this.#blockTail(streamId);\n return tail\n ? { nextSeq: tail.seq_to, lastChunkAt: tail.updated_at }\n : { nextSeq: 0, lastChunkAt: null };\n }\n\n /** The open block: one PK-served read (`ORDER BY block DESC LIMIT 1`). */\n #blockTail(streamId: string) {\n this.#foldLegacyChunks(streamId);\n return this.#sql<{\n block: number;\n seq_to: number;\n updated_at: number;\n len: number;\n }>`\n SELECT block, seq_to, updated_at, length(body) AS len\n FROM cf_agents_stream_blocks\n WHERE stream_id = ${streamId}\n ORDER BY block DESC\n LIMIT 1\n `[0];\n }\n\n /**\n * Schema v1 → v2 is lazy: the per-chunk `cf_agents_stream_chunks` rows of\n * ONE stream are folded into blocks the first time that stream is\n * touched (a tail read before an append or settle, a replay, a delete).\n * Startup never pays for the whole legacy log, so an object that let a\n * large log accumulate still boots within its memory budget; each fold\n * pages through its stream's rows and writes whole blocks, not one row\n * per chunk. The table is dropped once the last stream's rows are gone.\n */\n #foldLegacyChunks(streamId: string): void {\n if (!this.#legacyChunkTable) return;\n this.lifecycle.storage.transactionSync(() => {\n const tail = this.#sql<{ block: number; seq_to: number }>`\n SELECT block, seq_to FROM cf_agents_stream_blocks\n WHERE stream_id = ${streamId}\n ORDER BY block DESC\n LIMIT 1\n `[0];\n let block = (tail?.block ?? -1) + 1;\n let seq = tail?.seq_to ?? 0;\n let body = \"\";\n let seqFrom = seq;\n let createdAt = 0;\n let updatedAt = 0;\n const flush = () => {\n if (body === \"\") return;\n this.#sql`\n INSERT INTO cf_agents_stream_blocks\n (stream_id, block, seq_from, seq_to, body, created_at, updated_at)\n VALUES\n (${streamId}, ${block}, ${seqFrom}, ${seq}, ${body}, ${createdAt}, ${updatedAt})\n `;\n block += 1;\n body = \"\";\n seqFrom = seq;\n };\n let after = -1;\n for (;;) {\n const rows = this.#sql<{\n seq: number;\n chunk: string;\n created_at: number;\n }>`\n SELECT seq, chunk, created_at FROM cf_agents_stream_chunks\n WHERE stream_id = ${streamId} AND seq > ${after}\n ORDER BY seq ASC\n LIMIT ${LEGACY_FOLD_PAGE_ROWS}\n `;\n for (const row of rows) {\n if (\n body !== \"\" &&\n body.length + row.chunk.length + 1 > BLOCK_MAX_CHARS\n ) {\n flush();\n }\n if (body === \"\") createdAt = row.created_at;\n body = body === \"\" ? row.chunk : `${body},${row.chunk}`;\n updatedAt = row.created_at;\n seq += 1;\n after = row.seq;\n }\n if (rows.length < LEGACY_FOLD_PAGE_ROWS) break;\n }\n flush();\n if (after >= 0) {\n this.#sql`DELETE FROM cf_agents_stream_chunks WHERE stream_id = ${streamId}`;\n }\n this.#dropLegacyChunkTableIfEmpty();\n });\n }\n\n #hasLegacyChunkTable(): boolean {\n return (\n this.#sql<{ name: string }>`\n SELECT name FROM sqlite_master\n WHERE type = 'table' AND name = 'cf_agents_stream_chunks'\n `.length > 0\n );\n }\n\n #dropLegacyChunkTableIfEmpty(): void {\n const remaining = this.#sql<{ n: number }>`\n SELECT COUNT(*) AS n FROM (SELECT 1 FROM cf_agents_stream_chunks LIMIT 1)\n `[0].n;\n if (remaining > 0) return;\n this.#sqlWrite(\"DROP TABLE cf_agents_stream_chunks\", []);\n this.#legacyChunkTable = false;\n }\n\n #rowToStatus(row: StreamRow): StreamStatus {\n // A live row's stored counters are stale by design (appends write only\n // the chunk log), so cursor and updatedAt are derived from the tail;\n // terminal rows were stamped exact at settle and read straight through.\n let cursor = row.chunk_count;\n let updatedAt = row.updated_at;\n if (row.state === \"streaming\") {\n const tail = this.#tail(row.stream_id);\n cursor = tail.nextSeq;\n if (tail.lastChunkAt !== null && tail.lastChunkAt > updatedAt) {\n updatedAt = tail.lastChunkAt;\n }\n }\n return {\n streamId: row.stream_id,\n state: row.state,\n cursor,\n ...(row.tag !== null ? { tag: row.tag } : {}),\n ...(row.metadata !== null\n ? {\n metadata: JSON.parse(row.metadata) as Record<string, StreamJson>\n }\n : {}),\n ...(row.error_message !== null ? { error: row.error_message } : {}),\n createdAt: row.created_at,\n updatedAt,\n ...(row.closed_at !== null ? { closedAt: row.closed_at } : {})\n };\n }\n\n #ensureTables(): void {\n const rawSql = (query: string) => {\n try {\n this.lifecycle.storage.sql.exec(query);\n } catch (cause) {\n throw new SqlError(query, cause);\n }\n };\n // The chunk table is WITHOUT ROWID: Cloudflare bills index maintenance\n // as rows written, and an ordinary rowid table maintains a hidden\n // UNIQUE index for its PRIMARY KEY — one extra billed row on every\n // INSERT and DELETE. WITHOUT ROWID makes the PK the table itself, so a\n // chunk append bills exactly one row (see the write-accounting test).\n // The stream metadata table deliberately stays a rowid table: rowid is\n // the insertion-order tiebreak that keeps \"newest first\" deterministic\n // when rows share a created_at millisecond (stream ids are random),\n // and it costs one billed row per stream open — per turn, never per\n // chunk.\n rawSql(`\n CREATE TABLE IF NOT EXISTS cf_agents_streams (\n stream_id TEXT PRIMARY KEY,\n state TEXT NOT NULL CHECK (state IN (\n 'streaming', 'completed', 'errored'\n )),\n tag TEXT,\n metadata TEXT,\n error_message TEXT,\n chunk_count INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n closed_at INTEGER\n )`);\n rawSql(`\n CREATE INDEX IF NOT EXISTS idx_cf_agents_streams_tag\n ON cf_agents_streams(tag, created_at)\n `);\n rawSql(`\n CREATE TABLE IF NOT EXISTS cf_agents_stream_blocks (\n stream_id TEXT NOT NULL,\n block INTEGER NOT NULL,\n seq_from INTEGER NOT NULL,\n seq_to INTEGER NOT NULL,\n body TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (stream_id, block)\n ) WITHOUT ROWID`);\n }\n\n #emit(type: string, payload: Record<string, unknown>): void {\n this.lifecycle.events.emit(type, payload);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAWA,IAAa,oBAAb,cAAuC,MAAM;CAI3C,YAAY,UAAkB,QAAgB;EAC5C,MAAM,WAAW,SAAS,eAAe,QAAQ;EACjD,KAAK,OAAO;EACZ,KAAK,WAAW;CAClB;AACF;;;;;;;AAQA,IAAa,sBAAb,cAAyC,MAAM;CAG7C,YAAY,UAAkB;EAC5B,MACE,WAAW,SAAS,kFAEtB;EACA,KAAK,OAAO;EACZ,KAAK,WAAW;CAClB;AACF;;;;;;;AAQA,IAAa,2BAAb,cAA8C,MAAM;CAClD,YAAY,SAAiB,QAAgB;EAC3C,MAAM,oBAAoB,QAAQ,IAAI,QAAQ;EAC9C,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;;ACfA,MAAM,4BAA4B;AAClC,MAAM,gCAAgC;;;;;;;AAQtC,MAAM,kBAAkB,MAAM;;AAE9B,MAAM,wBAAwB;;AAG9B,MAAa,0BAA0B;AAEvC,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAE3B,MAAM,OAAO,IAAI,YAAY;;;;;;;;;;;;;;;;;AA6H7B,IAAa,UAAb,cAA6B,oBAAoB;CAW/C,YAAY,UAA0B,CAAC,GAAG;EACxC,MAAM,SAAS;;;6DATG,IAAI,IAA6B,CAAA;sDAMjC,KAAA;iDAqcqD,CAAC,CAAA;EAjcxE,uBAAA,gBAAA,MAAsB,QAAQ,iBAAA,OAAA;CAChC;;CAKA,MAAM,UAAyB;EAC7B,MAAM,UAAU,KAAK,UAAU;EAC/B,MAAM,UAAW,MAAM,QAAQ,IAAY,yBAAyB,KAAM;EAC1E,IAAI,UAAU,+BAA+B;GAC3C,kBAAA,gBAAA,MAAA,aAAA,CAAA,CAAA,KAAA,IAAmB;GACnB,MAAM,QAAQ,IACZ,2BACA,6BACF;EACF;EACA,IAAI,WAAW,GAAG,uBAAA,mBAAA,MAAA,kBAAA,gBAAyB,MAAA,oBAAA,CAAA,CAAA,KAAA,IAA0B,CAAA;CACvE;;;;;;CASA,MAAM,KACJ,UACA,UAA6B,CAAC,GACP;EACvB,MAAM,KAAK,UAAU,MAAM;EAC3B,kBAAA,gBAAA,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,QAAQ;EAE/B,MAAM,WAAA,kBAAA,gBAAW,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;EACzC,IAAI,UAAU;GACZ,IAAI,SAAS,UAAU,aACrB,MAAM,IAAI,kBACR,UACA,sBAAsB,SAAS,OACjC;GAIF,IACE,QAAQ,QAAQ,KAAA,KAChB,QAAQ,SAAS,SAAS,OAAO,KAAA,IAEjC,MAAM,IAAI,MACR,WAAW,SAAS,6BAA6B,KAAK,UAAU,SAAS,GAAG,EAAE,6BAA6B,KAAK,UAAU,QAAQ,GAAG,GACvI;GAEF,OAAA,kBAAA,gBAAO,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,QAAQ;EAC9B;EAEA,MAAM,eAAA,kBAAA,gBAAe,MAAA,UAAA,CAAA,CAAA,KAAA,MACnB,QAAQ,UACR,wBAAwB,SAAS,EACnC;EACA,MAAM,MAAM,KAAK,IAAI;EACrB,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;;WAIF,SAAS,iBAAiB,QAAQ,OAAO,KAAK,IAAI,aAAa,OAAO,IAAI,IAAI,IAAI;;EAEzF,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,iBAAiB,EAAE,SAAS,CAAC;EACxC,OAAA,kBAAA,gBAAO,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,QAAQ;CAC9B;;;;;;;;CAWA,OAAO,KACL,UACA,UAA6B,CAAC,GACgB;EAC9C,MAAM,SAAS,QAAQ;EACvB,WAAW,MAAM,SAAS,KAAK,YAAY,UAAU,OAAO,GAC1D,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,QAAQ,SAAS,MAAM,OAAO,0BAAU,IAAI,MAAM,cAAc;GACpE,MAAM;EACR;CAEJ;;;;;;;;;;;CAYA,OAAO,YACL,UACA,UAAoC,CAAC,GACW;EAChD,MAAM,KAAK,UAAU,MAAM;EAC3B,MAAM,SAAS,QAAQ;EACvB,MAAM,YAAY,KAAK,IACrB,GACA,KAAK,MAAM,QAAQ,aAAa,eAAe,CACjD;EACA,IAAI,OAAO,KAAK,IAAI,GAAG,QAAQ,QAAQ,CAAC;EACxC,IAAI,mBAAmB;EAEvB,IAAA,kBAAA,gBAAI,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,QAAQ,MAAM,KAAA,GAC5B,MAAM,IAAI,oBAAoB,QAAQ;EAGxC,SAAS;GACP,IAAI,QAAQ,SAAS,MAAM,OAAO,0BAAU,IAAI,MAAM,cAAc;GAEpE,MAAM,OAAA,kBAAA,gBAAO,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,UAAU,MAAM,SAAS;GACvD,IAAI,KAAK,SAAS,GAAG;IACnB,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC,MAAM;IACnC,MAAM,KAAK,KAAK,SAAS;KACvB,KAAK,IAAI;KACT,OAAO,KAAK,MAAM,IAAI,KAAK;IAC7B,EAAE;GACJ;GACA,IAAI,KAAK,WAAW,WAAW;GAK/B,IAAI,CAAC,kBAAkB;IACrB,mBAAmB;IACnB,QAAQ,aAAa;IAMrB;GACF;GAEA,MAAM,QAAA,kBAAA,gBAAQ,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,QAAQ;GAClC,IAAI,UAAU,KAAA,GAAW;GACzB,IAAI,UAAU,aAAa;IAMzB,IAAI,KAAK,WAAW,GAAG;IACvB;GACF;GAIA,MAAA,kBAAA,gBAAM,MAAA,cAAA,CAAA,CAAA,KAAA,MAAoB,UAAU,MAAM;EAC5C;CACF;;CAGA,MAAM,OAAO,UAAgD;EAC3D,MAAM,KAAK,UAAU,MAAM;EAC3B,MAAM,MAAA,kBAAA,gBAAM,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;EACpC,OAAO,MAAA,kBAAA,gBAAM,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,IAAI;CACxC;;CAGA,MAAM,KAAK,UAA6B,CAAC,GAA4B;EACnE,MAAM,KAAK,UAAU,MAAM;EAC3B,MAAM,SAAS,MAAM,QAAQ,QAAQ,KAAK,IACtC,QAAQ,QACR,QAAQ,UAAU,KAAA,IAChB,CAAC,QAAQ,KAAK,IACd,CAAC;EACP,IAAI,QAAQ;EACZ,MAAM,SAA8B,CAAC;EACrC,IAAI,OAAO,SAAS,GAAG;GACrB,SAAS,kBAAkB,OAAO,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;GAC5D,OAAO,KAAK,GAAG,MAAM;EACvB;EACA,IAAI,QAAQ,QAAQ,KAAA,GAAW;GAC7B,SAAS;GACT,OAAO,KAAK,QAAQ,GAAG;EACzB;EACA,SAAS;EACT,OAAO,KAAK,QAAQ,SAAS,kBAAkB;EAC/C,IAAI;EACJ,IAAI;GACF,OAAO,KAAK,UAAU,QAAQ,IAAI,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC,QAAQ;EACnE,SAAS,OAAO;GACd,MAAM,IAAI,SAAS,OAAO,KAAK;EACjC;EAEA,OAAQ,KAAqB,KAAK,QAAA,kBAAA,gBAAQ,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC;CAClE;;;;;;;CAQA,MAAM,OAAO,UAAoC;EAC/C,MAAM,KAAK,UAAU,MAAM;EAC3B,MAAM,MAAA,kBAAA,gBAAM,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;EACpC,IAAI,CAAC,KAAK,OAAO;EACjB,IAAI,IAAI,UAAU,aAChB,MAAM,IAAI,MACR,8BAA8B,SAAS,+BACzC;EAEF,kBAAA,gBAAA,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,QAAQ;EACzB,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,kBAAkB,EAAE,SAAS,CAAC;EACzC,OAAO;CACT;;;;;;;;;;;CAcA,gCAAqD;EACnD,OAAO;GACL,oBAAA,kBAAA,gBAAoB,MAAA,aAAA,CAAA,CAAA,KAAA,IAAmB;GACvC,YAAY,aAAA,kBAAA,gBAAa,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;GACjD,eAAe,UAAU,KAAK,aAAa;IACzC,kBAAA,gBAAA,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,QAAQ;IAC/B,MAAM,eAAA,kBAAA,gBAAe,MAAA,UAAA,CAAA,CAAA,KAAA,MACnB,UACA,wBAAwB,SAAS,EACnC;IACA,MAAM,MAAM,KAAK,IAAI;IACrB,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;;eAIF,SAAS,iBAAiB,IAAI,IAAI,aAAa,OAAO,IAAI,IAAI,IAAI;;IAEzE,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,iBAAiB,EAAE,SAAS,CAAC;GAC1C;GACA,cAAc,UAAU,aAAa;IACnC,MAAM,eAAA,kBAAA,gBAAe,MAAA,UAAA,CAAA,CAAA,KAAA,MACnB,UACA,wBAAwB,SAAS,EACnC;IACA,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;oDACmC,aAAa;8BACnC,SAAS;;GAEjC;GACA,SAAS,UAAU,UAAA,kBAAA,gBAAU,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,UAAU,KAAK;GACzD,cAAc,aAAA,kBAAA,gBAAa,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ,CAAC,CAAC;GAChD,SAAS,aAAA,kBAAA,gBAAa,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ,CAAC,CAAC;GAC3C,WAAW,SAAS;IAClB,uBAAA,cAAA,IAAA,CAAA,CAAkB,KAAK,IAAI;IAC3B,aAAa;KACX,MAAM,QAAA,uBAAA,cAAQ,IAAA,CAAA,CAAkB,QAAQ,IAAI;KAC5C,IAAI,UAAU,IAAI,uBAAA,cAAA,IAAA,CAAA,CAAkB,OAAO,OAAO,CAAC;IACrD;GACF;GACA,SAAS,UAAU,OAAO,QAAQ,YAAA,kBAAA,gBAChC,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,UAAU,OAAO,QAAQ,OAAO;GAC/C,kBAAkB,aAAa;IAE7B,IAAA,kBAAA,gBADgB,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,QACvB,IAAI,GAAG,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,kBAAkB,EAAE,SAAS,CAAC;IAG1D,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ;GACrB;GACA,aAAa,cAAc;IACzB,KAAK,MAAM,YAAY,WAAW;KAChC,kBAAA,gBAAA,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,QAAQ;KACzB,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ;IACrB;GACF;GACA,aAAa,UAAU,SAAS,UAAA,kBAAA,gBAC9B,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,UAAU,SAAS,KAAK;GAC3C,gBAAgB,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;GAGzB,YAAY,KAAK,UACf,QACI,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;4BAEO,IAAI,eAAe,MAAM;;gBAGzC,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;4BAEO,IAAI;;;GAG1B,eAAe,QAAQ;IACrB,kBAAA,gBAAA,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,IAAI,QAAQ;IACnC,MAAM,eAAA,kBAAA,gBAAe,MAAA,UAAA,CAAA,CAAA,KAAA,MACnB,IAAI,UACJ,wBAAwB,IAAI,SAAS,EACvC;IACA,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;;;eAKF,IAAI,SAAS,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,aAAa;eACxD,IAAI,WAAW,IAAI,IAAI,UAAU,IAAI,IAAI,UAAU;eACnD,IAAI,SAAS;;GAEtB;GACA,cAAc,UAAU,OAAO,cAAc;IAC3C,MAAM,YAAA,kBAAA,gBAAY,MAAA,UAAA,CAAA,CAAA,KAAA,MAChB,OACA,qBAAqB,SAAS,EAChC;IACA,IAAI,cAAc,MAAM;IACxB,kBAAA,gBAAA,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,UAAU,WAAW,SAAS;GACjD;EACF;CACF;AAsiBF;AAliBE,SAAA,QAAQ,UAAgC;CACtC,MAAM,aAAa;CACnB,OAAO;EACL;EACA,IAAI,SAAiB;GACnB,OAAA,kBAAA,gBAAO,YAAA,KAAA,CAAA,CAAA,KAAA,YAAiB,QAAQ,CAAC,CAAC;EACpC;EACA,SAAS,UAAA,kBAAA,gBAAU,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,UAAU,KAAK;EAC/C,QAAQ,YAAA,kBAAA,gBAAY,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,UAAU,aAAa,MAAM,OAAO;EACrE,QAAQ,QAAQ,YAAA,kBAAA,gBACd,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,UAAU,WAAW,UAAU,MAAM,OAAO;CAC7D;AACF;AAEA,SAAA,QAAQ,UAAkB,OAA2B;CAGnD,MAAM,YAAA,kBAAA,gBAAY,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,OAAO,qBAAqB,SAAS,EAAE;CACzE,IAAI,cAAc,MAChB,MAAM,IAAI,yBACR,qBAAqB,SAAS,IAC9B,8BACF;CASF,MAAM,QAAA,kBAAA,gBAAQ,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,QAAQ;CAClC,IAAI,UAAU,aACZ,MAAM,IAAI,kBACR,UACA,UAAU,KAAA,IAAY,sBAAsB,UAAU,gBACxD;CAEF,MAAM,MAAA,kBAAA,gBAAM,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,UAAU,WAAW,KAAK,IAAI,CAAC;CAC5D,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ;CACnB,OAAO;AACT;;;;;;;;AASA,SAAA,YAAY,UAAkB,WAAmB,IAAoB;CACnE,MAAM,OAAA,kBAAA,gBAAO,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;CACrC,MAAM,MAAM,MAAM,UAAU;CAC5B,IAAI,QAAQ,KAAK,MAAM,UAAU,SAAS,KAAK,iBAC7C,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;oCAEqB,UAAU,aAAa,MAAM,EAAE,iBAAiB,GAAG;4BAC3D,SAAS,eAAe,KAAK,MAAM;;MAGzD,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;;aAIF,SAAS,KAAK,MAAM,SAAS,MAAM,EAAE,IAAI,IAAI,IAAI,MAAM,EAAE,IAAI,UAAU,IAAI,GAAG,IAAI,GAAG;;CAG9F,OAAO;AACT;;;;;;AAOA,SAAA,YACE,UACA,SACA,OACkB;CAClB,kBAAA,gBAAA,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,QAAQ;CAC/B,MAAM,OAAyB,CAAC;CAChC,IAAI,QAAQ;CACZ,OAAO,KAAK,SAAS,OAAO;EAC1B,MAAM,OAAO,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;4BASA,SAAS,eAAe,MAAM,gBAAgB,QAAQ;;;QAG1E;EACF,IAAI,CAAC,MAAM;EACX,QAAQ,KAAK;EACb,MAAM,SAAS,KAAK,MAAM,IAAI,KAAK,KAAK,EAAE;EAC1C,KACE,IAAI,MAAM,KAAK,IAAI,SAAS,KAAK,QAAQ,GACzC,MAAM,KAAK,UAAU,KAAK,SAAS,OACnC,OAEA,KAAK,KAAK;GACR,WAAW;GACX;GACA,OAAO,KAAK,UAAU,OAAO,MAAM,KAAK,SAAS;GACjD,YAAY,KAAK;EACnB,CAAC;CAEL;CACA,OAAO;AACT;;;;;;AAUA,SAAA,YAAY,UAA0B;CACpC,IAAA,uBAAA,cAAI,IAAA,CAAA,CAAkB,SAAS,GAAG;EAChC,MAAM,MAAA,kBAAA,gBAAM,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;EACpC,IAAI,KAAK;GACP,MAAM,SAAA,kBAAA,gBAAS,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ,CAAC,CAAC;GACpC,KAAK,MAAM,QAAA,uBAAA,cAAQ,IAAA,GAAmB,KAAK,KAAK,MAAM;EACxD;CACF;CACA,IAAA,uBAAA,mBAAI,IAAA,GAAwB;EAE1B,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC,yDAAyD;EAClE,kBAAA,gBAAA,MAAA,4BAAA,CAAA,CAAA,KAAA,IAAkC;CACpC;CACA,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC,yDAAyD;CAClE,OAAA,kBAAA,gBAAO,MAAA,SAAA,CAAA,CAAA,KAAA,MAAe,qDAAqD,CACzE,QACF,CAAC;AACH;;;;;;AAOA,SAAA,QACE,UACA,OACA,QACA,SACS;CACT,IAAI,CAAC,SAAS,UAAU,CAAC,SAAS,SAAS;EACzC,MAAM,UAAA,kBAAA,gBAAU,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,UAAU,OAAO,MAAM;EACvD,IAAI,SAAS,kBAAA,gBAAA,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,UAAU,OAAO,MAAM;EAGtD,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ;EACnB,OAAO;CACT;CAMA,IAAI,UAAU;CACd,IAAI,UAAU;CACd,MAAM,YAAA,uBAAA,mBAAY,IAAA;CAClB,IAAI;EACF,KAAK,UAAU,QAAQ,sBAAsB;GAC3C,UAAA,kBAAA,gBAAU,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,UAAU,OAAO,MAAM;GACjD,IAAI,CAAC,SAAS;GACd,QAAQ,SAAS;GACjB,IAAI,QAAQ,SAAS,UAAA,kBAAA,gBAAU,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,QAAQ,IAAI;EAC9D,CAAC;CACH,UAAU;EAMR,IAAI,aAAa,CAAA,uBAAA,mBAAC,IAAA,GAChB,uBAAA,mBAAA,MAAA,kBAAA,gBAAyB,MAAA,oBAAA,CAAA,CAAA,KAAA,IAA0B,CAAA;CAEvD;CACA,IAAI,SAAS,kBAAA,gBAAA,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,UAAU,OAAO,MAAM;CACtD,IAAI,SAAS,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,kBAAkB,EAAE,SAAS,CAAC;CACtD,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ;CACnB,OAAO;AACT;;;;;;;;;AAUA,SAAA,WACE,UACA,OACA,QACS;CACT,MAAM,cAAA,kBAAA,gBAAc,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,QAAQ,CAAC,CAAC;CAQzC,OAAA,kBAAA,gBAPgB,MAAA,SAAA,CAAA,CAAA,KAAA,MACd;;;qDAIA;EAAC;EAAO;EAAQ,KAAK,IAAI;EAAG,KAAK,IAAI;EAAG;EAAa;CAAQ,CAElD,IAAI;AACnB;AAEA,SAAA,aACE,UACA,OACA,QACM;CACN,kBAAA,gBAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,UAAU,cAAc,kBAAkB,kBAAkB;EACrE;EACA,GAAI,WAAW,OAAO,EAAE,OAAO,IAAI,CAAC;CACtC,CAAC;AACH;AAIA,SAAA,MAAM,UAAwB;CAC5B,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc,IAAI,QAAQ;CAC1C,IAAI,CAAC,SAAS;CACd,uBAAA,UAAA,IAAA,CAAA,CAAc,OAAO,QAAQ;CAC7B,KAAK,MAAM,QAAQ,SAAS,KAAK;AACnC;AAEA,SAAA,eAAe,UAAkB,QAAqC;CACpE,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,UAAA,uBAAA,UAAU,IAAA,CAAA,CAAc,IAAI,QAAQ,qBAAK,IAAI,IAAI;EACvD,uBAAA,UAAA,IAAA,CAAA,CAAc,IAAI,UAAU,OAAO;EACnC,MAAM,aAAa;GACjB,QAAQ,oBAAoB,SAAS,OAAO;GAC5C,QAAQ;EACV;EACA,MAAM,gBAAgB;GACpB,QAAQ,OAAO,IAAI;GAGnB,IAAI,QAAQ,SAAS,KAAA,uBAAA,UAAK,IAAA,CAAA,CAAc,IAAI,QAAQ,MAAM,SACxD,uBAAA,UAAA,IAAA,CAAA,CAAc,OAAO,QAAQ;GAI/B,QAAQ;EACV;EACA,QAAQ,IAAI,IAAI;EAChB,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH;AAIA,SAAA,kBAAkB,UAAwB;CACxC,IAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GACtD,MAAM,IAAI,MAAM,sCAAsC;CAExD,IAAI,SAAS,SAAS,sBACpB,MAAM,IAAI,MAAM,qBAAqB,qBAAqB,YAAY;AAE1E;AAEA,SAAA,WAAW,OAAgB,SAAgC;CACzD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,UAAU,KAAK;CAC7B,SAAS,OAAO;EACd,MAAM,IAAI,yBACR,SACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACvD;CACF;CACA,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,yBACR,SACA,iBAAiB,OAAO,MAAM,4BAChC;CAEF,MAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC;CAChC,IAAI,QAAA,uBAAA,gBAAQ,IAAA,GACV,MAAM,IAAI,yBACR,SACA,mBAAmB,MAAM,qBAAA,uBAAA,gBAAqB,IAAA,EAAoB,YACpE;CAEF,OAAO;AACT;AAEA,SAAA,KACE,SACA,GAAG,QACE;CACL,MAAM,QAAQ,QAAQ,QACnB,QAAQ,MAAM,UACb,SAAS,QAAQ,QAAQ,OAAO,SAAS,MAAM,KACjD,EACF;CACA,IAAI;EAGF,OAAO,CAAC,GAAG,KAAK,UAAU,QAAQ,IAAI,KAAK,OAAO,GAAG,MAAM,CAAC;CAC9D,SAAS,OAAO;EACd,MAAM,IAAI,SAAS,OAAO,KAAK;CACjC;AACF;AAEA,SAAA,UAAU,OAAe,QAA4C;CACnE,IAAI;EACF,OAAO,KAAK,UAAU,QAAQ,IAAI,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC;CAC3D,SAAS,OAAO;EACd,MAAM,IAAI,SAAS,OAAO,KAAK;CACjC;AACF;AAEA,SAAA,WAAW,UAAyC;CAIlD,OAAO,kBAAA,gBAHM,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;0DACgC,SAAS;MAEnD;AACd;;;;;;AAOA,SAAA,OAAO,UAA2C;CAIhD,OAAO,kBAAA,gBAHM,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;8DACoC,SAAS;MAEvD,EAAE,EAAE;AAClB;;;;;;;;AASA,SAAA,MAAM,UAAmE;CACvE,MAAM,OAAA,kBAAA,gBAAO,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,QAAQ;CACrC,OAAO,OACH;EAAE,SAAS,KAAK;EAAQ,aAAa,KAAK;CAAW,IACrD;EAAE,SAAS;EAAG,aAAa;CAAK;AACtC;;AAGA,SAAA,WAAW,UAAkB;CAC3B,kBAAA,gBAAA,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,QAAQ;CAC/B,OAAO,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;0BAQM,SAAS;;;MAG7B;AACJ;;;;;;;;;;AAWA,SAAA,kBAAkB,UAAwB;CACxC,IAAI,CAAA,uBAAA,mBAAC,IAAA,GAAwB;CAC7B,KAAK,UAAU,QAAQ,sBAAsB;EAC3C,MAAM,OAAO,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;4BAEA,SAAS;;;QAG7B;EACF,IAAI,SAAS,MAAM,SAAS,MAAM;EAClC,IAAI,MAAM,MAAM,UAAU;EAC1B,IAAI,OAAO;EACX,IAAI,UAAU;EACd,IAAI,YAAY;EAChB,IAAI,YAAY;EAChB,MAAM,cAAc;GAClB,IAAI,SAAS,IAAI;GACjB,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;;eAIF,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,IAAI,IAAI,KAAK,IAAI,UAAU,IAAI,UAAU;;GAEnF,SAAS;GACT,OAAO;GACP,UAAU;EACZ;EACA,IAAI,QAAQ;EACZ,SAAS;GACP,MAAM,OAAO,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;8BAMA,SAAS,aAAa,MAAM;;kBAExC,sBAAsB;;GAEhC,KAAK,MAAM,OAAO,MAAM;IACtB,IACE,SAAS,MACT,KAAK,SAAS,IAAI,MAAM,SAAS,IAAI,iBAErC,MAAM;IAER,IAAI,SAAS,IAAI,YAAY,IAAI;IACjC,OAAO,SAAS,KAAK,IAAI,QAAQ,GAAG,KAAK,GAAG,IAAI;IAChD,YAAY,IAAI;IAChB,OAAO;IACP,QAAQ,IAAI;GACd;GACA,IAAI,KAAK,SAAS,uBAAuB;EAC3C;EACA,MAAM;EACN,IAAI,SAAS,GACX,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC,yDAAyD;EAEpE,kBAAA,gBAAA,MAAA,4BAAA,CAAA,CAAA,KAAA,IAAkC;CACpC,CAAC;AACH;AAEA,SAAA,uBAAgC;CAC9B,OACE,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;;QAGP,SAAS;AAEf;AAEA,SAAA,+BAAqC;CAInC,IAHkB,kBAAA,gBAAA,MAAA,IAAA,CAAA,CAAA,KAAA,IAAQ,CAAC;;MAEzB,EAAE,CAAC,IACW,GAAG;CACnB,kBAAA,gBAAA,MAAA,SAAA,CAAA,CAAA,KAAA,MAAe,sCAAsC,CAAC,CAAC;CACvD,uBAAA,mBAAA,MAAyB,KAAA;AAC3B;AAEA,SAAA,aAAa,KAA8B;CAIzC,IAAI,SAAS,IAAI;CACjB,IAAI,YAAY,IAAI;CACpB,IAAI,IAAI,UAAU,aAAa;EAC7B,MAAM,OAAA,kBAAA,gBAAO,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,IAAI,SAAS;EACrC,SAAS,KAAK;EACd,IAAI,KAAK,gBAAgB,QAAQ,KAAK,cAAc,WAClD,YAAY,KAAK;CAErB;CACA,OAAO;EACL,UAAU,IAAI;EACd,OAAO,IAAI;EACX;EACA,GAAI,IAAI,QAAQ,OAAO,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;EAC3C,GAAI,IAAI,aAAa,OACjB,EACE,UAAU,KAAK,MAAM,IAAI,QAAQ,EACnC,IACA,CAAC;EACL,GAAI,IAAI,kBAAkB,OAAO,EAAE,OAAO,IAAI,cAAc,IAAI,CAAC;EACjE,WAAW,IAAI;EACf;EACA,GAAI,IAAI,cAAc,OAAO,EAAE,UAAU,IAAI,UAAU,IAAI,CAAC;CAC9D;AACF;AAEA,SAAA,gBAAsB;CACpB,MAAM,UAAU,UAAkB;EAChC,IAAI;GACF,KAAK,UAAU,QAAQ,IAAI,KAAK,KAAK;EACvC,SAAS,OAAO;GACd,MAAM,IAAI,SAAS,OAAO,KAAK;EACjC;CACF;CAWA,OAAO;;;;;;;;;;;;;QAaH;CACJ,OAAO;;;KAGN;CACD,OAAO;;;;;;;;;;sBAUW;AACpB;AAEA,SAAA,MAAM,MAAc,SAAwC;CAC1D,KAAK,UAAU,OAAO,KAAK,MAAM,OAAO;AAC1C"}
|
package/dist/surface-bZZJqBka.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region src/channels/surface.ts
|
|
2
|
-
function isSurfaceValue(value) {
|
|
3
|
-
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
4
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
5
|
-
if (Array.isArray(value)) return value.every(isSurfaceValue);
|
|
6
|
-
return typeof value === "object" && Object.values(value).every(isSurfaceValue);
|
|
7
|
-
}
|
|
8
|
-
/** Validate the common envelope before provider-specific address parsing. */
|
|
9
|
-
function isChannelMessageSurface(value) {
|
|
10
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
11
|
-
const surface = value;
|
|
12
|
-
return typeof surface.channelKey === "string" && surface.channelKey.length > 0 && surface.version === 1 && isSurfaceValue(surface.address) && typeof surface.label === "string" && surface.label.trim().length > 0;
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
|
-
export { isChannelMessageSurface as t };
|
|
16
|
-
|
|
17
|
-
//# sourceMappingURL=surface-bZZJqBka.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"surface-bZZJqBka.js","names":[],"sources":["../src/channels/surface.ts"],"sourcesContent":["/**\n * A JSON value safe to persist across Worker and Durable Object boundaries.\n *\n * The recursive arms are interfaces rather than inline type literals so that\n * TypeScript defers resolving them. Workers RPC checks a return type with\n * `R extends Rpc.Serializable<R>`, which eagerly expands a self-referential\n * type alias and fails with \"Type instantiation is excessively deep\". Deferred\n * arms let a surface be returned from a Durable Object method.\n */\nexport type ChannelSurfaceValue =\n | null\n | boolean\n | number\n | string\n | ChannelSurfaceArray\n | ChannelSurfaceObject;\n\nexport interface ChannelSurfaceArray extends ReadonlyArray<ChannelSurfaceValue> {}\n\nexport interface ChannelSurfaceObject {\n readonly [key: string]: ChannelSurfaceValue;\n}\n\nfunction isSurfaceValue(value: unknown): value is ChannelSurfaceValue {\n if (\n value === null ||\n typeof value === \"boolean\" ||\n typeof value === \"string\"\n ) {\n return true;\n }\n if (typeof value === \"number\") return Number.isFinite(value);\n if (Array.isArray(value)) return value.every(isSurfaceValue);\n return (\n typeof value === \"object\" && Object.values(value).every(isSurfaceValue)\n );\n}\n\n/** A durable configured destination for one outbound message attempt. */\nexport type ChannelMessageSurface<\n TChannelKey extends string = string,\n TAddress extends ChannelSurfaceValue = ChannelSurfaceValue\n> = Readonly<{\n channelKey: TChannelKey;\n version: 1;\n address: TAddress;\n /** Human-readable destination text captured when the surface is created. */\n label: string;\n}>;\n\n/** A Channel-produced destination before its Host stamps the configured key. */\nexport type ChannelMessageSurfaceInput<\n TAddress extends ChannelSurfaceValue = ChannelSurfaceValue\n> = Readonly<{\n version: 1;\n address: TAddress;\n label: string;\n}>;\n\n/** Validate the common envelope before provider-specific address parsing. */\nexport function isChannelMessageSurface(\n value: unknown\n): value is ChannelMessageSurface {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return false;\n }\n const surface = value as Record<string, unknown>;\n return (\n typeof surface.channelKey === \"string\" &&\n surface.channelKey.length > 0 &&\n surface.version === 1 &&\n isSurfaceValue(surface.address) &&\n typeof surface.label === \"string\" &&\n surface.label.trim().length > 0\n );\n}\n"],"mappings":";AAuBA,SAAS,eAAe,OAA8C;CACpE,IACE,UAAU,QACV,OAAO,UAAU,aACjB,OAAO,UAAU,UAEjB,OAAO;CAET,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,SAAS,KAAK;CAC3D,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,cAAc;CAC3D,OACE,OAAO,UAAU,YAAY,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,cAAc;AAE1E;;AAwBA,SAAgB,wBACd,OACgC;CAChC,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GACpE,OAAO;CAET,MAAM,UAAU;CAChB,OACE,OAAO,QAAQ,eAAe,YAC9B,QAAQ,WAAW,SAAS,KAC5B,QAAQ,YAAY,KACpB,eAAe,QAAQ,OAAO,KAC9B,OAAO,QAAQ,UAAU,YACzB,QAAQ,MAAM,KAAK,CAAC,CAAC,SAAS;AAElC"}
|