UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

100 lines • 4.08 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { BytePipe } from "../../io/pipe.js"; import { ebadf } from "../../errors/fs.js"; import { abortable, makeAbortError } from "../../utils/abort.js"; import { concat } from "../../utils/bytes.js"; /** * One directional virtual descriptor, with bounded producer backpressure. * * The read end belongs to the scope that opened it, which closes it, and the * reader streams still open (`readers`, held weakly), when its command ends; * no reader closes it, so a function can read one substitution twice. Readers * take turns a chunk at a time, so they split the stream, and a ranged read * leaves the rest of its chunk to the next. A reader whose signal fires while * it waits leaves that chunk to the next reader. Mirrors Python's DevicePipe. */ export class DevicePipe { readable; pipe = new BytePipe(); readers = new Set(); source; pull; pending = new Uint8Array(); turn = Promise.resolve(); constructor(readable) { this.readable = readable; } async write(data, path, signal) { if (this.readable) throw ebadf(path); await this.pipe.write(data, signal); } async read(path, size = null, signal) { if (!this.readable) throw ebadf(path); const chunks = []; let remaining = size; while (remaining === null || remaining > 0) { const chunk = await this.take(remaining, signal); if (chunk.byteLength === 0) break; chunks.push(chunk); if (remaining !== null) remaining -= chunk.byteLength; } return concat(chunks); } /** `signal` is the reader's own: once it fires, the reader takes nothing more. */ async *stream(path, signal) { if (!this.readable) throw ebadf(path); for (;;) { const chunk = await this.take(null, signal); if (chunk.byteLength === 0) return; yield chunk; } } /** One reader's turn: the next bytes of the stream, at most `size`, empty at its end. */ take(size, signal) { const taken = this.turn.then(() => this.next(size, signal)); this.turn = taken.then(() => undefined, () => undefined); return taken; } async next(size, signal) { if (signal?.aborted === true) throw makeAbortError(signal); if (this.pending.byteLength === 0) { this.source ??= this.pipe.stream()[Symbol.asyncIterator](); // A pull outlives a reader that gives up while it waits: what it // brings is left for the next reader, as Python's shielded pull is. const pull = (this.pull ??= this.source.next()); const step = await abortable(pull, signal).catch((error) => { if (signal?.aborted !== true) this.pull = undefined; throw error; }); this.pull = undefined; if (step.done === true) return new Uint8Array(); this.pending = step.value; } const count = size === null ? this.pending.byteLength : Math.min(size, this.pending.byteLength); const taken = this.pending.subarray(0, count); this.pending = this.pending.subarray(count); return taken; } } //# sourceMappingURL=pipe.js.map