UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

137 lines • 5.69 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 { OutputStream, closeQuietly, ensureStream } from "../../../io/stream.js"; import { closeStream, streamOutputChunks } from "../../../rs/stream.js"; import { materialize } from "../../../io/types.js"; import { isEisdir } from "../../../errors/fs.js"; import { FileStat, FileType } from "../../../types.js"; import { encodeText } from "../../../shell/bytes.js"; import { STDIN_HEADER_NAME } from "./constants.js"; import { mergeSignals } from "../../../utils/abort.js"; export async function readStdinAsync(stdin) { return stdin === null ? null : materialize(stdin); } export function resolveSource(stdin) { // GNU semantics: no stdin behaves like empty input (/dev/null) return ensureStream(stdin ?? new Uint8Array(0)); } /** * Whether an operand reads stdin. `dash` says a literal `-` names stdin, as * it does for most GNU tools; util-linux `rev` and binutils `strings` open it * as a file, so only /dev/stdin is stdin to them. */ export function isStdin(path, dash = true) { return (dash && path.rawPath === '-') || path.virtual === '/dev/stdin'; } /** * Whether a failed read means the operand never opened: nothing arrived from * it, and it is neither stdin, which is always open, nor a directory, which * opens and then fails to read. GNU names such an operand as it does a * missing one, not as a read error. Mirrors Python's never_opened. */ export function neverOpened(reader, path, error) { return reader.loaded === 0 && !isStdin(path) && !isEisdir(error); } /** * The name a command's output gives an operand. Only a literal `-` is stdin by * name: /dev/stdin reads the same bytes, but GNU grep, head and tail name it as * the path it is. */ export function operandLabel(path, stdinName) { return path.rawPath === '-' ? stdinName : path.rawPath; } /** * The `==> name <==` banner head and tail print above an operand: null names * the stdin no operand named, which -v heads with the name `-` gets, and * `after` says a block printed before this one, which a blank line then * separates it from. Mirrors Python's operand_header. */ export function operandHeader(path, after = false) { const label = path === null ? STDIN_HEADER_NAME : operandLabel(path, STDIN_HEADER_NAME); return encodeText(`${after ? '\n' : ''}==> ${label} <==\n`); } /** * Whether head or tail banners its operands: under -v or for more than one * operand (a failed one included), never under -q. Mirrors Python's * shows_headers. */ export function showsHeaders(verbose, quiet, count) { return (verbose || count > 1) && !quiet; } /** * Read each operand from its backend, or from stdin for a stdin one. * * Every stdin operand shares one cursor, so a later `-` never replays bytes * an earlier one read, and the cursor never closes the input a later one may * still read. `sole` says stdin has exactly one reader, which takes the input * itself, so a scan that stops early closes it. `dash` is as in `isStdin`. */ export function stdinStream(read, stdin, sole = false, dash = true) { const input = resolveSource(stdin); const source = input[Symbol.asyncIterator](); async function* inputStream() { // All '-' operands share one cursor; a new operand must not replay bytes. for (;;) { const next = await source.next(); if (next.done === true) return; yield next.value; } } // Bind the backend stream while its mount cache context is active. // Byte consumption stays lazy; only stdin needs a shared cursor. return (path) => { if (!isStdin(path, dash)) return read(path); return sole ? input : inputStream(); }; } /** Stat each operand on its backend, or as a stream for a stdin one. */ export function stdinStat(stat, dash = true) { return (path) => isStdin(path, dash) ? Promise.resolve(new FileStat({ name: '-', type: FileType.FIFO })) : stat(path); } /** Drive an already validated stream through bounded calls. Mirrors Python's stream_output. */ export function streamOutput(stream, source, signal) { let started = false; const stop = new AbortController(); const stopped = mergeSignals(signal, stop.signal); async function* output() { started = true; try { yield* streamOutputChunks(stream, source, stopped); } catch (error) { if (!stop.signal.aborted || stopped?.reason !== stop.signal.reason || !(error instanceof DOMException) || error.name !== 'AbortError') throw error; } } const producer = output(); return new OutputStream(producer, async () => { stop.abort(); try { await producer.return(undefined); } finally { await closeStream(stream); if (!started) await closeQuietly(source); } }); } //# sourceMappingURL=stream.js.map