/** * Flydrive-backed spooled artifact storage for Node and server runtimes. * * @module @nhtio/adk/batteries/storage/flydrive * * @remarks * **Requires Node 24+.** `flydrive` uses the `node:stream` `ReadableStream` web API which is * only available from Node 24. This battery does not work in the browser or earlier Node versions. * * Opt-in storage battery backed by [flydrive](https://flydrive.dev). Provides * {@link FlydriveSpoolReader} (a {@link @nhtio/adk!SpoolReader} over a flydrive key) and * {@link FlydriveSpoolStore} (a `write(callId, bytes) → reader` persistence layer that wraps an * existing `Disk`). * * The reader has two modes selected at construction time based on the size of the underlying * object: * * - **Eager mode** — when the object's `contentLength` is below `streamThresholdBytes` (default * 10 MiB), the reader calls `disk.get(key)` once, splits the content on `\n`, and caches * lines + byte count. All subsequent `line() / byteLength() / lineCount()` calls resolve from * memory. * - **Streaming mode** — when `contentLength` meets or exceeds the threshold, the reader * streams the file once via `disk.getStream(key)` to build a line-offset index (`number[]` * of byte offsets per line), then serves each `line(i)` request by streaming the byte range * `[offsets[i], offsets[i+1])`. Caps RAM at one index + one line buffer regardless of file * size. * * Set `streamThresholdBytes: 0` to force streaming mode; set it to `Infinity` to force eager * mode. The default of 10 MiB matches typical tool output sizes — tune it for your workload. * * The store and reader are pure-flydrive: they don't know about S3, GCS, or filesystem * specifically — they delegate to whatever `Disk` you construct. */ import { Disk } from 'flydrive'; import type { ReaderDescriptor, SpoolReader, SpoolStore } from "../../../common"; /** * Resolver tag for the flydrive spool reader handle. The locator carries the flydrive `key`; the live * `Disk` binding is re-injected by the consumer-registered resolver on decode (the key alone cannot * re-open the object store). */ export declare const SPOOL_READER_TAG_FLYDRIVE = "spool:flydrive"; /** * Constructor options for {@link FlydriveSpoolReader}. */ export interface FlydriveSpoolReaderOptions { /** * Byte-length threshold that switches between eager and streaming modes. * * @remarks * - Below the threshold → eager (whole-file in memory). * - At or above the threshold → streaming (line-offset index + per-line streaming reads). * * Set to `0` to force streaming mode; set to `Number.POSITIVE_INFINITY` to force eager mode. * * @defaultValue `10 * 1024 * 1024` (10 MiB) */ streamThresholdBytes?: number; } /** * Reads a flydrive-backed file as a {@link @nhtio/adk!SpoolReader}. * * @remarks * Constructor is **not** async — but the first method call awaits a private readiness promise * that fetches the object's metadata (and in eager mode, its contents). Subsequent calls reuse * the cached state. This keeps construction call sites synchronous while still doing real I/O * lazily. * * Implementations of {@link @nhtio/adk!SpoolReader.line}, {@link @nhtio/adk!SpoolReader.byteLength}, and * {@link @nhtio/adk!SpoolReader.lineCount} all return promises. The `SpoolReader` contract supports both * sync and async return; consumers of `SpooledArtifact` handle either. */ export declare class FlydriveSpoolReader implements SpoolReader { #private; constructor(disk: Disk, key: string, opts?: FlydriveSpoolReaderOptions); line(index: number): Promise; byteLength(): Promise; lineCount(): Promise; /** * Returns the full underlying content as a single decoded string, byte-faithful to the source. * * @remarks * In **eager mode** the content is already cached at construction-time load and this method is * effectively a property access. In **streaming mode** there is no cache: the file is * re-streamed and concatenated on every call. Use {@link @nhtio/adk!SpooledArtifact.asString} judiciously * on large streaming-mode artifacts. */ readAll(): Promise; describe(): ReaderDescriptor; } /** * Constructor options for {@link FlydriveSpoolStore}. */ export interface FlydriveSpoolStoreOptions { /** * Optional key prefix prepended to every `callId`. Useful for namespacing tool-call artifacts * inside a shared bucket (e.g. `"tool-calls/"`). * * @defaultValue `""` */ keyPrefix?: string; /** * Default `streamThresholdBytes` for readers produced by `write()` and `read()`. Individual * calls may override via their own `opts` argument. * * @defaultValue `10 * 1024 * 1024` (10 MiB) */ streamThresholdBytes?: number; } /** * "Give bytes, get a reader" persistence layer over a flydrive {@link Disk}. * * @remarks * `write(callId, bytes)` calls `disk.put(key, bytes)` where `key = keyPrefix + callId`, then * returns a fresh {@link FlydriveSpoolReader} pointed at the same key. `read(callId)` returns * a reader without re-writing; `delete(callId)` calls `disk.delete(key)`. * * The store is stateless — it owns no in-memory cache of writes. Multiple `FlydriveSpoolStore` * instances sharing the same disk + key prefix see the same data. * * @example * ```ts * import { Disk } from 'flydrive' * import { FSDriver } from 'flydrive/drivers/fs' * import { FlydriveSpoolStore } from '@nhtio/adk/batteries/storage/flydrive' * * const disk = new Disk(new FSDriver({ location: './tmp', visibility: 'public' })) * const store = new FlydriveSpoolStore(disk) * * const bytes = await tool.executor(ctx)(args) * const reader = await store.write(callId, bytes) * const Ctor = tool.artifactConstructor?.() ?? SpooledArtifact * const artifact = new Ctor(reader) * ``` */ export declare class FlydriveSpoolStore implements SpoolStore { #private; constructor(disk: Disk, opts?: FlydriveSpoolStoreOptions); /** * Persists `bytes` under `callId` and returns a reader bound to the stored key. * * @remarks * `string`/`Uint8Array` input goes through `disk.put`; `ReadableStream` is forwarded * to `disk.putStream` (via `Readable.fromWeb`) so the payload streams straight to the backing * driver — to disk for `FSDriver`, to the object store for S3/GCS — without being materialized * in memory first. * * @param callId - Identifier used to retrieve the bytes via {@link FlydriveSpoolStore.read}. * @param bytes - The bytes to store, as a `string`, `Uint8Array`, or `ReadableStream`. * @param opts - Per-call override for `streamThresholdBytes`. * @returns A {@link FlydriveSpoolReader} over the stored bytes. */ write(callId: string, bytes: string | Uint8Array | ReadableStream, opts?: FlydriveSpoolReaderOptions): Promise; /** * Returns a reader over the bytes previously written under `callId`. * * @remarks * Returns `undefined` if the underlying key does not exist. Existence is checked via * `disk.exists(key)` before the reader is returned, so callers can rely on a defined return * value pointing at a real object. * * @param callId - Identifier supplied to a prior {@link FlydriveSpoolStore.write} call. * @param opts - Per-call override for `streamThresholdBytes`. * @returns A {@link FlydriveSpoolReader}, or `undefined` if the key is missing. */ read(callId: string, opts?: FlydriveSpoolReaderOptions): Promise; /** * Removes the entry under `callId`. * * @param callId - Identifier whose entry should be removed. * @returns `true` if the key existed and was removed; `false` if it didn't exist. */ delete(callId: string): Promise; /** * Returns the full disk key for a given `callId` (i.e. `keyPrefix + callId`). * * @remarks * Useful for tests or for callers that want to interact with the underlying disk directly. */ keyFor(callId: string): string; }