@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
105 lines • 4.27 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 { DIR_SIZE, S_IFDIR, S_IFMT, S_IFREG } from "../../../utils/stat_view.js";
const PERMISSION_BITS = 0o7777;
// What monty's own StatResult reports for a directory (DIR_SIZE, shared,
// and two links), so a guest sees the same two numbers whichever host
// answered.
const DIR_LINKS = 2;
const FILE_LINKS = 1;
/**
* The row's type bits, the one rule every answer derived from a mode
* reads.
*
* monty's own `StatResult` keeps whatever type bits the mode carries
* and ORs in a default only when it carries none, so a backend
* reporting a character device (`/dev/null`) or a symlink stays one
* and a backend reporting permissions alone still yields a mode
* `S_ISDIR` can mask. Deriving the type from `isDir` alone reported
* every such row regular.
*
* Args:
* st: the mount's row for the path.
*/
function fileType(st) {
const declared = st.mode & S_IFMT;
return declared !== 0 ? declared : st.isDir ? S_IFDIR : S_IFREG;
}
/** Whether the row is a regular file: python's `S_ISREG` of its mode. */
export function isRegularRow(st) {
return fileType(st) === S_IFREG;
}
/** Whether the row is a directory: python's `S_ISDIR` of its mode. */
export function isDirRow(st) {
return fileType(st) === S_IFDIR;
}
/**
* One mount row as the guest's stat fields.
*
* The numbers are monty's own `StatResult.file_stat` / `dir_stat`
* rules, replicated rather than imported because the JS package
* exports no `StatResult`: the type bits come from the row, the
* permission bits from its mode, and a directory reports 4096 bytes
* and two links whatever the backend said. Keeping the two hosts on
* one rule is the whole point — `python/mirage/runtime/python/monty/
* stat.py` reaches the same numbers by calling the constructors.
*
* Args:
* st: the mount's row for the path.
*/
/**
* The fields of the guest's `os.stat_result`, spelled the way CPython
* spells them because the guest reads them by those names.
*/
export function statFields(st) {
const type = fileType(st);
// Seconds, as CPython reports them; an unknown stamp is 0 rather
// than the host clock.
const stamp = (st.mtimeMs ?? 0) / 1000;
return {
st_mode: type | (st.mode & PERMISSION_BITS),
st_ino: 0,
st_dev: 0,
st_nlink: st.isDir ? DIR_LINKS : FILE_LINKS,
st_uid: 0,
st_gid: 0,
st_size: st.isDir ? DIR_SIZE : st.size,
st_atime: stamp,
st_mtime: stamp,
st_ctime: stamp,
};
}
/**
* The guest-side `os.stat_result` for one row.
*
* Wrapped in the engine's `ClassInstance` rather than returned bare:
* a plain object converts structurally and arrives as a dict, so
* `st.st_size` raised AttributeError. The wrapper (new in
* @pydantic/monty 0.0.22) sends the object as a class instance with
* its own name, so attribute access and `repr` both read as CPython's.
* The sequence half of a real `stat_result` does not cross: the guest
* cannot subscript, iterate or take `len` of the answer, because the
* JS input encoder cannot construct a NamedTuple node. The protocol
* supports it, and Python sends a real `StatResult`; the JS engine
* cannot send that value back. This also prevents `os.chdir()`, which
* requires a native NamedTuple stat result to validate the directory.
*
* Args:
* bits: the loaded engine's pieces.
* st: the mount's row for the path.
*/
export function statResult(bits, st) {
return new bits.ClassInstance(statFields(st), { name: 'stat_result', eagerAttrs: 'all' });
}
//# sourceMappingURL=stat.js.map