{"version":3,"file":"flydrive.mjs","names":["#disk","#key","#threshold","#load","#readRange","#ready","#init","#buildStreamingIndex","#prefix","#defaultThreshold"],"sources":["../../../src/batteries/storage/flydrive/index.ts"],"sourcesContent":["/**\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 { ReaderDescriptor, SpoolReader, SpoolStore } from '@nhtio/adk/common'\n\n/**\n * Resolver tag for the flydrive spool reader handle. The locator carries the flydrive `key`; the live\n * `Disk` binding is re-injected by the consumer-registered resolver on decode (the key alone cannot\n * re-open the object store).\n */\nexport const SPOOL_READER_TAG_FLYDRIVE = 'spool:flydrive'\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  describe(): ReaderDescriptor {\n    // The flydrive key is the re-openable locator. The live `Disk` is NOT serialised — the\n    // consumer-registered resolver re-injects it on decode (see registerSpoolReaderResolver).\n    return {\n      tag: SPOOL_READER_TAG_FLYDRIVE,\n      locator: { key: this.#key, streamThresholdBytes: this.#threshold },\n    }\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":";;;;;;;;;AA4CA,IAAa,4BAA4B;AAEzC,IAAM,iCAAiC,KAAK,OAAO;AAEnD,IAAM,KAAK;AAwCX,IAAM,6BAA6B,MACjC,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,KAAK;;;;;;;;;;;;;;AAetD,IAAa,sBAAb,MAAwD;CACtD;CACA;CACA;CACA;CAEA,YAAY,MAAY,KAAa,OAAmC,CAAC,GAAG;EAC1E,KAAKA,QAAQ;EACb,KAAKC,OAAO;EACZ,MAAM,MAAM,KAAK,wBAAwB;EAEzC,IAAI,OAAO,QAAQ,YAAY,OAAO,MAAM,GAAG,KAAK,MAAM,GACxD,MAAM,IAAI,UACR,4FAA4F,OAAO,GAAG,GACxG;EAEF,KAAKC,aAAa;CACpB;CAEA,MAAM,KAAK,OAA4C;EACrD,MAAM,QAAQ,MAAM,KAAKC,MAAM;EAC/B,IAAI,MAAM,SAAS,SAAS,OAAO,MAAM,MAAM;EAC/C,IAAI,QAAQ,KAAK,SAAS,MAAM,QAAQ,SAAS,GAAG,OAAO,KAAA;EAC3D,OAAO,KAAKC,WAAW,MAAM,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,EAAE;CACvE;CAEA,MAAM,aAA8B;EAElC,QAAO,MADa,KAAKD,MAAM,GAClB;CACf;CAEA,MAAM,YAA6B;EACjC,MAAM,QAAQ,MAAM,KAAKA,MAAM;EAC/B,OAAO,MAAM,SAAS,UAAU,MAAM,MAAM,SAAS,MAAM,QAAQ,SAAS;CAC9E;;;;;;;;;;CAWA,MAAM,UAA2B;EAC/B,MAAM,QAAQ,MAAM,KAAKA,MAAM;EAC/B,IAAI,MAAM,SAAS,SAAS,OAAO,MAAM;EACzC,MAAM,SAAS,MAAM,KAAKH,MAAM,UAAU,KAAKC,IAAI;EACnD,MAAM,SAAuB,CAAC;EAC9B,IAAI,QAAQ;EACZ,WAAW,MAAM,SAAS,QAA8C;GAEtE,MAAM,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;GACvE,OAAO,KAAK,IAAI;GAChB,SAAS,KAAK;EAChB;EACA,MAAM,SAAS,IAAI,WAAW,KAAK;EACnC,IAAI,SAAS;EACb,KAAK,MAAM,QAAQ,QAAQ;GACzB,OAAO,IAAI,MAAM,MAAM;GACvB,UAAU,KAAK;EACjB;EACA,OAAO,IAAI,YAAY,EAAE,OAAO,MAAM;CACxC;CAEA,WAA6B;EAG3B,OAAO;GACL,KAAK;GACL,SAAS;IAAE,KAAK,KAAKA;IAAM,sBAAsB,KAAKC;GAAW;EACnE;CACF;;;;;CAMA,QAA8B;EAC5B,IAAI,CAAC,KAAKG,QAAQ,KAAKA,SAAS,KAAKC,MAAM;EAC3C,OAAO,KAAKD;CACd;CAEA,MAAMC,QAA8B;EAElC,MAAM,SAAQ,MADK,KAAKN,MAAM,YAAY,KAAKC,IAAI,GAChC;EACnB,IAAI,CAAC,0BAA0B,KAAK,GAGlC,MAAM,IAAI,MACR,kEAAkE,OAAO,KAAK,EAAE,aAAa,KAAKA,KAAK,EACzG;EAEF,IAAI,QAAQ,KAAKC,YAAY;GAE3B,MAAM,UAAU,MAAM,KAAKF,MAAM,IAAI,KAAKC,IAAI;GAE9C,OAAO;IAAE,MAAM;IAAS,OADV,YAAY,KAAK,CAAC,IAAI,QAAQ,MAAM,IAAI;IACvB;IAAO;GAAQ;EAChD;EAEA,OAAO,KAAKM,qBAAqB,KAAK;CACxC;CAEA,MAAMA,qBAAqB,OAAwC;EAEjE,IAAI,UAAU,GAAG,OAAO;GAAE,MAAM;GAAa,SAAS,CAAC,CAAC;GAAG;EAAM;EAEjE,MAAM,SAAS,MAAM,KAAKP,MAAM,UAAU,KAAKC,IAAI;EAKnD,MAAM,UAAoB,CAAC,CAAC;EAC5B,IAAI,WAAW;EACf,IAAI,WAAW;EACf,WAAW,MAAM,SAAS,QAA8C;GAEtE,MAAM,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;GACvE,KAAK,MAAM,QAAQ,MAAM;IACvB;IACA,IAAI,SAAS,IAAI,QAAQ,KAAK,QAAQ;IACtC,WAAW;GACb;EACF;EAIA,IAAI,aAAa,IAAI,QAAQ,KAAK,QAAQ;OACrC,IAAI,QAAQ,QAAQ,SAAS,OAAO,UAAU,QAAQ,KAAK,QAAQ;EACxE,OAAO;GAAE,MAAM;GAAa;GAAS;EAAM;CAC7C;;;;;;;;;;;CAYA,MAAMG,WAAW,OAAe,KAA8B;EAC5D,IAAI,UAAU,KAAK,OAAO;EAC1B,MAAM,SAAS,MAAM,KAAKJ,MAAM,UAAU,KAAKC,IAAI;EACnD,MAAM,MAAgB,CAAC;EACvB,IAAI,WAAW;EACf,WAAW,MAAM,SAAS,QAA8C;GAEtE,MAAM,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;GAEvE,IAAI,YAAY,KAAK;IAInB,IAAI,kBAAkB,UAAU,OAAO,QAAQ;IAC/C;GACF;GAEA,IAAI,WAAW,KAAK,UAAU,OAAO;IACnC,YAAY,KAAK;IACjB;GACF;GAEA,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,QAAQ;GAC/C,MAAM,WAAW,KAAK,IAAI,KAAK,QAAQ,MAAM,QAAQ;GACrD,KAAK,IAAI,IAAI,YAAY,IAAI,UAAU,KAAK,IAAI,KAAK,KAAK,EAAE;GAC5D,YAAY,KAAK;EACnB;EAIA,IAAI,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,OAAO,IAAI,IAAI,IAAI;EAC1D,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,GAAG,CAAC;CACrD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,qBAAb,MAAsD;CACpD;CACA;CACA;CAEA,YAAY,MAAY,OAAkC,CAAC,GAAG;EAC5D,KAAKD,QAAQ;EACb,KAAKQ,UAAU,KAAK,aAAa;EACjC,KAAKC,oBAAoB,KAAK,wBAAwB;CACxD;;;;;;;;;;;;;;;CAgBA,MAAM,MACJ,QACA,OACA,MAC8B;EAC9B,MAAM,MAAM,KAAKD,UAAU;EAC3B,IAAI,aAAa,OAAO,kBAAkB,cAAc,GACtD,MAAM,KAAKR,MAAM,UACf,KACA,SAAS,QAAQ,KAA+C,CAClE;OAEA,MAAM,KAAKA,MAAM,IAAI,KAAK,KAAK;EAEjC,OAAO,IAAI,oBAAoB,KAAKA,OAAO,KAAK,EAC9C,sBAAsB,MAAM,wBAAwB,KAAKS,kBAC3D,CAAC;CACH;;;;;;;;;;;;;CAcA,MAAM,KACJ,QACA,MAC0C;EAC1C,MAAM,MAAM,KAAKD,UAAU;EAC3B,IAAI,CAAE,MAAM,KAAKR,MAAM,OAAO,GAAG,GAAI,OAAO,KAAA;EAC5C,OAAO,IAAI,oBAAoB,KAAKA,OAAO,KAAK,EAC9C,sBAAsB,MAAM,wBAAwB,KAAKS,kBAC3D,CAAC;CACH;;;;;;;CAQA,MAAM,OAAO,QAAkC;EAC7C,MAAM,MAAM,KAAKD,UAAU;EAE3B,IAAI,CAAC,MADiB,KAAKR,MAAM,OAAO,GAAG,GAC7B,OAAO;EACrB,MAAM,KAAKA,MAAM,OAAO,GAAG;EAC3B,OAAO;CACT;;;;;;;CAQA,OAAO,QAAwB;EAC7B,OAAO,KAAKQ,UAAU;CACxB;AACF"}