@nhtio/adk 0.1.0-master-d3feb810 → 0.1.0-master-445a9ed0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/batteries/llm/openai_chat_completions/adapter.cjs +9 -10
  3. package/batteries/llm/openai_chat_completions/adapter.cjs.map +1 -1
  4. package/batteries/llm/openai_chat_completions/adapter.mjs +8 -8
  5. package/batteries/llm/openai_chat_completions/adapter.mjs.map +1 -1
  6. package/batteries/llm/openai_chat_completions/exceptions.cjs +1 -1
  7. package/batteries/llm/openai_chat_completions/helpers.cjs +16 -16
  8. package/batteries/llm/openai_chat_completions/helpers.cjs.map +1 -1
  9. package/batteries/llm/openai_chat_completions/helpers.d.ts +10 -10
  10. package/batteries/llm/openai_chat_completions/helpers.mjs +16 -16
  11. package/batteries/llm/openai_chat_completions/helpers.mjs.map +1 -1
  12. package/batteries/llm/openai_chat_completions/types.d.ts +26 -6
  13. package/batteries/llm/openai_chat_completions/validation.cjs +3 -1
  14. package/batteries/llm/openai_chat_completions/validation.cjs.map +1 -1
  15. package/batteries/llm/openai_chat_completions/validation.mjs +2 -0
  16. package/batteries/llm/openai_chat_completions/validation.mjs.map +1 -1
  17. package/batteries/llm/webllm_chat_completions/adapter.cjs +9 -10
  18. package/batteries/llm/webllm_chat_completions/adapter.cjs.map +1 -1
  19. package/batteries/llm/webllm_chat_completions/adapter.mjs +8 -8
  20. package/batteries/llm/webllm_chat_completions/adapter.mjs.map +1 -1
  21. package/batteries/llm/webllm_chat_completions/exceptions.cjs +1 -1
  22. package/batteries/llm/webllm_chat_completions/validation.cjs +3 -1
  23. package/batteries/llm/webllm_chat_completions/validation.cjs.map +1 -1
  24. package/batteries/llm/webllm_chat_completions/validation.mjs +2 -0
  25. package/batteries/llm/webllm_chat_completions/validation.mjs.map +1 -1
  26. package/batteries/storage/flydrive/index.d.ts +10 -4
  27. package/batteries/storage/flydrive.cjs +12 -3
  28. package/batteries/storage/flydrive.cjs.map +1 -1
  29. package/batteries/storage/flydrive.mjs +11 -2
  30. package/batteries/storage/flydrive.mjs.map +1 -1
  31. package/batteries/storage/in_memory/index.d.ts +31 -17
  32. package/batteries/storage/in_memory.cjs +89 -30
  33. package/batteries/storage/in_memory.cjs.map +1 -1
  34. package/batteries/storage/in_memory.mjs +89 -30
  35. package/batteries/storage/in_memory.mjs.map +1 -1
  36. package/batteries/storage/opfs/index.d.ts +10 -4
  37. package/batteries/storage/opfs.cjs +55 -5
  38. package/batteries/storage/opfs.cjs.map +1 -1
  39. package/batteries/storage/opfs.mjs +54 -4
  40. package/batteries/storage/opfs.mjs.map +1 -1
  41. package/batteries/tools/color.cjs +3 -3
  42. package/batteries/tools/color.mjs +2 -2
  43. package/batteries/tools/comparison.cjs +3 -4
  44. package/batteries/tools/comparison.cjs.map +1 -1
  45. package/batteries/tools/comparison.mjs +2 -2
  46. package/batteries/tools/data_structure.cjs +3 -4
  47. package/batteries/tools/data_structure.cjs.map +1 -1
  48. package/batteries/tools/data_structure.mjs +2 -2
  49. package/batteries/tools/datetime_extended.cjs +4 -4
  50. package/batteries/tools/datetime_extended.mjs +2 -2
  51. package/batteries/tools/datetime_math.cjs +3 -3
  52. package/batteries/tools/datetime_math.mjs +2 -2
  53. package/batteries/tools/encoding.cjs +3 -4
  54. package/batteries/tools/encoding.cjs.map +1 -1
  55. package/batteries/tools/encoding.mjs +2 -2
  56. package/batteries/tools/formatting.cjs +3 -4
  57. package/batteries/tools/formatting.cjs.map +1 -1
  58. package/batteries/tools/formatting.mjs +2 -2
  59. package/batteries/tools/geo_basics.cjs +3 -3
  60. package/batteries/tools/geo_basics.mjs +2 -2
  61. package/batteries/tools/math.cjs +3 -4
  62. package/batteries/tools/math.cjs.map +1 -1
  63. package/batteries/tools/math.mjs +2 -2
  64. package/batteries/tools/memory.cjs +6 -7
  65. package/batteries/tools/memory.cjs.map +1 -1
  66. package/batteries/tools/memory.mjs +5 -5
  67. package/batteries/tools/parsing.cjs +5 -6
  68. package/batteries/tools/parsing.cjs.map +1 -1
  69. package/batteries/tools/parsing.mjs +3 -3
  70. package/batteries/tools/retrievables.cjs +11 -11
  71. package/batteries/tools/retrievables.cjs.map +1 -1
  72. package/batteries/tools/retrievables.mjs +10 -9
  73. package/batteries/tools/retrievables.mjs.map +1 -1
  74. package/batteries/tools/standing_instructions.cjs +4 -5
  75. package/batteries/tools/standing_instructions.cjs.map +1 -1
  76. package/batteries/tools/standing_instructions.mjs +3 -3
  77. package/batteries/tools/statistics.cjs +4 -5
  78. package/batteries/tools/statistics.cjs.map +1 -1
  79. package/batteries/tools/statistics.mjs +3 -3
  80. package/batteries/tools/string_processing.cjs +3 -4
  81. package/batteries/tools/string_processing.cjs.map +1 -1
  82. package/batteries/tools/string_processing.mjs +2 -2
  83. package/batteries/tools/structured_data.cjs +3 -4
  84. package/batteries/tools/structured_data.cjs.map +1 -1
  85. package/batteries/tools/structured_data.mjs +2 -2
  86. package/batteries/tools/text_analysis.cjs +4 -4
  87. package/batteries/tools/text_analysis.mjs +3 -3
  88. package/batteries/tools/text_comparison.cjs +3 -3
  89. package/batteries/tools/text_comparison.mjs +2 -2
  90. package/batteries/tools/time.cjs +3 -3
  91. package/batteries/tools/time.mjs +2 -2
  92. package/batteries/tools/unit_conversion.cjs +3 -3
  93. package/batteries/tools/unit_conversion.mjs +2 -2
  94. package/batteries/tools.cjs +3 -3
  95. package/batteries/tools.mjs +3 -3
  96. package/batteries.cjs +3 -3
  97. package/batteries.mjs +3 -3
  98. package/{common-Od8edUXU.js → common-BJ6V6dsH.js} +89 -12
  99. package/common-BJ6V6dsH.js.map +1 -0
  100. package/{common-DeZaonK1.mjs → common-aFmr9Oqs.mjs} +76 -10
  101. package/common-aFmr9Oqs.mjs.map +1 -0
  102. package/common.cjs +9 -7
  103. package/common.d.ts +8 -0
  104. package/common.mjs +7 -7
  105. package/{dispatch_runner-CsoH0nld.js → dispatch_runner-BWYNxmnp.js} +37 -6
  106. package/dispatch_runner-BWYNxmnp.js.map +1 -0
  107. package/{dispatch_runner-9j6bXHL3.mjs → dispatch_runner-OimGCkk7.mjs} +34 -2
  108. package/dispatch_runner-OimGCkk7.mjs.map +1 -0
  109. package/dispatch_runner.cjs +1 -1
  110. package/dispatch_runner.mjs +1 -1
  111. package/{exceptions-D5YrO9Vm.js → exceptions-CSqzbL1N.js} +2 -2
  112. package/{exceptions-D5YrO9Vm.js.map → exceptions-CSqzbL1N.js.map} +1 -1
  113. package/exceptions.cjs +2 -2
  114. package/factories.cjs +1 -1
  115. package/forge.cjs +4 -4
  116. package/forge.mjs +3 -3
  117. package/guards.cjs +9 -9
  118. package/guards.mjs +7 -7
  119. package/index.cjs +12 -12
  120. package/index.cjs.map +1 -1
  121. package/index.d.ts +1 -1
  122. package/index.mjs +9 -9
  123. package/index.mjs.map +1 -1
  124. package/lib/classes/retrievable.d.ts +47 -4
  125. package/lib/contracts/byte_store.d.ts +93 -0
  126. package/lib/contracts/dispatch_context.d.ts +44 -0
  127. package/lib/contracts/turn_runner_config.d.ts +5 -1
  128. package/lib/contracts/turn_runner_context.d.ts +25 -0
  129. package/package.json +67 -67
  130. package/{runtime-BJVkrGQe.js → runtime-BUDWyd-R.js} +2 -2
  131. package/{runtime-BJVkrGQe.js.map → runtime-BUDWyd-R.js.map} +1 -1
  132. package/skills/adk-assembly/SKILL.md +2 -2
  133. package/{spooled_artifact-C5ZtGxuJ.mjs → spooled_artifact-B_tVDDdB.mjs} +2 -2
  134. package/{spooled_artifact-C5ZtGxuJ.mjs.map → spooled_artifact-B_tVDDdB.mjs.map} +1 -1
  135. package/{spooled_artifact-Cm9Te22K.js → spooled_artifact-CFstzlqX.js} +5 -6
  136. package/{spooled_artifact-Cm9Te22K.js.map → spooled_artifact-CFstzlqX.js.map} +1 -1
  137. package/spooled_artifact.cjs +2 -2
  138. package/spooled_artifact.mjs +2 -2
  139. package/{spooled_markdown_artifact-RRB113sy.js → spooled_markdown_artifact-DK-T8Hy6.js} +6 -7
  140. package/{spooled_markdown_artifact-RRB113sy.js.map → spooled_markdown_artifact-DK-T8Hy6.js.map} +1 -1
  141. package/{spooled_markdown_artifact-BpUJol0W.mjs → spooled_markdown_artifact-DWWak35I.mjs} +2 -2
  142. package/{spooled_markdown_artifact-BpUJol0W.mjs.map → spooled_markdown_artifact-DWWak35I.mjs.map} +1 -1
  143. package/{thought-CDb457b4.mjs → thought-DDqjQu3m.mjs} +2 -2
  144. package/{thought-CDb457b4.mjs.map → thought-DDqjQu3m.mjs.map} +1 -1
  145. package/{thought-DuN2PgdO.js → thought-DTsFRGdE.js} +5 -6
  146. package/{thought-DuN2PgdO.js.map → thought-DTsFRGdE.js.map} +1 -1
  147. package/{tool-COSeH8I6.js → tool-cwJyEHI9.js} +4 -5
  148. package/{tool-COSeH8I6.js.map → tool-cwJyEHI9.js.map} +1 -1
  149. package/{tool-D2WB1EA1.mjs → tool-q4LskG7K.mjs} +1 -1
  150. package/{tool-D2WB1EA1.mjs.map → tool-q4LskG7K.mjs.map} +1 -1
  151. package/{tool_call-DFgzcVcU.js → tool_call-3T0xTXlD.js} +5 -6
  152. package/{tool_call-DFgzcVcU.js.map → tool_call-3T0xTXlD.js.map} +1 -1
  153. package/{tool_call-BKyyxGaZ.mjs → tool_call-BKIdAAoY.mjs} +2 -2
  154. package/{tool_call-BKyyxGaZ.mjs.map → tool_call-BKIdAAoY.mjs.map} +1 -1
  155. package/{tool_registry-Dkfprsck.js → tool_registry-snPjF0zJ.js} +39 -5
  156. package/{tool_registry-Dkfprsck.js.map → tool_registry-snPjF0zJ.js.map} +1 -1
  157. package/{turn_runner-CMm2BHdX.js → turn_runner-BScT8OgA.js} +10 -7
  158. package/turn_runner-BScT8OgA.js.map +1 -0
  159. package/{turn_runner-y7eyEcJH.mjs → turn_runner-DRBLN2Y_.mjs} +7 -3
  160. package/turn_runner-DRBLN2Y_.mjs.map +1 -0
  161. package/turn_runner.cjs +1 -1
  162. package/turn_runner.mjs +1 -1
  163. package/types.d.ts +2 -2
  164. package/chunk-KmRHZBOW.js +0 -35
  165. package/common-DeZaonK1.mjs.map +0 -1
  166. package/common-Od8edUXU.js.map +0 -1
  167. package/dispatch_runner-9j6bXHL3.mjs.map +0 -1
  168. package/dispatch_runner-CsoH0nld.js.map +0 -1
  169. package/turn_runner-CMm2BHdX.js.map +0 -1
  170. package/turn_runner-y7eyEcJH.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"flydrive.cjs","names":["#disk","#key","#threshold","#load","#readRange","#ready","#init","#buildStreamingIndex","#prefix","#defaultThreshold"],"sources":["../../../__vite-browser-external","../../../src/batteries/storage/flydrive/index.ts"],"sourcesContent":["module.exports = {}","/**\n * Flydrive-backed spooled artifact storage for Node and server runtimes.\n *\n * @module @nhtio/adk/batteries/storage/flydrive\n *\n * @remarks\n * **Requires Node 24+.** `flydrive` uses the `node:stream` `ReadableStream` web API which is\n * only available from Node 24. This battery does not work in the browser or earlier Node versions.\n *\n * Opt-in storage battery backed by [flydrive](https://flydrive.dev). Provides\n * {@link FlydriveSpoolReader} (a {@link @nhtio/adk!SpoolReader} over a flydrive key) and\n * {@link FlydriveSpoolStore} (a `write(callId, bytes) → reader` persistence layer that wraps an\n * existing `Disk`).\n *\n * The reader has two modes selected at construction time based on the size of the underlying\n * object:\n *\n * - **Eager mode** — when the object's `contentLength` is below `streamThresholdBytes` (default\n * 10 MiB), the reader calls `disk.get(key)` once, splits the content on `\\n`, and caches\n * lines + byte count. All subsequent `line() / byteLength() / lineCount()` calls resolve from\n * memory.\n * - **Streaming mode** — when `contentLength` meets or exceeds the threshold, the reader\n * streams the file once via `disk.getStream(key)` to build a line-offset index (`number[]`\n * of byte offsets per line), then serves each `line(i)` request by streaming the byte range\n * `[offsets[i], offsets[i+1])`. Caps RAM at one index + one line buffer regardless of file\n * size.\n *\n * Set `streamThresholdBytes: 0` to force streaming mode; set it to `Infinity` to force eager\n * mode. The default of 10 MiB matches typical tool output sizes — tune it for your workload.\n *\n * The store and reader are pure-flydrive: they don't know about S3, GCS, or filesystem\n * specifically — they delegate to whatever `Disk` you construct.\n */\n\nimport { Disk } from 'flydrive'\nimport { Readable } from 'node:stream'\nimport type { SpoolReader } from '@nhtio/adk/common'\n\nconst DEFAULT_STREAM_THRESHOLD_BYTES = 10 * 1024 * 1024 // 10 MiB\n\nconst LF = 0x0a // '\\n'\n\n/**\n * Constructor options for {@link FlydriveSpoolReader}.\n */\nexport interface FlydriveSpoolReaderOptions {\n /**\n * Byte-length threshold that switches between eager and streaming modes.\n *\n * @remarks\n * - Below the threshold → eager (whole-file in memory).\n * - At or above the threshold → streaming (line-offset index + per-line streaming reads).\n *\n * Set to `0` to force streaming mode; set to `Number.POSITIVE_INFINITY` to force eager mode.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\ninterface EagerState {\n mode: 'eager'\n lines: string[]\n bytes: number\n content: string\n}\n\ninterface StreamingState {\n mode: 'streaming'\n /**\n * Byte offsets where each line *starts*. Length equals lineCount + 1; the final entry equals\n * the total byte length. So `offsets[i + 1] - offsets[i]` is the byte length of line `i`\n * including any trailing `\\n`.\n */\n offsets: number[]\n bytes: number\n}\n\ntype ReaderState = EagerState | StreamingState\n\nconst isNonNegativeFiniteNumber = (n: unknown): n is number =>\n typeof n === 'number' && Number.isFinite(n) && n >= 0\n\n/**\n * Reads a flydrive-backed file as a {@link @nhtio/adk!SpoolReader}.\n *\n * @remarks\n * Constructor is **not** async — but the first method call awaits a private readiness promise\n * that fetches the object's metadata (and in eager mode, its contents). Subsequent calls reuse\n * the cached state. This keeps construction call sites synchronous while still doing real I/O\n * lazily.\n *\n * Implementations of {@link @nhtio/adk!SpoolReader.line}, {@link @nhtio/adk!SpoolReader.byteLength}, and\n * {@link @nhtio/adk!SpoolReader.lineCount} all return promises. The `SpoolReader` contract supports both\n * sync and async return; consumers of `SpooledArtifact` handle either.\n */\nexport class FlydriveSpoolReader implements SpoolReader {\n readonly #disk: Disk\n readonly #key: string\n readonly #threshold: number\n #ready: Promise<ReaderState> | undefined\n\n constructor(disk: Disk, key: string, opts: FlydriveSpoolReaderOptions = {}) {\n this.#disk = disk\n this.#key = key\n const raw = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n // Allow `Infinity` (forces eager) but reject anything non-finite-negative.\n if (typeof raw !== 'number' || Number.isNaN(raw) || raw < 0) {\n throw new TypeError(\n `FlydriveSpoolReader: streamThresholdBytes must be a non-negative number or Infinity, got ${String(raw)}`\n )\n }\n this.#threshold = raw\n }\n\n async line(index: number): Promise<string | undefined> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.lines[index]\n if (index < 0 || index >= state.offsets.length - 1) return undefined\n return this.#readRange(state.offsets[index], state.offsets[index + 1])\n }\n\n async byteLength(): Promise<number> {\n const state = await this.#load()\n return state.bytes\n }\n\n async lineCount(): Promise<number> {\n const state = await this.#load()\n return state.mode === 'eager' ? state.lines.length : state.offsets.length - 1\n }\n\n /**\n * Returns the full underlying content as a single decoded string, byte-faithful to the source.\n *\n * @remarks\n * In **eager mode** the content is already cached at construction-time load and this method is\n * effectively a property access. In **streaming mode** there is no cache: the file is\n * re-streamed and concatenated on every call. Use {@link @nhtio/adk!SpooledArtifact.asString} judiciously\n * on large streaming-mode artifacts.\n */\n async readAll(): Promise<string> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.content\n const stream = await this.#disk.getStream(this.#key)\n const chunks: Uint8Array[] = []\n let total = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n chunks.push(view)\n total += view.length\n }\n const concat = new Uint8Array(total)\n let offset = 0\n for (const view of chunks) {\n concat.set(view, offset)\n offset += view.length\n }\n return new TextDecoder().decode(concat)\n }\n\n /**\n * Lazily initialise the reader's mode-specific state. Called by every public method; the\n * promise is cached so the work runs at most once.\n */\n #load(): Promise<ReaderState> {\n if (!this.#ready) this.#ready = this.#init()\n return this.#ready\n }\n\n async #init(): Promise<ReaderState> {\n const meta = await this.#disk.getMetaData(this.#key)\n const bytes = meta.contentLength\n if (!isNonNegativeFiniteNumber(bytes)) {\n // Defensive — flydrive's contract types this as `number`, but cloud drivers occasionally\n // return NaN/Infinity if the backing store omits the size header.\n throw new Error(\n `FlydriveSpoolReader: disk returned a non-finite contentLength (${String(bytes)}) for key \"${this.#key}\"`\n )\n }\n if (bytes < this.#threshold) {\n // Eager — pull the whole thing into memory.\n const content = await this.#disk.get(this.#key)\n const lines = content === '' ? [] : content.split('\\n')\n return { mode: 'eager', lines, bytes, content }\n }\n // Streaming — build a line-offset index by scanning bytes once.\n return this.#buildStreamingIndex(bytes)\n }\n\n async #buildStreamingIndex(bytes: number): Promise<StreamingState> {\n // Edge case first — an empty file is one offset (the EOF), zero lines.\n if (bytes === 0) return { mode: 'streaming', offsets: [0], bytes }\n\n const stream = await this.#disk.getStream(this.#key)\n // offsets[i] is the byte position where line `i` starts. offsets[lineCount] is one-past-end.\n // For \"a\\nb\\nc\" → offsets=[0, 2, 4, 5] (3 lines).\n // For \"a\\nb\\n\" → offsets=[0, 2, 4, 4] (3 lines, last is the trailing empty line). This\n // mirrors `String.prototype.split('\\n')` semantics so streaming and eager agree.\n const offsets: number[] = [0]\n let position = 0\n let lastByte = -1\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n for (const byte of view) {\n position++\n if (byte === LF) offsets.push(position)\n lastByte = byte\n }\n }\n // If the file ends on a newline, the byte after the LF is the start of an empty trailing\n // line — record it. If it doesn't, the final line's end is the EOF and we need to push\n // it so line(N-1) can read up to bytes.\n if (lastByte === LF) offsets.push(position)\n else if (offsets[offsets.length - 1] !== position) offsets.push(position)\n return { mode: 'streaming', offsets, bytes }\n }\n\n /**\n * Streams the byte range `[start, end)` from the backing disk and returns it as a UTF-8\n * string, stripping a trailing `\\n` if present.\n *\n * @remarks\n * flydrive doesn't expose native byte-range reads, so we open a fresh stream and skip until\n * we reach the requested start offset, then collect until we reach `end`. This is O(end)\n * per call — fine for occasional reads but worth profiling if a workload performs many\n * sequential `line()` calls on a large file.\n */\n async #readRange(start: number, end: number): Promise<string> {\n if (start === end) return ''\n const stream = await this.#disk.getStream(this.#key)\n const out: number[] = []\n let position = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n // Past the range entirely → stop early\n if (position >= end) {\n // Destroy the stream if it's a Node Readable; otherwise the for-await will naturally\n // continue, costing extra reads we don't need.\n // eslint-disable-next-line adk/use-is-instance-of -- native Node built-in; flydrive returns a real Readable, no cross-realm risk\n if (stream instanceof Readable) stream.destroy()\n break\n }\n // Fully before the range → skip the whole chunk\n if (position + view.length <= start) {\n position += view.length\n continue\n }\n // Partial overlap — copy the bytes that fall inside [start, end)\n const localStart = Math.max(0, start - position)\n const localEnd = Math.min(view.length, end - position)\n for (let i = localStart; i < localEnd; i++) out.push(view[i])\n position += view.length\n }\n // Strip the trailing newline if the range ended on one. The line-offset index ends each\n // line *after* its terminating LF (so offsets[i+1] points to the start of the next line),\n // and the SpoolReader contract returns lines *without* their trailing newline.\n if (out.length > 0 && out[out.length - 1] === LF) out.pop()\n return new TextDecoder().decode(new Uint8Array(out))\n }\n}\n\n/**\n * Constructor options for {@link FlydriveSpoolStore}.\n */\nexport interface FlydriveSpoolStoreOptions {\n /**\n * Optional key prefix prepended to every `callId`. Useful for namespacing tool-call artifacts\n * inside a shared bucket (e.g. `\"tool-calls/\"`).\n *\n * @defaultValue `\"\"`\n */\n keyPrefix?: string\n\n /**\n * Default `streamThresholdBytes` for readers produced by `write()` and `read()`. Individual\n * calls may override via their own `opts` argument.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\n/**\n * \"Give bytes, get a reader\" persistence layer over a flydrive {@link Disk}.\n *\n * @remarks\n * `write(callId, bytes)` calls `disk.put(key, bytes)` where `key = keyPrefix + callId`, then\n * returns a fresh {@link FlydriveSpoolReader} pointed at the same key. `read(callId)` returns\n * a reader without re-writing; `delete(callId)` calls `disk.delete(key)`.\n *\n * The store is stateless — it owns no in-memory cache of writes. Multiple `FlydriveSpoolStore`\n * instances sharing the same disk + key prefix see the same data.\n *\n * @example\n * ```ts\n * import { Disk } from 'flydrive'\n * import { FSDriver } from 'flydrive/drivers/fs'\n * import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'\n *\n * const disk = new Disk(new FSDriver({ location: './tmp', visibility: 'public' }))\n * const store = new FlydriveSpoolStore(disk)\n *\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class FlydriveSpoolStore {\n readonly #disk: Disk\n readonly #prefix: string\n readonly #defaultThreshold: number\n\n constructor(disk: Disk, opts: FlydriveSpoolStoreOptions = {}) {\n this.#disk = disk\n this.#prefix = opts.keyPrefix ?? ''\n this.#defaultThreshold = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n }\n\n /**\n * Persists `bytes` under `callId` and returns a reader bound to the stored key.\n *\n * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}.\n * @param bytes - The bytes to store, as a `string` or `Uint8Array`.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader} over the stored bytes.\n */\n async write(\n callId: string,\n bytes: string | Uint8Array,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader> {\n const key = this.#prefix + callId\n await this.#disk.put(key, bytes)\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`.\n *\n * @remarks\n * Returns `undefined` if the underlying key does not exist. Existence is checked via\n * `disk.exists(key)` before the reader is returned, so callers can rely on a defined return\n * value pointing at a real object.\n *\n * @param callId - Identifier supplied to a prior {@link FlydriveSpoolStore.write} call.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader}, or `undefined` if the key is missing.\n */\n async read(\n callId: string,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader | undefined> {\n const key = this.#prefix + callId\n if (!(await this.#disk.exists(key))) return undefined\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if the key existed and was removed; `false` if it didn't exist.\n */\n async delete(callId: string): Promise<boolean> {\n const key = this.#prefix + callId\n const existed = await this.#disk.exists(key)\n if (!existed) return false\n await this.#disk.delete(key)\n return true\n }\n\n /**\n * Returns the full disk key for a given `callId` (i.e. `keyPrefix + callId`).\n *\n * @remarks\n * Useful for tests or for callers that want to interact with the underlying disk directly.\n */\n keyFor(callId: string): string {\n return this.#prefix + callId\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"flydrive.cjs","names":["#disk","#key","#threshold","#load","#readRange","#ready","#init","#buildStreamingIndex","#prefix","#defaultThreshold"],"sources":["../../../__vite-browser-external","../../../src/batteries/storage/flydrive/index.ts"],"sourcesContent":["module.exports = {}","/**\n * Flydrive-backed spooled artifact storage for Node and server runtimes.\n *\n * @module @nhtio/adk/batteries/storage/flydrive\n *\n * @remarks\n * **Requires Node 24+.** `flydrive` uses the `node:stream` `ReadableStream` web API which is\n * only available from Node 24. This battery does not work in the browser or earlier Node versions.\n *\n * Opt-in storage battery backed by [flydrive](https://flydrive.dev). Provides\n * {@link FlydriveSpoolReader} (a {@link @nhtio/adk!SpoolReader} over a flydrive key) and\n * {@link FlydriveSpoolStore} (a `write(callId, bytes) → reader` persistence layer that wraps an\n * existing `Disk`).\n *\n * The reader has two modes selected at construction time based on the size of the underlying\n * object:\n *\n * - **Eager mode** — when the object's `contentLength` is below `streamThresholdBytes` (default\n * 10 MiB), the reader calls `disk.get(key)` once, splits the content on `\\n`, and caches\n * lines + byte count. All subsequent `line() / byteLength() / lineCount()` calls resolve from\n * memory.\n * - **Streaming mode** — when `contentLength` meets or exceeds the threshold, the reader\n * streams the file once via `disk.getStream(key)` to build a line-offset index (`number[]`\n * of byte offsets per line), then serves each `line(i)` request by streaming the byte range\n * `[offsets[i], offsets[i+1])`. Caps RAM at one index + one line buffer regardless of file\n * size.\n *\n * Set `streamThresholdBytes: 0` to force streaming mode; set it to `Infinity` to force eager\n * mode. The default of 10 MiB matches typical tool output sizes — tune it for your workload.\n *\n * The store and reader are pure-flydrive: they don't know about S3, GCS, or filesystem\n * specifically — they delegate to whatever `Disk` you construct.\n */\n\nimport { Disk } from 'flydrive'\nimport { Readable } from 'node:stream'\nimport { isInstanceOf } from '@nhtio/adk/guards'\nimport type { SpoolReader, SpoolStore } from '@nhtio/adk/common'\n\nconst DEFAULT_STREAM_THRESHOLD_BYTES = 10 * 1024 * 1024 // 10 MiB\n\nconst LF = 0x0a // '\\n'\n\n/**\n * Constructor options for {@link FlydriveSpoolReader}.\n */\nexport interface FlydriveSpoolReaderOptions {\n /**\n * Byte-length threshold that switches between eager and streaming modes.\n *\n * @remarks\n * - Below the threshold → eager (whole-file in memory).\n * - At or above the threshold → streaming (line-offset index + per-line streaming reads).\n *\n * Set to `0` to force streaming mode; set to `Number.POSITIVE_INFINITY` to force eager mode.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\ninterface EagerState {\n mode: 'eager'\n lines: string[]\n bytes: number\n content: string\n}\n\ninterface StreamingState {\n mode: 'streaming'\n /**\n * Byte offsets where each line *starts*. Length equals lineCount + 1; the final entry equals\n * the total byte length. So `offsets[i + 1] - offsets[i]` is the byte length of line `i`\n * including any trailing `\\n`.\n */\n offsets: number[]\n bytes: number\n}\n\ntype ReaderState = EagerState | StreamingState\n\nconst isNonNegativeFiniteNumber = (n: unknown): n is number =>\n typeof n === 'number' && Number.isFinite(n) && n >= 0\n\n/**\n * Reads a flydrive-backed file as a {@link @nhtio/adk!SpoolReader}.\n *\n * @remarks\n * Constructor is **not** async — but the first method call awaits a private readiness promise\n * that fetches the object's metadata (and in eager mode, its contents). Subsequent calls reuse\n * the cached state. This keeps construction call sites synchronous while still doing real I/O\n * lazily.\n *\n * Implementations of {@link @nhtio/adk!SpoolReader.line}, {@link @nhtio/adk!SpoolReader.byteLength}, and\n * {@link @nhtio/adk!SpoolReader.lineCount} all return promises. The `SpoolReader` contract supports both\n * sync and async return; consumers of `SpooledArtifact` handle either.\n */\nexport class FlydriveSpoolReader implements SpoolReader {\n readonly #disk: Disk\n readonly #key: string\n readonly #threshold: number\n #ready: Promise<ReaderState> | undefined\n\n constructor(disk: Disk, key: string, opts: FlydriveSpoolReaderOptions = {}) {\n this.#disk = disk\n this.#key = key\n const raw = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n // Allow `Infinity` (forces eager) but reject anything non-finite-negative.\n if (typeof raw !== 'number' || Number.isNaN(raw) || raw < 0) {\n throw new TypeError(\n `FlydriveSpoolReader: streamThresholdBytes must be a non-negative number or Infinity, got ${String(raw)}`\n )\n }\n this.#threshold = raw\n }\n\n async line(index: number): Promise<string | undefined> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.lines[index]\n if (index < 0 || index >= state.offsets.length - 1) return undefined\n return this.#readRange(state.offsets[index], state.offsets[index + 1])\n }\n\n async byteLength(): Promise<number> {\n const state = await this.#load()\n return state.bytes\n }\n\n async lineCount(): Promise<number> {\n const state = await this.#load()\n return state.mode === 'eager' ? state.lines.length : state.offsets.length - 1\n }\n\n /**\n * Returns the full underlying content as a single decoded string, byte-faithful to the source.\n *\n * @remarks\n * In **eager mode** the content is already cached at construction-time load and this method is\n * effectively a property access. In **streaming mode** there is no cache: the file is\n * re-streamed and concatenated on every call. Use {@link @nhtio/adk!SpooledArtifact.asString} judiciously\n * on large streaming-mode artifacts.\n */\n async readAll(): Promise<string> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.content\n const stream = await this.#disk.getStream(this.#key)\n const chunks: Uint8Array[] = []\n let total = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n chunks.push(view)\n total += view.length\n }\n const concat = new Uint8Array(total)\n let offset = 0\n for (const view of chunks) {\n concat.set(view, offset)\n offset += view.length\n }\n return new TextDecoder().decode(concat)\n }\n\n /**\n * Lazily initialise the reader's mode-specific state. Called by every public method; the\n * promise is cached so the work runs at most once.\n */\n #load(): Promise<ReaderState> {\n if (!this.#ready) this.#ready = this.#init()\n return this.#ready\n }\n\n async #init(): Promise<ReaderState> {\n const meta = await this.#disk.getMetaData(this.#key)\n const bytes = meta.contentLength\n if (!isNonNegativeFiniteNumber(bytes)) {\n // Defensive — flydrive's contract types this as `number`, but cloud drivers occasionally\n // return NaN/Infinity if the backing store omits the size header.\n throw new Error(\n `FlydriveSpoolReader: disk returned a non-finite contentLength (${String(bytes)}) for key \"${this.#key}\"`\n )\n }\n if (bytes < this.#threshold) {\n // Eager — pull the whole thing into memory.\n const content = await this.#disk.get(this.#key)\n const lines = content === '' ? [] : content.split('\\n')\n return { mode: 'eager', lines, bytes, content }\n }\n // Streaming — build a line-offset index by scanning bytes once.\n return this.#buildStreamingIndex(bytes)\n }\n\n async #buildStreamingIndex(bytes: number): Promise<StreamingState> {\n // Edge case first — an empty file is one offset (the EOF), zero lines.\n if (bytes === 0) return { mode: 'streaming', offsets: [0], bytes }\n\n const stream = await this.#disk.getStream(this.#key)\n // offsets[i] is the byte position where line `i` starts. offsets[lineCount] is one-past-end.\n // For \"a\\nb\\nc\" → offsets=[0, 2, 4, 5] (3 lines).\n // For \"a\\nb\\n\" → offsets=[0, 2, 4, 4] (3 lines, last is the trailing empty line). This\n // mirrors `String.prototype.split('\\n')` semantics so streaming and eager agree.\n const offsets: number[] = [0]\n let position = 0\n let lastByte = -1\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n for (const byte of view) {\n position++\n if (byte === LF) offsets.push(position)\n lastByte = byte\n }\n }\n // If the file ends on a newline, the byte after the LF is the start of an empty trailing\n // line — record it. If it doesn't, the final line's end is the EOF and we need to push\n // it so line(N-1) can read up to bytes.\n if (lastByte === LF) offsets.push(position)\n else if (offsets[offsets.length - 1] !== position) offsets.push(position)\n return { mode: 'streaming', offsets, bytes }\n }\n\n /**\n * Streams the byte range `[start, end)` from the backing disk and returns it as a UTF-8\n * string, stripping a trailing `\\n` if present.\n *\n * @remarks\n * flydrive doesn't expose native byte-range reads, so we open a fresh stream and skip until\n * we reach the requested start offset, then collect until we reach `end`. This is O(end)\n * per call — fine for occasional reads but worth profiling if a workload performs many\n * sequential `line()` calls on a large file.\n */\n async #readRange(start: number, end: number): Promise<string> {\n if (start === end) return ''\n const stream = await this.#disk.getStream(this.#key)\n const out: number[] = []\n let position = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n // Past the range entirely → stop early\n if (position >= end) {\n // Destroy the stream if it's a Node Readable; otherwise the for-await will naturally\n // continue, costing extra reads we don't need.\n // eslint-disable-next-line adk/use-is-instance-of -- native Node built-in; flydrive returns a real Readable, no cross-realm risk\n if (stream instanceof Readable) stream.destroy()\n break\n }\n // Fully before the range → skip the whole chunk\n if (position + view.length <= start) {\n position += view.length\n continue\n }\n // Partial overlap — copy the bytes that fall inside [start, end)\n const localStart = Math.max(0, start - position)\n const localEnd = Math.min(view.length, end - position)\n for (let i = localStart; i < localEnd; i++) out.push(view[i])\n position += view.length\n }\n // Strip the trailing newline if the range ended on one. The line-offset index ends each\n // line *after* its terminating LF (so offsets[i+1] points to the start of the next line),\n // and the SpoolReader contract returns lines *without* their trailing newline.\n if (out.length > 0 && out[out.length - 1] === LF) out.pop()\n return new TextDecoder().decode(new Uint8Array(out))\n }\n}\n\n/**\n * Constructor options for {@link FlydriveSpoolStore}.\n */\nexport interface FlydriveSpoolStoreOptions {\n /**\n * Optional key prefix prepended to every `callId`. Useful for namespacing tool-call artifacts\n * inside a shared bucket (e.g. `\"tool-calls/\"`).\n *\n * @defaultValue `\"\"`\n */\n keyPrefix?: string\n\n /**\n * Default `streamThresholdBytes` for readers produced by `write()` and `read()`. Individual\n * calls may override via their own `opts` argument.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\n/**\n * \"Give bytes, get a reader\" persistence layer over a flydrive {@link Disk}.\n *\n * @remarks\n * `write(callId, bytes)` calls `disk.put(key, bytes)` where `key = keyPrefix + callId`, then\n * returns a fresh {@link FlydriveSpoolReader} pointed at the same key. `read(callId)` returns\n * a reader without re-writing; `delete(callId)` calls `disk.delete(key)`.\n *\n * The store is stateless — it owns no in-memory cache of writes. Multiple `FlydriveSpoolStore`\n * instances sharing the same disk + key prefix see the same data.\n *\n * @example\n * ```ts\n * import { Disk } from 'flydrive'\n * import { FSDriver } from 'flydrive/drivers/fs'\n * import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'\n *\n * const disk = new Disk(new FSDriver({ location: './tmp', visibility: 'public' }))\n * const store = new FlydriveSpoolStore(disk)\n *\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class FlydriveSpoolStore implements SpoolStore {\n readonly #disk: Disk\n readonly #prefix: string\n readonly #defaultThreshold: number\n\n constructor(disk: Disk, opts: FlydriveSpoolStoreOptions = {}) {\n this.#disk = disk\n this.#prefix = opts.keyPrefix ?? ''\n this.#defaultThreshold = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n }\n\n /**\n * Persists `bytes` under `callId` and returns a reader bound to the stored key.\n *\n * @remarks\n * `string`/`Uint8Array` input goes through `disk.put`; `ReadableStream<Uint8Array>` is forwarded\n * to `disk.putStream` (via `Readable.fromWeb`) so the payload streams straight to the backing\n * driver — to disk for `FSDriver`, to the object store for S3/GCS — without being materialized\n * in memory first.\n *\n * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}.\n * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader} over the stored bytes.\n */\n async write(\n callId: string,\n bytes: string | Uint8Array | ReadableStream<Uint8Array>,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader> {\n const key = this.#prefix + callId\n if (isInstanceOf(bytes, 'ReadableStream', ReadableStream)) {\n await this.#disk.putStream(\n key,\n Readable.fromWeb(bytes as Parameters<typeof Readable.fromWeb>[0])\n )\n } else {\n await this.#disk.put(key, bytes)\n }\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`.\n *\n * @remarks\n * Returns `undefined` if the underlying key does not exist. Existence is checked via\n * `disk.exists(key)` before the reader is returned, so callers can rely on a defined return\n * value pointing at a real object.\n *\n * @param callId - Identifier supplied to a prior {@link FlydriveSpoolStore.write} call.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader}, or `undefined` if the key is missing.\n */\n async read(\n callId: string,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader | undefined> {\n const key = this.#prefix + callId\n if (!(await this.#disk.exists(key))) return undefined\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if the key existed and was removed; `false` if it didn't exist.\n */\n async delete(callId: string): Promise<boolean> {\n const key = this.#prefix + callId\n const existed = await this.#disk.exists(key)\n if (!existed) return false\n await this.#disk.delete(key)\n return true\n }\n\n /**\n * Returns the full disk key for a given `callId` (i.e. `keyPrefix + callId`).\n *\n * @remarks\n * Useful for tests or for callers that want to interact with the underlying disk directly.\n */\n keyFor(callId: string): string {\n return this.#prefix + callId\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { s as isInstanceOf } from "../../tool_registry-DqLOyGyG.mjs";
2
+ import "../../guards.mjs";
1
3
  //#region \0rolldown/runtime.js
2
4
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
3
5
  //#endregion
@@ -194,14 +196,21 @@ var FlydriveSpoolStore = class {
194
196
  /**
195
197
  * Persists `bytes` under `callId` and returns a reader bound to the stored key.
196
198
  *
199
+ * @remarks
200
+ * `string`/`Uint8Array` input goes through `disk.put`; `ReadableStream<Uint8Array>` is forwarded
201
+ * to `disk.putStream` (via `Readable.fromWeb`) so the payload streams straight to the backing
202
+ * driver — to disk for `FSDriver`, to the object store for S3/GCS — without being materialized
203
+ * in memory first.
204
+ *
197
205
  * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}.
198
- * @param bytes - The bytes to store, as a `string` or `Uint8Array`.
206
+ * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.
199
207
  * @param opts - Per-call override for `streamThresholdBytes`.
200
208
  * @returns A {@link FlydriveSpoolReader} over the stored bytes.
201
209
  */
202
210
  async write(callId, bytes, opts) {
203
211
  const key = this.#prefix + callId;
204
- await this.#disk.put(key, bytes);
212
+ if (isInstanceOf(bytes, "ReadableStream", ReadableStream)) await this.#disk.putStream(key, import___vite_browser_external.Readable.fromWeb(bytes));
213
+ else await this.#disk.put(key, bytes);
205
214
  return new FlydriveSpoolReader(this.#disk, key, { streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold });
206
215
  }
207
216
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"flydrive.mjs","names":["#disk","#key","#threshold","#load","#readRange","#ready","#init","#buildStreamingIndex","#prefix","#defaultThreshold"],"sources":["../../../__vite-browser-external","../../../src/batteries/storage/flydrive/index.ts"],"sourcesContent":["module.exports = {}","/**\n * Flydrive-backed spooled artifact storage for Node and server runtimes.\n *\n * @module @nhtio/adk/batteries/storage/flydrive\n *\n * @remarks\n * **Requires Node 24+.** `flydrive` uses the `node:stream` `ReadableStream` web API which is\n * only available from Node 24. This battery does not work in the browser or earlier Node versions.\n *\n * Opt-in storage battery backed by [flydrive](https://flydrive.dev). Provides\n * {@link FlydriveSpoolReader} (a {@link @nhtio/adk!SpoolReader} over a flydrive key) and\n * {@link FlydriveSpoolStore} (a `write(callId, bytes) → reader` persistence layer that wraps an\n * existing `Disk`).\n *\n * The reader has two modes selected at construction time based on the size of the underlying\n * object:\n *\n * - **Eager mode** — when the object's `contentLength` is below `streamThresholdBytes` (default\n * 10 MiB), the reader calls `disk.get(key)` once, splits the content on `\\n`, and caches\n * lines + byte count. All subsequent `line() / byteLength() / lineCount()` calls resolve from\n * memory.\n * - **Streaming mode** — when `contentLength` meets or exceeds the threshold, the reader\n * streams the file once via `disk.getStream(key)` to build a line-offset index (`number[]`\n * of byte offsets per line), then serves each `line(i)` request by streaming the byte range\n * `[offsets[i], offsets[i+1])`. Caps RAM at one index + one line buffer regardless of file\n * size.\n *\n * Set `streamThresholdBytes: 0` to force streaming mode; set it to `Infinity` to force eager\n * mode. The default of 10 MiB matches typical tool output sizes — tune it for your workload.\n *\n * The store and reader are pure-flydrive: they don't know about S3, GCS, or filesystem\n * specifically — they delegate to whatever `Disk` you construct.\n */\n\nimport { Disk } from 'flydrive'\nimport { Readable } from 'node:stream'\nimport type { SpoolReader } from '@nhtio/adk/common'\n\nconst DEFAULT_STREAM_THRESHOLD_BYTES = 10 * 1024 * 1024 // 10 MiB\n\nconst LF = 0x0a // '\\n'\n\n/**\n * Constructor options for {@link FlydriveSpoolReader}.\n */\nexport interface FlydriveSpoolReaderOptions {\n /**\n * Byte-length threshold that switches between eager and streaming modes.\n *\n * @remarks\n * - Below the threshold → eager (whole-file in memory).\n * - At or above the threshold → streaming (line-offset index + per-line streaming reads).\n *\n * Set to `0` to force streaming mode; set to `Number.POSITIVE_INFINITY` to force eager mode.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\ninterface EagerState {\n mode: 'eager'\n lines: string[]\n bytes: number\n content: string\n}\n\ninterface StreamingState {\n mode: 'streaming'\n /**\n * Byte offsets where each line *starts*. Length equals lineCount + 1; the final entry equals\n * the total byte length. So `offsets[i + 1] - offsets[i]` is the byte length of line `i`\n * including any trailing `\\n`.\n */\n offsets: number[]\n bytes: number\n}\n\ntype ReaderState = EagerState | StreamingState\n\nconst isNonNegativeFiniteNumber = (n: unknown): n is number =>\n typeof n === 'number' && Number.isFinite(n) && n >= 0\n\n/**\n * Reads a flydrive-backed file as a {@link @nhtio/adk!SpoolReader}.\n *\n * @remarks\n * Constructor is **not** async — but the first method call awaits a private readiness promise\n * that fetches the object's metadata (and in eager mode, its contents). Subsequent calls reuse\n * the cached state. This keeps construction call sites synchronous while still doing real I/O\n * lazily.\n *\n * Implementations of {@link @nhtio/adk!SpoolReader.line}, {@link @nhtio/adk!SpoolReader.byteLength}, and\n * {@link @nhtio/adk!SpoolReader.lineCount} all return promises. The `SpoolReader` contract supports both\n * sync and async return; consumers of `SpooledArtifact` handle either.\n */\nexport class FlydriveSpoolReader implements SpoolReader {\n readonly #disk: Disk\n readonly #key: string\n readonly #threshold: number\n #ready: Promise<ReaderState> | undefined\n\n constructor(disk: Disk, key: string, opts: FlydriveSpoolReaderOptions = {}) {\n this.#disk = disk\n this.#key = key\n const raw = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n // Allow `Infinity` (forces eager) but reject anything non-finite-negative.\n if (typeof raw !== 'number' || Number.isNaN(raw) || raw < 0) {\n throw new TypeError(\n `FlydriveSpoolReader: streamThresholdBytes must be a non-negative number or Infinity, got ${String(raw)}`\n )\n }\n this.#threshold = raw\n }\n\n async line(index: number): Promise<string | undefined> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.lines[index]\n if (index < 0 || index >= state.offsets.length - 1) return undefined\n return this.#readRange(state.offsets[index], state.offsets[index + 1])\n }\n\n async byteLength(): Promise<number> {\n const state = await this.#load()\n return state.bytes\n }\n\n async lineCount(): Promise<number> {\n const state = await this.#load()\n return state.mode === 'eager' ? state.lines.length : state.offsets.length - 1\n }\n\n /**\n * Returns the full underlying content as a single decoded string, byte-faithful to the source.\n *\n * @remarks\n * In **eager mode** the content is already cached at construction-time load and this method is\n * effectively a property access. In **streaming mode** there is no cache: the file is\n * re-streamed and concatenated on every call. Use {@link @nhtio/adk!SpooledArtifact.asString} judiciously\n * on large streaming-mode artifacts.\n */\n async readAll(): Promise<string> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.content\n const stream = await this.#disk.getStream(this.#key)\n const chunks: Uint8Array[] = []\n let total = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n chunks.push(view)\n total += view.length\n }\n const concat = new Uint8Array(total)\n let offset = 0\n for (const view of chunks) {\n concat.set(view, offset)\n offset += view.length\n }\n return new TextDecoder().decode(concat)\n }\n\n /**\n * Lazily initialise the reader's mode-specific state. Called by every public method; the\n * promise is cached so the work runs at most once.\n */\n #load(): Promise<ReaderState> {\n if (!this.#ready) this.#ready = this.#init()\n return this.#ready\n }\n\n async #init(): Promise<ReaderState> {\n const meta = await this.#disk.getMetaData(this.#key)\n const bytes = meta.contentLength\n if (!isNonNegativeFiniteNumber(bytes)) {\n // Defensive — flydrive's contract types this as `number`, but cloud drivers occasionally\n // return NaN/Infinity if the backing store omits the size header.\n throw new Error(\n `FlydriveSpoolReader: disk returned a non-finite contentLength (${String(bytes)}) for key \"${this.#key}\"`\n )\n }\n if (bytes < this.#threshold) {\n // Eager — pull the whole thing into memory.\n const content = await this.#disk.get(this.#key)\n const lines = content === '' ? [] : content.split('\\n')\n return { mode: 'eager', lines, bytes, content }\n }\n // Streaming — build a line-offset index by scanning bytes once.\n return this.#buildStreamingIndex(bytes)\n }\n\n async #buildStreamingIndex(bytes: number): Promise<StreamingState> {\n // Edge case first — an empty file is one offset (the EOF), zero lines.\n if (bytes === 0) return { mode: 'streaming', offsets: [0], bytes }\n\n const stream = await this.#disk.getStream(this.#key)\n // offsets[i] is the byte position where line `i` starts. offsets[lineCount] is one-past-end.\n // For \"a\\nb\\nc\" → offsets=[0, 2, 4, 5] (3 lines).\n // For \"a\\nb\\n\" → offsets=[0, 2, 4, 4] (3 lines, last is the trailing empty line). This\n // mirrors `String.prototype.split('\\n')` semantics so streaming and eager agree.\n const offsets: number[] = [0]\n let position = 0\n let lastByte = -1\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n for (const byte of view) {\n position++\n if (byte === LF) offsets.push(position)\n lastByte = byte\n }\n }\n // If the file ends on a newline, the byte after the LF is the start of an empty trailing\n // line — record it. If it doesn't, the final line's end is the EOF and we need to push\n // it so line(N-1) can read up to bytes.\n if (lastByte === LF) offsets.push(position)\n else if (offsets[offsets.length - 1] !== position) offsets.push(position)\n return { mode: 'streaming', offsets, bytes }\n }\n\n /**\n * Streams the byte range `[start, end)` from the backing disk and returns it as a UTF-8\n * string, stripping a trailing `\\n` if present.\n *\n * @remarks\n * flydrive doesn't expose native byte-range reads, so we open a fresh stream and skip until\n * we reach the requested start offset, then collect until we reach `end`. This is O(end)\n * per call — fine for occasional reads but worth profiling if a workload performs many\n * sequential `line()` calls on a large file.\n */\n async #readRange(start: number, end: number): Promise<string> {\n if (start === end) return ''\n const stream = await this.#disk.getStream(this.#key)\n const out: number[] = []\n let position = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n // Past the range entirely → stop early\n if (position >= end) {\n // Destroy the stream if it's a Node Readable; otherwise the for-await will naturally\n // continue, costing extra reads we don't need.\n // eslint-disable-next-line adk/use-is-instance-of -- native Node built-in; flydrive returns a real Readable, no cross-realm risk\n if (stream instanceof Readable) stream.destroy()\n break\n }\n // Fully before the range → skip the whole chunk\n if (position + view.length <= start) {\n position += view.length\n continue\n }\n // Partial overlap — copy the bytes that fall inside [start, end)\n const localStart = Math.max(0, start - position)\n const localEnd = Math.min(view.length, end - position)\n for (let i = localStart; i < localEnd; i++) out.push(view[i])\n position += view.length\n }\n // Strip the trailing newline if the range ended on one. The line-offset index ends each\n // line *after* its terminating LF (so offsets[i+1] points to the start of the next line),\n // and the SpoolReader contract returns lines *without* their trailing newline.\n if (out.length > 0 && out[out.length - 1] === LF) out.pop()\n return new TextDecoder().decode(new Uint8Array(out))\n }\n}\n\n/**\n * Constructor options for {@link FlydriveSpoolStore}.\n */\nexport interface FlydriveSpoolStoreOptions {\n /**\n * Optional key prefix prepended to every `callId`. Useful for namespacing tool-call artifacts\n * inside a shared bucket (e.g. `\"tool-calls/\"`).\n *\n * @defaultValue `\"\"`\n */\n keyPrefix?: string\n\n /**\n * Default `streamThresholdBytes` for readers produced by `write()` and `read()`. Individual\n * calls may override via their own `opts` argument.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\n/**\n * \"Give bytes, get a reader\" persistence layer over a flydrive {@link Disk}.\n *\n * @remarks\n * `write(callId, bytes)` calls `disk.put(key, bytes)` where `key = keyPrefix + callId`, then\n * returns a fresh {@link FlydriveSpoolReader} pointed at the same key. `read(callId)` returns\n * a reader without re-writing; `delete(callId)` calls `disk.delete(key)`.\n *\n * The store is stateless — it owns no in-memory cache of writes. Multiple `FlydriveSpoolStore`\n * instances sharing the same disk + key prefix see the same data.\n *\n * @example\n * ```ts\n * import { Disk } from 'flydrive'\n * import { FSDriver } from 'flydrive/drivers/fs'\n * import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'\n *\n * const disk = new Disk(new FSDriver({ location: './tmp', visibility: 'public' }))\n * const store = new FlydriveSpoolStore(disk)\n *\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class FlydriveSpoolStore {\n readonly #disk: Disk\n readonly #prefix: string\n readonly #defaultThreshold: number\n\n constructor(disk: Disk, opts: FlydriveSpoolStoreOptions = {}) {\n this.#disk = disk\n this.#prefix = opts.keyPrefix ?? ''\n this.#defaultThreshold = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n }\n\n /**\n * Persists `bytes` under `callId` and returns a reader bound to the stored key.\n *\n * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}.\n * @param bytes - The bytes to store, as a `string` or `Uint8Array`.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader} over the stored bytes.\n */\n async write(\n callId: string,\n bytes: string | Uint8Array,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader> {\n const key = this.#prefix + callId\n await this.#disk.put(key, bytes)\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`.\n *\n * @remarks\n * Returns `undefined` if the underlying key does not exist. Existence is checked via\n * `disk.exists(key)` before the reader is returned, so callers can rely on a defined return\n * value pointing at a real object.\n *\n * @param callId - Identifier supplied to a prior {@link FlydriveSpoolStore.write} call.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader}, or `undefined` if the key is missing.\n */\n async read(\n callId: string,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader | undefined> {\n const key = this.#prefix + callId\n if (!(await this.#disk.exists(key))) return undefined\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if the key existed and was removed; `false` if it didn't exist.\n */\n async delete(callId: string): Promise<boolean> {\n const key = this.#prefix + callId\n const existed = await this.#disk.exists(key)\n if (!existed) return false\n await this.#disk.delete(key)\n return true\n }\n\n /**\n * Returns the full disk key for a given `callId` (i.e. `keyPrefix + callId`).\n *\n * @remarks\n * Useful for tests or for callers that want to interact with the underlying disk directly.\n */\n keyFor(callId: string): string {\n return this.#prefix + callId\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"flydrive.mjs","names":["#disk","#key","#threshold","#load","#readRange","#ready","#init","#buildStreamingIndex","#prefix","#defaultThreshold"],"sources":["../../../__vite-browser-external","../../../src/batteries/storage/flydrive/index.ts"],"sourcesContent":["module.exports = {}","/**\n * Flydrive-backed spooled artifact storage for Node and server runtimes.\n *\n * @module @nhtio/adk/batteries/storage/flydrive\n *\n * @remarks\n * **Requires Node 24+.** `flydrive` uses the `node:stream` `ReadableStream` web API which is\n * only available from Node 24. This battery does not work in the browser or earlier Node versions.\n *\n * Opt-in storage battery backed by [flydrive](https://flydrive.dev). Provides\n * {@link FlydriveSpoolReader} (a {@link @nhtio/adk!SpoolReader} over a flydrive key) and\n * {@link FlydriveSpoolStore} (a `write(callId, bytes) → reader` persistence layer that wraps an\n * existing `Disk`).\n *\n * The reader has two modes selected at construction time based on the size of the underlying\n * object:\n *\n * - **Eager mode** — when the object's `contentLength` is below `streamThresholdBytes` (default\n * 10 MiB), the reader calls `disk.get(key)` once, splits the content on `\\n`, and caches\n * lines + byte count. All subsequent `line() / byteLength() / lineCount()` calls resolve from\n * memory.\n * - **Streaming mode** — when `contentLength` meets or exceeds the threshold, the reader\n * streams the file once via `disk.getStream(key)` to build a line-offset index (`number[]`\n * of byte offsets per line), then serves each `line(i)` request by streaming the byte range\n * `[offsets[i], offsets[i+1])`. Caps RAM at one index + one line buffer regardless of file\n * size.\n *\n * Set `streamThresholdBytes: 0` to force streaming mode; set it to `Infinity` to force eager\n * mode. The default of 10 MiB matches typical tool output sizes — tune it for your workload.\n *\n * The store and reader are pure-flydrive: they don't know about S3, GCS, or filesystem\n * specifically — they delegate to whatever `Disk` you construct.\n */\n\nimport { Disk } from 'flydrive'\nimport { Readable } from 'node:stream'\nimport { isInstanceOf } from '@nhtio/adk/guards'\nimport type { SpoolReader, SpoolStore } from '@nhtio/adk/common'\n\nconst DEFAULT_STREAM_THRESHOLD_BYTES = 10 * 1024 * 1024 // 10 MiB\n\nconst LF = 0x0a // '\\n'\n\n/**\n * Constructor options for {@link FlydriveSpoolReader}.\n */\nexport interface FlydriveSpoolReaderOptions {\n /**\n * Byte-length threshold that switches between eager and streaming modes.\n *\n * @remarks\n * - Below the threshold → eager (whole-file in memory).\n * - At or above the threshold → streaming (line-offset index + per-line streaming reads).\n *\n * Set to `0` to force streaming mode; set to `Number.POSITIVE_INFINITY` to force eager mode.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\ninterface EagerState {\n mode: 'eager'\n lines: string[]\n bytes: number\n content: string\n}\n\ninterface StreamingState {\n mode: 'streaming'\n /**\n * Byte offsets where each line *starts*. Length equals lineCount + 1; the final entry equals\n * the total byte length. So `offsets[i + 1] - offsets[i]` is the byte length of line `i`\n * including any trailing `\\n`.\n */\n offsets: number[]\n bytes: number\n}\n\ntype ReaderState = EagerState | StreamingState\n\nconst isNonNegativeFiniteNumber = (n: unknown): n is number =>\n typeof n === 'number' && Number.isFinite(n) && n >= 0\n\n/**\n * Reads a flydrive-backed file as a {@link @nhtio/adk!SpoolReader}.\n *\n * @remarks\n * Constructor is **not** async — but the first method call awaits a private readiness promise\n * that fetches the object's metadata (and in eager mode, its contents). Subsequent calls reuse\n * the cached state. This keeps construction call sites synchronous while still doing real I/O\n * lazily.\n *\n * Implementations of {@link @nhtio/adk!SpoolReader.line}, {@link @nhtio/adk!SpoolReader.byteLength}, and\n * {@link @nhtio/adk!SpoolReader.lineCount} all return promises. The `SpoolReader` contract supports both\n * sync and async return; consumers of `SpooledArtifact` handle either.\n */\nexport class FlydriveSpoolReader implements SpoolReader {\n readonly #disk: Disk\n readonly #key: string\n readonly #threshold: number\n #ready: Promise<ReaderState> | undefined\n\n constructor(disk: Disk, key: string, opts: FlydriveSpoolReaderOptions = {}) {\n this.#disk = disk\n this.#key = key\n const raw = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n // Allow `Infinity` (forces eager) but reject anything non-finite-negative.\n if (typeof raw !== 'number' || Number.isNaN(raw) || raw < 0) {\n throw new TypeError(\n `FlydriveSpoolReader: streamThresholdBytes must be a non-negative number or Infinity, got ${String(raw)}`\n )\n }\n this.#threshold = raw\n }\n\n async line(index: number): Promise<string | undefined> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.lines[index]\n if (index < 0 || index >= state.offsets.length - 1) return undefined\n return this.#readRange(state.offsets[index], state.offsets[index + 1])\n }\n\n async byteLength(): Promise<number> {\n const state = await this.#load()\n return state.bytes\n }\n\n async lineCount(): Promise<number> {\n const state = await this.#load()\n return state.mode === 'eager' ? state.lines.length : state.offsets.length - 1\n }\n\n /**\n * Returns the full underlying content as a single decoded string, byte-faithful to the source.\n *\n * @remarks\n * In **eager mode** the content is already cached at construction-time load and this method is\n * effectively a property access. In **streaming mode** there is no cache: the file is\n * re-streamed and concatenated on every call. Use {@link @nhtio/adk!SpooledArtifact.asString} judiciously\n * on large streaming-mode artifacts.\n */\n async readAll(): Promise<string> {\n const state = await this.#load()\n if (state.mode === 'eager') return state.content\n const stream = await this.#disk.getStream(this.#key)\n const chunks: Uint8Array[] = []\n let total = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n chunks.push(view)\n total += view.length\n }\n const concat = new Uint8Array(total)\n let offset = 0\n for (const view of chunks) {\n concat.set(view, offset)\n offset += view.length\n }\n return new TextDecoder().decode(concat)\n }\n\n /**\n * Lazily initialise the reader's mode-specific state. Called by every public method; the\n * promise is cached so the work runs at most once.\n */\n #load(): Promise<ReaderState> {\n if (!this.#ready) this.#ready = this.#init()\n return this.#ready\n }\n\n async #init(): Promise<ReaderState> {\n const meta = await this.#disk.getMetaData(this.#key)\n const bytes = meta.contentLength\n if (!isNonNegativeFiniteNumber(bytes)) {\n // Defensive — flydrive's contract types this as `number`, but cloud drivers occasionally\n // return NaN/Infinity if the backing store omits the size header.\n throw new Error(\n `FlydriveSpoolReader: disk returned a non-finite contentLength (${String(bytes)}) for key \"${this.#key}\"`\n )\n }\n if (bytes < this.#threshold) {\n // Eager — pull the whole thing into memory.\n const content = await this.#disk.get(this.#key)\n const lines = content === '' ? [] : content.split('\\n')\n return { mode: 'eager', lines, bytes, content }\n }\n // Streaming — build a line-offset index by scanning bytes once.\n return this.#buildStreamingIndex(bytes)\n }\n\n async #buildStreamingIndex(bytes: number): Promise<StreamingState> {\n // Edge case first — an empty file is one offset (the EOF), zero lines.\n if (bytes === 0) return { mode: 'streaming', offsets: [0], bytes }\n\n const stream = await this.#disk.getStream(this.#key)\n // offsets[i] is the byte position where line `i` starts. offsets[lineCount] is one-past-end.\n // For \"a\\nb\\nc\" → offsets=[0, 2, 4, 5] (3 lines).\n // For \"a\\nb\\n\" → offsets=[0, 2, 4, 4] (3 lines, last is the trailing empty line). This\n // mirrors `String.prototype.split('\\n')` semantics so streaming and eager agree.\n const offsets: number[] = [0]\n let position = 0\n let lastByte = -1\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n for (const byte of view) {\n position++\n if (byte === LF) offsets.push(position)\n lastByte = byte\n }\n }\n // If the file ends on a newline, the byte after the LF is the start of an empty trailing\n // line — record it. If it doesn't, the final line's end is the EOF and we need to push\n // it so line(N-1) can read up to bytes.\n if (lastByte === LF) offsets.push(position)\n else if (offsets[offsets.length - 1] !== position) offsets.push(position)\n return { mode: 'streaming', offsets, bytes }\n }\n\n /**\n * Streams the byte range `[start, end)` from the backing disk and returns it as a UTF-8\n * string, stripping a trailing `\\n` if present.\n *\n * @remarks\n * flydrive doesn't expose native byte-range reads, so we open a fresh stream and skip until\n * we reach the requested start offset, then collect until we reach `end`. This is O(end)\n * per call — fine for occasional reads but worth profiling if a workload performs many\n * sequential `line()` calls on a large file.\n */\n async #readRange(start: number, end: number): Promise<string> {\n if (start === end) return ''\n const stream = await this.#disk.getStream(this.#key)\n const out: number[] = []\n let position = 0\n for await (const chunk of stream as AsyncIterable<Buffer | Uint8Array>) {\n // eslint-disable-next-line adk/use-is-instance-of -- native built-in narrowing on stream chunks; cross-realm fragility does not apply here\n const view = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)\n // Past the range entirely → stop early\n if (position >= end) {\n // Destroy the stream if it's a Node Readable; otherwise the for-await will naturally\n // continue, costing extra reads we don't need.\n // eslint-disable-next-line adk/use-is-instance-of -- native Node built-in; flydrive returns a real Readable, no cross-realm risk\n if (stream instanceof Readable) stream.destroy()\n break\n }\n // Fully before the range → skip the whole chunk\n if (position + view.length <= start) {\n position += view.length\n continue\n }\n // Partial overlap — copy the bytes that fall inside [start, end)\n const localStart = Math.max(0, start - position)\n const localEnd = Math.min(view.length, end - position)\n for (let i = localStart; i < localEnd; i++) out.push(view[i])\n position += view.length\n }\n // Strip the trailing newline if the range ended on one. The line-offset index ends each\n // line *after* its terminating LF (so offsets[i+1] points to the start of the next line),\n // and the SpoolReader contract returns lines *without* their trailing newline.\n if (out.length > 0 && out[out.length - 1] === LF) out.pop()\n return new TextDecoder().decode(new Uint8Array(out))\n }\n}\n\n/**\n * Constructor options for {@link FlydriveSpoolStore}.\n */\nexport interface FlydriveSpoolStoreOptions {\n /**\n * Optional key prefix prepended to every `callId`. Useful for namespacing tool-call artifacts\n * inside a shared bucket (e.g. `\"tool-calls/\"`).\n *\n * @defaultValue `\"\"`\n */\n keyPrefix?: string\n\n /**\n * Default `streamThresholdBytes` for readers produced by `write()` and `read()`. Individual\n * calls may override via their own `opts` argument.\n *\n * @defaultValue `10 * 1024 * 1024` (10 MiB)\n */\n streamThresholdBytes?: number\n}\n\n/**\n * \"Give bytes, get a reader\" persistence layer over a flydrive {@link Disk}.\n *\n * @remarks\n * `write(callId, bytes)` calls `disk.put(key, bytes)` where `key = keyPrefix + callId`, then\n * returns a fresh {@link FlydriveSpoolReader} pointed at the same key. `read(callId)` returns\n * a reader without re-writing; `delete(callId)` calls `disk.delete(key)`.\n *\n * The store is stateless — it owns no in-memory cache of writes. Multiple `FlydriveSpoolStore`\n * instances sharing the same disk + key prefix see the same data.\n *\n * @example\n * ```ts\n * import { Disk } from 'flydrive'\n * import { FSDriver } from 'flydrive/drivers/fs'\n * import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive'\n *\n * const disk = new Disk(new FSDriver({ location: './tmp', visibility: 'public' }))\n * const store = new FlydriveSpoolStore(disk)\n *\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class FlydriveSpoolStore implements SpoolStore {\n readonly #disk: Disk\n readonly #prefix: string\n readonly #defaultThreshold: number\n\n constructor(disk: Disk, opts: FlydriveSpoolStoreOptions = {}) {\n this.#disk = disk\n this.#prefix = opts.keyPrefix ?? ''\n this.#defaultThreshold = opts.streamThresholdBytes ?? DEFAULT_STREAM_THRESHOLD_BYTES\n }\n\n /**\n * Persists `bytes` under `callId` and returns a reader bound to the stored key.\n *\n * @remarks\n * `string`/`Uint8Array` input goes through `disk.put`; `ReadableStream<Uint8Array>` is forwarded\n * to `disk.putStream` (via `Readable.fromWeb`) so the payload streams straight to the backing\n * driver — to disk for `FSDriver`, to the object store for S3/GCS — without being materialized\n * in memory first.\n *\n * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}.\n * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader} over the stored bytes.\n */\n async write(\n callId: string,\n bytes: string | Uint8Array | ReadableStream<Uint8Array>,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader> {\n const key = this.#prefix + callId\n if (isInstanceOf(bytes, 'ReadableStream', ReadableStream)) {\n await this.#disk.putStream(\n key,\n Readable.fromWeb(bytes as Parameters<typeof Readable.fromWeb>[0])\n )\n } else {\n await this.#disk.put(key, bytes)\n }\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`.\n *\n * @remarks\n * Returns `undefined` if the underlying key does not exist. Existence is checked via\n * `disk.exists(key)` before the reader is returned, so callers can rely on a defined return\n * value pointing at a real object.\n *\n * @param callId - Identifier supplied to a prior {@link FlydriveSpoolStore.write} call.\n * @param opts - Per-call override for `streamThresholdBytes`.\n * @returns A {@link FlydriveSpoolReader}, or `undefined` if the key is missing.\n */\n async read(\n callId: string,\n opts?: FlydriveSpoolReaderOptions\n ): Promise<FlydriveSpoolReader | undefined> {\n const key = this.#prefix + callId\n if (!(await this.#disk.exists(key))) return undefined\n return new FlydriveSpoolReader(this.#disk, key, {\n streamThresholdBytes: opts?.streamThresholdBytes ?? this.#defaultThreshold,\n })\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if the key existed and was removed; `false` if it didn't exist.\n */\n async delete(callId: string): Promise<boolean> {\n const key = this.#prefix + callId\n const existed = await this.#disk.exists(key)\n if (!existed) return false\n await this.#disk.delete(key)\n return true\n }\n\n /**\n * Returns the full disk key for a given `callId` (i.e. `keyPrefix + callId`).\n *\n * @remarks\n * Useful for tests or for callers that want to interact with the underlying disk directly.\n */\n keyFor(callId: string): string {\n return this.#prefix + callId\n }\n}\n"],"mappings":"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"}
@@ -18,14 +18,19 @@
18
18
  * Do **not** use this for production agents that need durability across process restarts —
19
19
  * everything lives in process memory and is lost on exit.
20
20
  */
21
- import type { SpoolReader } from "../../../common";
21
+ import type { SpoolReader, SpoolStore } from "../../../common";
22
22
  /**
23
- * Sync in-memory {@link @nhtio/adk!SpoolReader} over a `string` body.
23
+ * Sync in-memory {@link @nhtio/adk!SpoolReader} over a byte-faithful `Uint8Array` body.
24
24
  *
25
25
  * @remarks
26
- * Splits the supplied content on `\n` at construction time and caches the resulting line array
27
- * plus the UTF-8 byte length. All three `SpoolReader` methods resolve synchronously from the
28
- * cache — no I/O happens after construction.
26
+ * Stores the raw bytes and decodes them as UTF-8 once at construction, then splits the decoded
27
+ * string on `\n` and caches the resulting line array. All four `SpoolReader` methods resolve
28
+ * synchronously from the cache — no I/O happens after construction. `byteLength()` reports the
29
+ * true stored byte count (not the decoded character count), so it stays correct for multi-byte
30
+ * content; `line()`/`readAll()` operate on the decoded text.
31
+ *
32
+ * The reader accepts a `string` or a `Uint8Array`. A `string` is encoded as UTF-8 for the byte
33
+ * count; a `Uint8Array` is held byte-faithfully (no lossy re-encode) and decoded for text reads.
29
34
  *
30
35
  * Empty input yields a reader with `lineCount() === 0` and `byteLength() === 0`. A trailing
31
36
  * newline produces a final empty line: `"a\nb\n".split('\n') === ['a', 'b', '']`. This matches
@@ -34,7 +39,7 @@ import type { SpoolReader } from "../../../common";
34
39
  */
35
40
  export declare class InMemorySpoolReader implements SpoolReader {
36
41
  #private;
37
- constructor(content: string);
42
+ constructor(content: string | Uint8Array);
38
43
  line(index: number): string | undefined;
39
44
  byteLength(): number;
40
45
  lineCount(): number;
@@ -44,38 +49,47 @@ export declare class InMemorySpoolReader implements SpoolReader {
44
49
  * In-memory "give bytes, get a reader" persistence layer keyed by `callId`.
45
50
  *
46
51
  * @remarks
47
- * Stores the canonical UTF-8 string form of each value. `Uint8Array` inputs are decoded via
48
- * `TextDecoder` once at write time — subsequent `read()` calls return a reader over the cached
49
- * string with no further decoding.
52
+ * Stores each value byte-faithfully as a `Uint8Array`. `string` inputs are encoded as UTF-8;
53
+ * `Uint8Array` inputs are held verbatim (no lossy text round-trip, so binary payloads survive
54
+ * intact); `ReadableStream<Uint8Array>` inputs are drained fully into a buffer — in-memory storage
55
+ * cannot stream to disk, so the stream form resolves asynchronously and is the documented
56
+ * trade-off for this battery.
50
57
  *
51
58
  * Each `write()` and each `read()` returns a *fresh* {@link InMemorySpoolReader} — the store
52
59
  * owns the bytes, the reader is a view. Mutating the store after handing out a reader does not
53
60
  * invalidate the reader.
54
61
  *
62
+ * Implements {@link @nhtio/adk!SpoolStore} (i.e. `ByteStore<SpoolReader>`).
63
+ *
55
64
  * @example
56
65
  * ```ts
57
66
  * const store = new InMemorySpoolStore()
58
67
  * const bytes = await tool.executor(ctx)(args)
59
- * const reader = store.write(callId, bytes)
68
+ * const reader = await store.write(callId, bytes)
60
69
  * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact
61
70
  * const artifact = new Ctor(reader)
62
71
  * ```
63
72
  */
64
- export declare class InMemorySpoolStore {
73
+ export declare class InMemorySpoolStore implements SpoolStore {
65
74
  #private;
66
75
  /**
67
76
  * Persists `bytes` under `callId` and returns a reader over them.
68
77
  *
69
78
  * @remarks
70
- * `Uint8Array` inputs are decoded as UTF-8. Re-writing the same `callId` replaces the prior
71
- * entry; readers handed out before the rewrite continue to view the old bytes (they hold their
72
- * own snapshot via the `InMemorySpoolReader` constructor).
79
+ * `string` input is encoded as UTF-8; `Uint8Array` is stored byte-faithfully;
80
+ * `ReadableStream<Uint8Array>` is drained fully (and `write` returns a `Promise`). Re-writing the
81
+ * same `callId` replaces the prior entry; readers handed out before the rewrite continue to view
82
+ * the old bytes (they hold their own snapshot via the `InMemorySpoolReader` constructor).
73
83
  *
74
84
  * @param callId - Identifier used to retrieve the bytes via {@link InMemorySpoolStore.read}.
75
- * @param bytes - The bytes to store, as a `string` or `Uint8Array`.
76
- * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes.
85
+ * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.
86
+ * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes — a `Promise` for stream
87
+ * input, synchronous otherwise.
77
88
  */
78
- write(callId: string, bytes: string | Uint8Array): InMemorySpoolReader;
89
+ write(callId: string, bytes: string): InMemorySpoolReader;
90
+ write(callId: string, bytes: Uint8Array): InMemorySpoolReader;
91
+ write(callId: string, bytes: ReadableStream<Uint8Array>): Promise<InMemorySpoolReader>;
92
+ write(callId: string, bytes: string | Uint8Array | ReadableStream<Uint8Array>): InMemorySpoolReader | Promise<InMemorySpoolReader>;
79
93
  /**
80
94
  * Returns a reader over the bytes previously written under `callId`, or `undefined` if the
81
95
  * entry has not been written or has been deleted.
@@ -1,12 +1,39 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_tool_registry = require("../../tool_registry-snPjF0zJ.js");
3
+ require("../../guards.cjs");
2
4
  //#region src/batteries/storage/in_memory/index.ts
3
5
  /**
4
- * Sync in-memory {@link @nhtio/adk!SpoolReader} over a `string` body.
6
+ * In-memory spool readers and stores for tests, scripts, and non-durable prototypes.
7
+ *
8
+ * @module @nhtio/adk/batteries/storage/in_memory
5
9
  *
6
10
  * @remarks
7
- * Splits the supplied content on `\n` at construction time and caches the resulting line array
8
- * plus the UTF-8 byte length. All three `SpoolReader` methods resolve synchronously from the
9
- * cache no I/O happens after construction.
11
+ * Opt-in in-memory persistence battery. Provides {@link InMemorySpoolReader} (a sync
12
+ * {@link @nhtio/adk!SpoolReader} over a string) plus {@link InMemorySpoolStore} (a `Map<callId, bytes>`
13
+ * with a `write()` method that returns a fresh reader bound to the stored bytes).
14
+ *
15
+ * Use this when:
16
+ *
17
+ * - Writing unit or functional tests that need a real `SpoolReader` over known bytes.
18
+ * - Running a REPL or one-shot script where persistence beyond the process lifetime is not
19
+ * needed.
20
+ * - Prototyping an agent before deciding on a real disk/object-store-backed persistence layer.
21
+ *
22
+ * Do **not** use this for production agents that need durability across process restarts —
23
+ * everything lives in process memory and is lost on exit.
24
+ */
25
+ /**
26
+ * Sync in-memory {@link @nhtio/adk!SpoolReader} over a byte-faithful `Uint8Array` body.
27
+ *
28
+ * @remarks
29
+ * Stores the raw bytes and decodes them as UTF-8 once at construction, then splits the decoded
30
+ * string on `\n` and caches the resulting line array. All four `SpoolReader` methods resolve
31
+ * synchronously from the cache — no I/O happens after construction. `byteLength()` reports the
32
+ * true stored byte count (not the decoded character count), so it stays correct for multi-byte
33
+ * content; `line()`/`readAll()` operate on the decoded text.
34
+ *
35
+ * The reader accepts a `string` or a `Uint8Array`. A `string` is encoded as UTF-8 for the byte
36
+ * count; a `Uint8Array` is held byte-faithfully (no lossy re-encode) and decoded for text reads.
10
37
  *
11
38
  * Empty input yields a reader with `lineCount() === 0` and `byteLength() === 0`. A trailing
12
39
  * newline produces a final empty line: `"a\nb\n".split('\n') === ['a', 'b', '']`. This matches
@@ -18,9 +45,14 @@ var InMemorySpoolReader = class {
18
45
  #lines;
19
46
  #bytes;
20
47
  constructor(content) {
21
- this.#content = content;
22
- this.#lines = content === "" ? [] : content.split("\n");
23
- this.#bytes = new TextEncoder().encode(content).length;
48
+ if (typeof content === "string") {
49
+ this.#content = content;
50
+ this.#bytes = new TextEncoder().encode(content).length;
51
+ } else {
52
+ this.#content = new TextDecoder().decode(content);
53
+ this.#bytes = content.byteLength;
54
+ }
55
+ this.#lines = this.#content === "" ? [] : this.#content.split("\n");
24
56
  }
25
57
  line(index) {
26
58
  return this.#lines[index];
@@ -36,45 +68,72 @@ var InMemorySpoolReader = class {
36
68
  }
37
69
  };
38
70
  /**
71
+ * Drains a `ReadableStream<Uint8Array>` into a single concatenated `Uint8Array`.
72
+ *
73
+ * @remarks
74
+ * In-memory storage cannot stream-to-disk, so a stream input is buffered fully — the documented
75
+ * trade-off for {@link InMemorySpoolStore}. Use {@link @nhtio/adk/batteries/storage/opfs!OpfsSpoolStore}
76
+ * or a Flydrive-backed store when true streaming persistence is required.
77
+ */
78
+ var drainStream = async (stream) => {
79
+ const chunks = [];
80
+ let total = 0;
81
+ const reader = stream.getReader();
82
+ try {
83
+ for (;;) {
84
+ const { done, value } = await reader.read();
85
+ if (done) break;
86
+ if (value) {
87
+ chunks.push(value);
88
+ total += value.byteLength;
89
+ }
90
+ }
91
+ } finally {
92
+ reader.releaseLock();
93
+ }
94
+ const out = new Uint8Array(total);
95
+ let offset = 0;
96
+ for (const chunk of chunks) {
97
+ out.set(chunk, offset);
98
+ offset += chunk.byteLength;
99
+ }
100
+ return out;
101
+ };
102
+ /**
39
103
  * In-memory "give bytes, get a reader" persistence layer keyed by `callId`.
40
104
  *
41
105
  * @remarks
42
- * Stores the canonical UTF-8 string form of each value. `Uint8Array` inputs are decoded via
43
- * `TextDecoder` once at write time — subsequent `read()` calls return a reader over the cached
44
- * string with no further decoding.
106
+ * Stores each value byte-faithfully as a `Uint8Array`. `string` inputs are encoded as UTF-8;
107
+ * `Uint8Array` inputs are held verbatim (no lossy text round-trip, so binary payloads survive
108
+ * intact); `ReadableStream<Uint8Array>` inputs are drained fully into a buffer — in-memory storage
109
+ * cannot stream to disk, so the stream form resolves asynchronously and is the documented
110
+ * trade-off for this battery.
45
111
  *
46
112
  * Each `write()` and each `read()` returns a *fresh* {@link InMemorySpoolReader} — the store
47
113
  * owns the bytes, the reader is a view. Mutating the store after handing out a reader does not
48
114
  * invalidate the reader.
49
115
  *
116
+ * Implements {@link @nhtio/adk!SpoolStore} (i.e. `ByteStore<SpoolReader>`).
117
+ *
50
118
  * @example
51
119
  * ```ts
52
120
  * const store = new InMemorySpoolStore()
53
121
  * const bytes = await tool.executor(ctx)(args)
54
- * const reader = store.write(callId, bytes)
122
+ * const reader = await store.write(callId, bytes)
55
123
  * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact
56
124
  * const artifact = new Ctor(reader)
57
125
  * ```
58
126
  */
59
127
  var InMemorySpoolStore = class {
60
128
  #entries = /* @__PURE__ */ new Map();
61
- #decoder = new TextDecoder();
62
- /**
63
- * Persists `bytes` under `callId` and returns a reader over them.
64
- *
65
- * @remarks
66
- * `Uint8Array` inputs are decoded as UTF-8. Re-writing the same `callId` replaces the prior
67
- * entry; readers handed out before the rewrite continue to view the old bytes (they hold their
68
- * own snapshot via the `InMemorySpoolReader` constructor).
69
- *
70
- * @param callId - Identifier used to retrieve the bytes via {@link InMemorySpoolStore.read}.
71
- * @param bytes - The bytes to store, as a `string` or `Uint8Array`.
72
- * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes.
73
- */
74
129
  write(callId, bytes) {
75
- const text = typeof bytes === "string" ? bytes : this.#decoder.decode(bytes);
76
- this.#entries.set(callId, text);
77
- return new InMemorySpoolReader(text);
130
+ if (require_tool_registry.isInstanceOf(bytes, "ReadableStream", ReadableStream)) return drainStream(bytes).then((buffer) => {
131
+ this.#entries.set(callId, buffer);
132
+ return new InMemorySpoolReader(buffer);
133
+ });
134
+ const buffer = typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes;
135
+ this.#entries.set(callId, buffer);
136
+ return new InMemorySpoolReader(buffer);
78
137
  }
79
138
  /**
80
139
  * Returns a reader over the bytes previously written under `callId`, or `undefined` if the
@@ -84,9 +143,9 @@ var InMemorySpoolStore = class {
84
143
  * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes, or `undefined`.
85
144
  */
86
145
  read(callId) {
87
- const text = this.#entries.get(callId);
88
- if (text === void 0) return void 0;
89
- return new InMemorySpoolReader(text);
146
+ const buffer = this.#entries.get(callId);
147
+ if (buffer === void 0) return void 0;
148
+ return new InMemorySpoolReader(buffer);
90
149
  }
91
150
  /**
92
151
  * Removes the entry under `callId`.
@@ -1 +1 @@
1
- {"version":3,"file":"in_memory.cjs","names":["#content","#lines","#bytes","#entries","#decoder"],"sources":["../../../src/batteries/storage/in_memory/index.ts"],"sourcesContent":["/**\n * In-memory spool readers and stores for tests, scripts, and non-durable prototypes.\n *\n * @module @nhtio/adk/batteries/storage/in_memory\n *\n * @remarks\n * Opt-in in-memory persistence battery. Provides {@link InMemorySpoolReader} (a sync\n * {@link @nhtio/adk!SpoolReader} over a string) plus {@link InMemorySpoolStore} (a `Map<callId, bytes>`\n * with a `write()` method that returns a fresh reader bound to the stored bytes).\n *\n * Use this when:\n *\n * - Writing unit or functional tests that need a real `SpoolReader` over known bytes.\n * - Running a REPL or one-shot script where persistence beyond the process lifetime is not\n * needed.\n * - Prototyping an agent before deciding on a real disk/object-store-backed persistence layer.\n *\n * Do **not** use this for production agents that need durability across process restarts —\n * everything lives in process memory and is lost on exit.\n */\n\nimport type { SpoolReader } from '@nhtio/adk/common'\n\n/**\n * Sync in-memory {@link @nhtio/adk!SpoolReader} over a `string` body.\n *\n * @remarks\n * Splits the supplied content on `\\n` at construction time and caches the resulting line array\n * plus the UTF-8 byte length. All three `SpoolReader` methods resolve synchronously from the\n * cache — no I/O happens after construction.\n *\n * Empty input yields a reader with `lineCount() === 0` and `byteLength() === 0`. A trailing\n * newline produces a final empty line: `\"a\\nb\\n\".split('\\n') === ['a', 'b', '']`. This matches\n * the JavaScript `String.prototype.split` contract and lets a `lineCount()` consumer\n * distinguish \"two lines, no trailing newline\" from \"two lines, trailing newline\".\n */\nexport class InMemorySpoolReader implements SpoolReader {\n readonly #content: string\n readonly #lines: string[]\n readonly #bytes: number\n\n constructor(content: string) {\n this.#content = content\n this.#lines = content === '' ? [] : content.split('\\n')\n this.#bytes = new TextEncoder().encode(content).length\n }\n\n line(index: number): string | undefined {\n return this.#lines[index]\n }\n\n byteLength(): number {\n return this.#bytes\n }\n\n lineCount(): number {\n return this.#lines.length\n }\n\n readAll(): string {\n return this.#content\n }\n}\n\n/**\n * In-memory \"give bytes, get a reader\" persistence layer keyed by `callId`.\n *\n * @remarks\n * Stores the canonical UTF-8 string form of each value. `Uint8Array` inputs are decoded via\n * `TextDecoder` once at write timesubsequent `read()` calls return a reader over the cached\n * string with no further decoding.\n *\n * Each `write()` and each `read()` returns a *fresh* {@link InMemorySpoolReader} — the store\n * owns the bytes, the reader is a view. Mutating the store after handing out a reader does not\n * invalidate the reader.\n *\n * @example\n * ```ts\n * const store = new InMemorySpoolStore()\n * const bytes = await tool.executor(ctx)(args)\n * const reader = store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class InMemorySpoolStore {\n readonly #entries = new Map<string, string>()\n readonly #decoder = new TextDecoder()\n\n /**\n * Persists `bytes` under `callId` and returns a reader over them.\n *\n * @remarks\n * `Uint8Array` inputs are decoded as UTF-8. Re-writing the same `callId` replaces the prior\n * entry; readers handed out before the rewrite continue to view the old bytes (they hold their\n * own snapshot via the `InMemorySpoolReader` constructor).\n *\n * @param callId - Identifier used to retrieve the bytes via {@link InMemorySpoolStore.read}.\n * @param bytes - The bytes to store, as a `string` or `Uint8Array`.\n * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes.\n */\n write(callId: string, bytes: string | Uint8Array): InMemorySpoolReader {\n const text = typeof bytes === 'string' ? bytes : this.#decoder.decode(bytes)\n this.#entries.set(callId, text)\n return new InMemorySpoolReader(text)\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`, or `undefined` if the\n * entry has not been written or has been deleted.\n *\n * @param callId - Identifier supplied to a prior {@link InMemorySpoolStore.write} call.\n * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes, or `undefined`.\n */\n read(callId: string): InMemorySpoolReader | undefined {\n const text = this.#entries.get(callId)\n if (text === undefined) return undefined\n return new InMemorySpoolReader(text)\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if an entry existed and was removed; `false` otherwise.\n */\n delete(callId: string): boolean {\n return this.#entries.delete(callId)\n }\n\n /**\n * Removes every entry from the store.\n *\n * @remarks\n * Existing readers handed out by prior `write()` / `read()` calls remain valid — they hold\n * their own snapshot.\n */\n clear(): void {\n this.#entries.clear()\n }\n\n /**\n * Returns the number of entries currently in the store.\n */\n get size(): number {\n return this.#entries.size\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,IAAa,sBAAb,MAAwD;CACtD;CACA;CACA;CAEA,YAAY,SAAiB;EAC3B,KAAKA,WAAW;EAChB,KAAKC,SAAS,YAAY,KAAK,CAAC,IAAI,QAAQ,MAAM,IAAI;EACtD,KAAKC,SAAS,IAAI,YAAY,EAAE,OAAO,OAAO,EAAE;CAClD;CAEA,KAAK,OAAmC;EACtC,OAAO,KAAKD,OAAO;CACrB;CAEA,aAAqB;EACnB,OAAO,KAAKC;CACd;CAEA,YAAoB;EAClB,OAAO,KAAKD,OAAO;CACrB;CAEA,UAAkB;EAChB,OAAO,KAAKD;CACd;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,qBAAb,MAAgC;CAC9B,2BAAoB,IAAI,IAAoB;CAC5C,WAAoB,IAAI,YAAY;;;;;;;;;;;;;CAcpC,MAAM,QAAgB,OAAiD;EACrE,MAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAKI,SAAS,OAAO,KAAK;EAC3E,KAAKD,SAAS,IAAI,QAAQ,IAAI;EAC9B,OAAO,IAAI,oBAAoB,IAAI;CACrC;;;;;;;;CASA,KAAK,QAAiD;EACpD,MAAM,OAAO,KAAKA,SAAS,IAAI,MAAM;EACrC,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;EAC/B,OAAO,IAAI,oBAAoB,IAAI;CACrC;;;;;;;CAQA,OAAO,QAAyB;EAC9B,OAAO,KAAKA,SAAS,OAAO,MAAM;CACpC;;;;;;;;CASA,QAAc;EACZ,KAAKA,SAAS,MAAM;CACtB;;;;CAKA,IAAI,OAAe;EACjB,OAAO,KAAKA,SAAS;CACvB;AACF"}
1
+ {"version":3,"file":"in_memory.cjs","names":["#content","#lines","#bytes","#entries"],"sources":["../../../src/batteries/storage/in_memory/index.ts"],"sourcesContent":["/**\n * In-memory spool readers and stores for tests, scripts, and non-durable prototypes.\n *\n * @module @nhtio/adk/batteries/storage/in_memory\n *\n * @remarks\n * Opt-in in-memory persistence battery. Provides {@link InMemorySpoolReader} (a sync\n * {@link @nhtio/adk!SpoolReader} over a string) plus {@link InMemorySpoolStore} (a `Map<callId, bytes>`\n * with a `write()` method that returns a fresh reader bound to the stored bytes).\n *\n * Use this when:\n *\n * - Writing unit or functional tests that need a real `SpoolReader` over known bytes.\n * - Running a REPL or one-shot script where persistence beyond the process lifetime is not\n * needed.\n * - Prototyping an agent before deciding on a real disk/object-store-backed persistence layer.\n *\n * Do **not** use this for production agents that need durability across process restarts —\n * everything lives in process memory and is lost on exit.\n */\n\nimport { isInstanceOf } from '@nhtio/adk/guards'\nimport type { SpoolReader, SpoolStore } from '@nhtio/adk/common'\n\n/**\n * Sync in-memory {@link @nhtio/adk!SpoolReader} over a byte-faithful `Uint8Array` body.\n *\n * @remarks\n * Stores the raw bytes and decodes them as UTF-8 once at construction, then splits the decoded\n * string on `\\n` and caches the resulting line array. All four `SpoolReader` methods resolve\n * synchronously from the cache — no I/O happens after construction. `byteLength()` reports the\n * true stored byte count (not the decoded character count), so it stays correct for multi-byte\n * content; `line()`/`readAll()` operate on the decoded text.\n *\n * The reader accepts a `string` or a `Uint8Array`. A `string` is encoded as UTF-8 for the byte\n * count; a `Uint8Array` is held byte-faithfully (no lossy re-encode) and decoded for text reads.\n *\n * Empty input yields a reader with `lineCount() === 0` and `byteLength() === 0`. A trailing\n * newline produces a final empty line: `\"a\\nb\\n\".split('\\n') === ['a', 'b', '']`. This matches\n * the JavaScript `String.prototype.split` contract and lets a `lineCount()` consumer\n * distinguish \"two lines, no trailing newline\" from \"two lines, trailing newline\".\n */\nexport class InMemorySpoolReader implements SpoolReader {\n readonly #content: string\n readonly #lines: string[]\n readonly #bytes: number\n\n constructor(content: string | Uint8Array) {\n if (typeof content === 'string') {\n this.#content = content\n this.#bytes = new TextEncoder().encode(content).length\n } else {\n this.#content = new TextDecoder().decode(content)\n this.#bytes = content.byteLength\n }\n this.#lines = this.#content === '' ? [] : this.#content.split('\\n')\n }\n\n line(index: number): string | undefined {\n return this.#lines[index]\n }\n\n byteLength(): number {\n return this.#bytes\n }\n\n lineCount(): number {\n return this.#lines.length\n }\n\n readAll(): string {\n return this.#content\n }\n}\n\n/**\n * Drains a `ReadableStream<Uint8Array>` into a single concatenated `Uint8Array`.\n *\n * @remarks\n * In-memory storage cannot stream-to-disk, so a stream input is buffered fully — the documented\n * trade-off for {@link InMemorySpoolStore}. Use {@link @nhtio/adk/batteries/storage/opfs!OpfsSpoolStore}\n * or a Flydrive-backed store when true streaming persistence is required.\n */\nconst drainStream = async (stream: ReadableStream<Uint8Array>): Promise<Uint8Array> => {\n const chunks: Uint8Array[] = []\n let total = 0\n const reader = stream.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (value) {\n chunks.push(value)\n total += value.byteLength\n }\n }\n } finally {\n reader.releaseLock()\n }\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.byteLength\n }\n return out\n}\n\n/**\n * In-memory \"give bytes, get a reader\" persistence layer keyed by `callId`.\n *\n * @remarks\n * Stores each value byte-faithfully as a `Uint8Array`. `string` inputs are encoded as UTF-8;\n * `Uint8Array` inputs are held verbatim (no lossy text round-trip, so binary payloads survive\n * intact); `ReadableStream<Uint8Array>` inputs are drained fully into a buffer in-memory storage\n * cannot stream to disk, so the stream form resolves asynchronously and is the documented\n * trade-off for this battery.\n *\n * Each `write()` and each `read()` returns a *fresh* {@link InMemorySpoolReader} — the store\n * owns the bytes, the reader is a view. Mutating the store after handing out a reader does not\n * invalidate the reader.\n *\n * Implements {@link @nhtio/adk!SpoolStore} (i.e. `ByteStore<SpoolReader>`).\n *\n * @example\n * ```ts\n * const store = new InMemorySpoolStore()\n * const bytes = await tool.executor(ctx)(args)\n * const reader = await store.write(callId, bytes)\n * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact\n * const artifact = new Ctor(reader)\n * ```\n */\nexport class InMemorySpoolStore implements SpoolStore {\n readonly #entries = new Map<string, Uint8Array>()\n\n /**\n * Persists `bytes` under `callId` and returns a reader over them.\n *\n * @remarks\n * `string` input is encoded as UTF-8; `Uint8Array` is stored byte-faithfully;\n * `ReadableStream<Uint8Array>` is drained fully (and `write` returns a `Promise`). Re-writing the\n * same `callId` replaces the prior entry; readers handed out before the rewrite continue to view\n * the old bytes (they hold their own snapshot via the `InMemorySpoolReader` constructor).\n *\n * @param callId - Identifier used to retrieve the bytes via {@link InMemorySpoolStore.read}.\n * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream<Uint8Array>`.\n * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes — a `Promise` for stream\n * input, synchronous otherwise.\n */\n write(callId: string, bytes: string): InMemorySpoolReader\n write(callId: string, bytes: Uint8Array): InMemorySpoolReader\n write(callId: string, bytes: ReadableStream<Uint8Array>): Promise<InMemorySpoolReader>\n write(\n callId: string,\n bytes: string | Uint8Array | ReadableStream<Uint8Array>\n ): InMemorySpoolReader | Promise<InMemorySpoolReader>\n write(\n callId: string,\n bytes: string | Uint8Array | ReadableStream<Uint8Array>\n ): InMemorySpoolReader | Promise<InMemorySpoolReader> {\n if (isInstanceOf(bytes, 'ReadableStream', ReadableStream)) {\n return drainStream(bytes).then((buffer) => {\n this.#entries.set(callId, buffer)\n return new InMemorySpoolReader(buffer)\n })\n }\n const buffer = typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes\n this.#entries.set(callId, buffer)\n return new InMemorySpoolReader(buffer)\n }\n\n /**\n * Returns a reader over the bytes previously written under `callId`, or `undefined` if the\n * entry has not been written or has been deleted.\n *\n * @param callId - Identifier supplied to a prior {@link InMemorySpoolStore.write} call.\n * @returns A fresh {@link InMemorySpoolReader} bound to the stored bytes, or `undefined`.\n */\n read(callId: string): InMemorySpoolReader | undefined {\n const buffer = this.#entries.get(callId)\n if (buffer === undefined) return undefined\n return new InMemorySpoolReader(buffer)\n }\n\n /**\n * Removes the entry under `callId`.\n *\n * @param callId - Identifier whose entry should be removed.\n * @returns `true` if an entry existed and was removed; `false` otherwise.\n */\n delete(callId: string): boolean {\n return this.#entries.delete(callId)\n }\n\n /**\n * Removes every entry from the store.\n *\n * @remarks\n * Existing readers handed out by prior `write()` / `read()` calls remain valid — they hold\n * their own snapshot.\n */\n clear(): void {\n this.#entries.clear()\n }\n\n /**\n * Returns the number of entries currently in the store.\n */\n get size(): number {\n return this.#entries.size\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,sBAAb,MAAwD;CACtD;CACA;CACA;CAEA,YAAY,SAA8B;EACxC,IAAI,OAAO,YAAY,UAAU;GAC/B,KAAKA,WAAW;GAChB,KAAKE,SAAS,IAAI,YAAY,EAAE,OAAO,OAAO,EAAE;EAClD,OAAO;GACL,KAAKF,WAAW,IAAI,YAAY,EAAE,OAAO,OAAO;GAChD,KAAKE,SAAS,QAAQ;EACxB;EACA,KAAKD,SAAS,KAAKD,aAAa,KAAK,CAAC,IAAI,KAAKA,SAAS,MAAM,IAAI;CACpE;CAEA,KAAK,OAAmC;EACtC,OAAO,KAAKC,OAAO;CACrB;CAEA,aAAqB;EACnB,OAAO,KAAKC;CACd;CAEA,YAAoB;EAClB,OAAO,KAAKD,OAAO;CACrB;CAEA,UAAkB;EAChB,OAAO,KAAKD;CACd;AACF;;;;;;;;;AAUA,IAAM,cAAc,OAAO,WAA4D;CACrF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,MAAM,SAAS,OAAO,UAAU;CAChC,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,IAAI,OAAO;IACT,OAAO,KAAK,KAAK;IACjB,SAAS,MAAM;GACjB;EACF;CACF,UAAU;EACR,OAAO,YAAY;CACrB;CACA,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,IAAa,qBAAb,MAAsD;CACpD,2BAAoB,IAAI,IAAwB;CAuBhD,MACE,QACA,OACoD;EACpD,IAAI,sBAAA,aAAa,OAAO,kBAAkB,cAAc,GACtD,OAAO,YAAY,KAAK,EAAE,MAAM,WAAW;GACzC,KAAKG,SAAS,IAAI,QAAQ,MAAM;GAChC,OAAO,IAAI,oBAAoB,MAAM;EACvC,CAAC;EAEH,MAAM,SAAS,OAAO,UAAU,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,IAAI;EAC7E,KAAKA,SAAS,IAAI,QAAQ,MAAM;EAChC,OAAO,IAAI,oBAAoB,MAAM;CACvC;;;;;;;;CASA,KAAK,QAAiD;EACpD,MAAM,SAAS,KAAKA,SAAS,IAAI,MAAM;EACvC,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;EACjC,OAAO,IAAI,oBAAoB,MAAM;CACvC;;;;;;;CAQA,OAAO,QAAyB;EAC9B,OAAO,KAAKA,SAAS,OAAO,MAAM;CACpC;;;;;;;;CASA,QAAc;EACZ,KAAKA,SAAS,MAAM;CACtB;;;;CAKA,IAAI,OAAe;EACjB,OAAO,KAAKA,SAAS;CACvB;AACF"}