UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

110 lines • 4.65 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 { 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