@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
110 lines • 4.65 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 { GuestExit, WasiFs, wasiImports } from "./fs.js";
import { failureText, requestOverBridge, requestSync } from "./transport.js";
// The worker a wasm guest runs on: the module runs here, blocking this
// thread on each host call as a process blocks on a syscall, while the
// host answers on its own loop. Python runs the guest the same way, on a
// worker thread whose calls wait on the event loop.
const node = globalThis.process?.versions?.node;
const port = node === undefined ? null : (await import('node:worker_threads')).parentPort;
const page = globalThis;
const post = (message) => {
if (port !== null)
port.postMessage(message);
else
page.postMessage(message);
};
let bridge;
// The bridge answers a null as nothing at all, and the calls here read a
// null as "none", so it is put back.
const call = (request) => (bridge === undefined ? requestSync(post, request) : requestOverBridge(post, request, bridge)) ??
null;
/** One open file of the host's, its calls made across the bridge. */
function guestFile(id, mode, opened, inode) {
const on = (method, ...args) => call({ op: 'file', file: id, method, args });
return {
mode,
opened,
inode,
path: () => on('path'),
position: () => on('position'),
size: () => on('size'),
read: (size) => on('read', size),
pread: (offset, size) => on('pread', offset, size),
write: (data) => on('write', data),
pwrite: (offset, data) => on('pwrite', offset, data),
seek: (offset, whence) => on('seek', offset, whence),
truncate: (size) => on('truncate', size),
land: () => on('land'),
close: () => on('close'),
fstat: () => on('fstat'),
unlinked: () => on('unlinked'),
};
}
const viewCall = (method, ...args) => call({ op: 'view', method, args });
const view = {
stat: (path) => viewCall('stat', path),
lstat: (path) => viewCall('lstat', path),
statOrNull: (path, nofollow = false) => viewCall('statOrNull', path, nofollow),
open: (path, mode) => {
const { id, opened, inode } = viewCall('open', path, mode);
return guestFile(id, mode, opened, inode);
},
unlink: (path) => viewCall('unlink', path),
mkdir: (path) => viewCall('mkdir', path),
rmdir: (path) => viewCall('rmdir', path),
rename: (src, dst) => viewCall('rename', src, dst),
symlink: (path, target) => viewCall('symlink', path, target),
walked: (path, follow = true) => viewCall('walked', path, follow),
readlink: (path) => viewCall('readlink', path),
setattr: (path, atime, mtime, nofollow) => viewCall('setattr', path, atime, mtime, nofollow),
readdir: (path) => viewCall('readdir', path),
};
async function run(request) {
bridge = request.bridge;
const wasi = new WasiFs(view, request.piped ? (size) => call({ op: 'stdin', args: [size] }) : null, (fd, bytes) => call({ op: 'output', args: [fd, bytes] }), (ms) => call({ op: 'sleep', args: [ms] }), request.argv, request.env, request.offset);
try {
const instance = await WebAssembly.instantiate(request.module, wasiImports(wasi));
wasi.memory = instance.exports.memory;
let exitCode = 0;
let trap = null;
try {
;
instance.exports._start();
}
catch (err) {
if (err instanceof GuestExit)
exitCode = err.code;
else {
exitCode = 1;
trap = `wasm trap: ${failureText(err)}`;
}
}
post({ kind: 'result', exitCode, trap, lost: wasi.closeAll() });
}
catch (err) {
post({ kind: 'error', message: failureText(err) });
}
}
if (port !== null)
port.on('message', (request) => {
void run(request);
});
else
page.onmessage = (event) => {
void run(event.data);
};
post({ kind: 'ready' });
//# sourceMappingURL=worker.js.map