@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
94 lines • 4.19 kB
JavaScript
// ========= 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 { SharedInput } from "../../../io/async_line_iterator.js";
import { IOResult } from "../../../io/types.js";
import { encodeText } from "../../../shell/bytes.js";
import { LineFacts } from "../../../types.js";
import { CommandCatalog, command, } from "../../config.js";
import { specOf } from "../../spec/builtins.js";
import { guardInput } from "../utils/limit.js";
import { BUILDERS } from "./builders/index.js";
import { withReading } from "./files.js";
function bind(b, vfs, stream) {
// The builder reads its files under the command tier's guards: what it
// reads, cut into pieces unless it passes its input through. Every call it
// makes is the dispatcher's, which judges each itself. Stdin is cut the
// same way, under the invocation's abort and deadline.
const reading = { split: b.passThrough !== true, stream };
const fn = (accessor, paths, texts, ctx) => {
if (ctx.vfs === undefined) {
return [
null,
new IOResult({ exitCode: 1, stderr: encodeText(`${b.name}: no workspace files\n`) }),
];
}
const run = {
...ctx,
// head guards its own pulls and needs the descriptor cursor to return lookahead.
stdin: ctx.stdin === null || (b.name === 'head' && ctx.stdin instanceof SharedInput)
? ctx.stdin
: guardInput(ctx.stdin, ctx, reading.split),
};
return b.fn(accessor, paths, texts, withReading(run, reading));
};
return command({
name: b.name,
vfs,
spec: specOf(b.name),
fn,
aggregate: b.aggregate ?? null,
write: b.write === true,
});
}
const BY_NAME = new Map(BUILDERS.map((b) => [b.name, b]));
/**
* Every mount falls back to these after its VFS's own commands. Each reads
* and writes through the session's files (`ctx.vfs`), so one set serves the
* whole workspace. Mirrors Python's `GENERIC_COMMANDS`.
*/
export const GENERIC_COMMANDS = new CommandCatalog(BUILDERS.flatMap((b) => bind(b, null, null)));
/**
* The default command `name`, which a mount runs when its VFS has no command
* of that name. A VFS that replaces a command hands what the replacement does
* not support to this one: `generic('grep').fn(accessor, paths, texts, ctx)`.
* With `vfs`, the same command registered for that backend. Mirrors Python's
* `generic`.
*/
export function generic(name, options = {}) {
if (!BY_NAME.has(name))
throw new Error(`no generic command named '${name}'`);
if (options.vfs === undefined)
return GENERIC_COMMANDS.require(name);
return genericReading(name, { vfs: options.vfs });
}
/**
* The generic command `name` registered for `vfs`, each operand streamed by
* `stream` where it answers one (null keeps the dispatched read): `/dev`'s
* endless `cat` and `head`. Mirrors Python's `generic_reading`.
*/
export function genericReading(name, options) {
const b = BY_NAME.get(name);
if (b === undefined)
throw new Error(`no generic command named '${name}'`);
return new CommandCatalog(bind(b, options.vfs, options.stream ?? null)).require(name);
}
/**
* What the generic command `name` declares a line of it does; a name with no
* generic command declares nothing (`new LineFacts()`). Mirrors Python's
* `declared_facts`.
*/
export function declaredFacts(name) {
return BY_NAME.get(name)?.facts ?? new LineFacts();
}
//# sourceMappingURL=factory.js.map