UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

114 lines • 5.63 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 { PythonRuntime } from "../base.js"; import { memoryLimit } from "../../config.js"; import { RuntimeFiles } from "../../files.js"; import { unhonoredNotice } from "../flags.js"; import { MontyExecution } from "./execution.js"; /** * Run Python code on the Monty sandboxed interpreter (`@pydantic/monty`). * * Code executes in a crash-isolated Monty worker: no host filesystem, * environment, or network access. `pathlib` I/O is serviced through the * workspace bridge, so the code sees the workspace mounts and nothing * else, and the run's env is readable both ways python's monty spells * it: `os.getenv` and `os.environ` (a dict copy, so the two hosts run * the same program). Command-line arguments are exposed as the * `argv` global (`argv[0]` is the script name) and piped input as the * `stdin` global (bytes, None when nothing was piped). The builtin * `open()` is bridged (@pydantic/monty 0.0.21 carries a * `MontyFileHandle` back from the `os` callback), and a path no mount * serves is refused, as on the python host: the guest sees the * workspace and nothing else. `Path.stat()` is bridged too, as a class instance carrying * CPython's `stat_result` fields; only its sequence half (`st[6]`, * `len(st)`) is the python host's alone. Monty implements a Python * subset; host-only features (`sys.stdin`, `sys.argv`, third-party * imports) are unavailable; supported stdlib modules are listed under * "Python subset" in docs/home/setup/runtime/monty.mdx. The parser * refuses class inheritance, method decorators and `yield` — use the * pyodide runtime for a program that needs those. * * `externalLookup` is the host functions the code may call by name, empty * unless the one who built the runtime hands it some, as a policy does for * its own programs. `shared` runs it on the process's shared worker pool; a * policy's own runtime keeps a pool of its own, since its programs judge the * reads runs on the shared pool make and must not wait behind them. */ export class MontyRuntime extends PythonRuntime { name = 'monty'; versionSuffix = ' (monty)'; // The interpreter is an in-process guest with no host syscalls: its // file I/O can only travel the VFS bridge, so every effect passes // the workspace gate (mount modes, policy, recording). reach = 'workspace'; filesystem = ['read', 'write', 'list', 'stat']; // No import system to resolve a module with, so `-m` has nothing to // run; the refusal names this runtime rather than inventing a // "No module named" that would imply a search happened. runsModules = false; externalLookup = {}; shared = true; maxMemory; constructor(options = {}) { super(options, ['maxMemory']); this.maxMemory = memoryLimit(this.config.maxMemory); } openEngine() { return Promise.resolve(new MontyExecution(this.maxMemory, this.shared)); } closeEngine(engine) { return engine.close(); } /** * Run one program, reporting any switch this engine cannot honor. * * Monty implements a Python subset with no `compile`, no `warnings` * and no `sys.path`, so the interpreter-init switches have nothing to * act on here even though every real-CPython engine honors them. The * notice rides on stderr, ahead of what the program writes there, and * the program's own exit code stands. * * @param engine the install's worker pool. * @param program the source and argv to run. * @param ctx the run's context. * @param output receives stdout as it is printed. * @param errors receives stderr as it is printed. */ async interpret(engine, program, ctx, output, errors) { let notice = unhonoredNotice(ctx.flags, this.name); if (notice.length > 0 && errors !== undefined) { await errors(notice); notice = new Uint8Array(); } // A nested call must not queue behind a parent holding this pool. const execution = (ctx.processes?.depth ?? 0) > 0 ? new MontyExecution(this.maxMemory) : engine; // Every file call is the guest's own: none is left to land at the end. const files = RuntimeFiles.of(ctx, this.binding); const live = ctx.signal === undefined ? files : (files?.withSignal(ctx.signal) ?? null); const result = await execution .run(program, ctx, live, output, errors, this.externalLookup) .finally(async () => { if (execution !== engine) await execution.close(); }); if (notice.length === 0) return result; const stderr = result.stderr ?? new Uint8Array(); const merged = new Uint8Array(notice.length + stderr.length); merged.set(notice, 0); merged.set(stderr, notice.length); return { ...result, stderr: merged }; } } //# sourceMappingURL=runtime.js.map