@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
114 lines • 5.63 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 { 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