@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
101 lines • 3.77 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 {} from "../../io/types.js";
import { RAMIndexCacheStore } from "../../cache/index/ram.js";
import { RAMAccessor } from "../../accessor/ram.js";
import { FileStat, FileType } from "../../types.js";
import { RAMVFS } from "../ram/ram.js";
import { activePipe } from "../../core/dev/device.js";
import { keyStat } from "../../core/dev/stat.js";
import { childKeys } from "../../core/ram/readdir.js";
import { gnuBasename, norm } from "../../utils/path.js";
import { DevStore } from "./store.js";
/**
* The index every DevVFS runs under: it never keeps an entry,
* because a path-only index would publish one session's descriptors to
* another. Mirrors the `NULL_INDEX` Python's registry uses for DevVFS.
*/
export class DevIndex extends RAMIndexCacheStore {
seed() {
return undefined;
}
put() {
return Promise.resolve();
}
setDir() {
return Promise.resolve([]);
}
refillDir() {
return Promise.resolve([]);
}
setPartialDir() {
return Promise.resolve();
}
addEntry() {
return Promise.resolve();
}
}
export class DevVFS extends RAMVFS {
devices;
store;
accessor;
constructor(devices = new DevStore()) {
super();
this.devices = devices;
this.store = devices;
this.accessor = new RAMAccessor(this.store);
}
forSession(owner) {
return new DevVFS(this.devices.forSession(owner));
}
allocateInput(pipe) {
return this.store.files.allocateInput(pipe);
}
releaseInput(path, allocation) {
const files = this.store.files;
if (files.releaseInput(path, allocation)) {
this.store.modified.delete(path.slice(4));
this.store.attrs.delete(path.slice(4));
}
}
/**
* The RAM listing, each child as `stat` answers it, so a device or a live
* descriptor lists as one. Mirrors Python's `readdir`.
*/
async readdir(path) {
return (await childKeys(this.accessor, path)).map((key) => keyStat(this.accessor, key) ??
new FileStat({ name: gnuBasename(key), type: FileType.DIRECTORY }));
}
/** Null: a walk of /dev is one readdir. Mirrors Python's `read_tree`. */
readTree(_path) {
return Promise.resolve(null);
}
/**
* A whole read of a pipe answers its stream. The stream is finite: a
* command that consumes a whole input reads the refusing read, and only
* the two bounded streaming commands opt into the endless source
* (`commands/builtin/dev`).
*/
read(path, handed, offset = 0, size = null, signal) {
// Not async, so a pipe is bound as the read opens and a descriptor reads
// on after the pipe's path is released.
if (offset === 0 && size === null) {
const pipe = activePipe(this.accessor, norm(path.mountPath));
if (pipe !== null)
return pipe.stream(path, signal);
}
return super.read(path, handed, offset, size, signal);
}
}
//# sourceMappingURL=dev.js.map