@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
96 lines • 3.92 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 { classify } from "../../../errors/index.js";
import { isAborting } from "../../../errors/fs.js";
import { POSIX } from "../../../errors/posix.js";
export class MontyUnavailableError extends Error {
constructor(message, options) {
super(message, options);
this.name = 'MontyUnavailableError';
}
}
// The builtin a guest `except`s for a condition, where CPython raises a
// subclass for its errno; every other condition is a plain OSError.
const EXCEPTIONS = {
ENOENT: 'FileNotFoundError',
ENOTDIR: 'NotADirectoryError',
EISDIR: 'IsADirectoryError',
EEXIST: 'FileExistsError',
EACCES: 'PermissionError',
EPERM: 'PermissionError',
};
/**
* The guest-python rendering for a condition: CPython on Linux, whose
* number and phrase are the shared POSIX table's (that table is Linux's
* numbering already).
*/
export function cpythonError(condition) {
return { exception: EXCEPTIONS[condition] ?? 'OSError', ...POSIX[condition] };
}
/** The traceback monty renders for one of its own errors. */
export function displayError(err) {
const e = err;
if (typeof e.display === 'function')
return e.display('traceback');
return e instanceof Error ? e.message : String(err);
}
/**
* Build the guest-side exception for one condition, in CPython's
* message shape.
*
* Args:
* code: the condition, e.g. ENOENT.
* path: the path the operation names.
* target: rename's destination, when there is one.
*/
export function guestError(code, path, target) {
const row = cpythonError(code);
const where = target === undefined ? `'${path}'` : `'${path}' -> '${target}'`;
const guest = Object.assign(new Error(`[Errno ${String(row.errno)}] ${row.phrase}: ${where}`), {
guestCondition: code,
});
guest.name = row.exception;
return guest;
}
/**
* Re-throw a mount failure under its python exception name: the monty
* engine raises `err.name` as the matching guest exception type
* (PYTHON_EXC_NAMES), so agent code can `except FileNotFoundError`
* exactly as it does on the python host. Every named condition
* converts (a non-empty rmdir is an OSError with errno 39, not a raw
* JS error), and a failure the vocabulary does not name is EIO, as a
* kernel reports a device that failed (the engine knows only builtin
* types, so a backend's own error would reach the guest as
* RuntimeError). A stopped run's refusal is no failed device: it leaves
* as itself, so a callback making call after call (a scan) ends with
* its run.
*
* Args:
* err: whatever the mount op rejected with.
* path: the path the operation names.
* target: a rename's destination, which CPython prints after the
* source.
*/
export function asGuestError(err, path, target) {
// A guest exception this adapter already built (a refusal before any
// mount op) is CPython's shape already, and keeps its own condition. A
// backend error that merely shares a CPython name is classified like
// any other.
if (err.guestCondition !== undefined)
return err;
if (isAborting(err))
return err;
return guestError(classify(err) ?? 'EIO', path, target);
}
//# sourceMappingURL=errors.js.map