@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
367 lines • 15.4 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 { enotsup, staleWrite } from "../errors/fs.js";
import { markLost } from "../observe/context.js";
import { currentCaller, liveCallers, runWithCaller } from "../context/session_context.js";
import { keyPath, underPath } from "../utils/key_prefix.js";
import { rstripSlash } from "../utils/slash.js";
import { ANY_WRITE, ABSENT_WRITE, OwnRead, } from "./types.js";
/**
* Run `fn` with `manager` active for the current async context.
* Mirrors `runWithRevisions`: the mount entry point wraps command
* dispatch.
*/
export function runWithCacheManager(manager, fn) {
return runWithCaller({ ...currentCaller(), cache: { manager } }, fn);
}
/**
* Return the active cache manager for the current async context.
*
* Serves the read-through paths, so a wrong manager is worse than
* none: a warm hit from another mount's cache is another mount's
* bytes, where a miss just reads the backend. On an isolating runtime
* one binding is live and answers as bound; on the fallback storage
* the manager answers only while every live frame agrees on it, and a
* disagreement (overlapping commands on different mounts) reads as no
* manager, failing toward the cold read.
*/
export function activeCacheManager() {
const states = liveCallers().flatMap((caller) => (caller.cache === null ? [] : [caller.cache]));
const first = states[0];
if (first === undefined)
return null;
for (const state of states) {
if (state.manager !== first.manager)
return null;
}
return first.manager;
}
/**
* Run `op`, then `settle`, also when `op` fails.
*
* An op that fails partway (a paginated delete, a folder copy that merged
* some children) has already changed the backend, so its record is owed
* either way. `settle` gets the op's result, or undefined when the op
* failed. After a failed op a settle error is reported, not thrown, so the
* caller still learns why the op failed. Failure records invalidate shared
* caches but preserve session versions for untouched files. Mirrors Python's `settle_after`.
*
* Args:
* op: the backend change.
* settle: records what the op changed, given its result.
*/
export async function settleAfter(op, settle) {
let result;
try {
result = await op();
}
catch (error) {
await runWithCaller({ ...currentCaller(), versionReads: false }, () => settle(undefined)).catch((settleError) => {
console.warn(`settling after a failed op: ${String(settleError)}`);
});
throw error;
}
await settle(result);
return result;
}
/** Collect tokens belonging to the exact bytes returned by a fetch. */
export async function captureRead(path, fetch) {
const facts = new WeakMap();
return runWithCaller({ ...currentCaller(), readFacts: { path, facts } }, async () => {
const data = await fetch();
return [data, data instanceof Uint8Array ? (facts.get(data) ?? []) : []];
});
}
/** Publish a backend-verified token without activating observation. */
export function publishRead(path, data, fingerprint) {
// Fallback contexts overlap; exact result identity makes broadcast safe.
for (const capture of liveCallers().flatMap((caller) => caller.readFacts === null ? [] : [caller.readFacts])) {
if (capture.path !== path)
continue;
const tokens = capture.facts.get(data) ?? [];
tokens.push(fingerprint);
capture.facts.set(data, tokens);
}
}
/** Run `fn` with `context` bound as the write context of the mount at `prefix`. */
export function runWithWriteContext(prefix, context, fn) {
return runWithCaller({ ...currentCaller(), write: { prefix, context } }, fn);
}
/**
* The write context of the current async context. On the fallback storage
* overlapping lines on different mounts hold frames at once; the mount
* that owns the path answers, the longest prefix covering it as the mount
* table routes. Frames there that still disagree, or none covering the
* path, cannot read as "none", which would send a conditional mount's write
* unconditioned, so the write is refused instead.
*/
function activeWriteContext(path) {
const states = liveCallers().flatMap((caller) => (caller.write === null ? [] : [caller.write]));
if (agree(states, (state) => state.context))
return states[0]?.context ?? null;
const owners = ownersOf(path, states);
if (owners.length === 0 || !agree(owners, (state) => state.context))
throw overlapping(path);
return owners[0]?.context ?? null;
}
/** The live frames of the longest prefix covering `path`, as the mount table routes. */
function ownersOf(path, states) {
let best = -1;
let owners = [];
for (const state of states) {
if (!underPath(path.virtual, state.prefix))
continue;
const length = rstripSlash(state.prefix).length;
if (length > best)
[best, owners] = [length, [state]];
else if (length === best)
owners.push(state);
}
return owners;
}
/** Whether every state picks the same value. */
function agree(states, pick) {
return states.every((state, i) => i === 0 || pick(state) === pick(states[0]));
}
function overlapping(path) {
return enotsup('workspace', 'conditional write (overlapping lines)', path);
}
export function conditionVersion(condition) {
return condition?.kind === 'match' ? condition.version : null;
}
/**
* What a write's condition asked for: a version, ABSENT for a create. Mirrors
* Python's `condition_basis`.
*/
export function conditionBasis(condition) {
return condition?.kind === 'absent' ? OwnRead.ABSENT : conditionVersion(condition);
}
export function requireSupported(support, vfs, path, kind, condition) {
if (condition.kind !== 'any' && !(support[kind] ?? []).includes(condition.kind)) {
throw enotsup(vfs, `conditional ${kind} (${condition.kind})`, path);
}
}
/**
* Run a read and return its bytes with the token they carried: the one its
* backend published for these exact bytes, null when it published none or
* two that disagree. Mirrors Python's `read_versioned`.
*/
export async function readVersioned(path, fetch) {
const [data, facts] = await captureRead(path.virtual, fetch);
const first = facts[0];
const token = first !== undefined && facts.every((f) => f === first) ? first : null;
return [data, token];
}
export async function writeCondition(path, kind, own = null) {
const context = activeWriteContext(path);
if (context === null)
return kind === 'put' ? (own === OwnRead.ABSENT ? ABSENT_WRITE : ANY_WRITE) : null;
return settle(context, path, kind, own, await context.readVersion(path));
}
/**
* The condition a delete of `path` carries, null when unconditional: a
* delete needs no read of its own, only that nobody wrote since, so the
* version the mount holds, else the one its own lookup found. Mirrors
* Python's `delete_condition`.
*
* @throws a stale-write error when the session found no file there, and one
* has appeared since
* @throws an ENOTSUP error when the backend cannot condition a delete
*/
export async function deleteCondition(path, lookedUp) {
const context = activeWriteContext(path);
if (context === null)
return null;
let cached = await context.readVersion(path);
if (cached === OwnRead.ABSENT) {
if (lookedUp !== null && lookedUp !== '')
throw await stale(path);
cached = null;
}
const version = cached !== null && cached !== '' ? cached : lookedUp;
return condition(context, path, 'delete', version !== '' ? version : null);
}
/**
* The conditions a move carries: the copy onto `dst` and the source's pin,
* both versions from one store lookup. Null and null when unconditional.
* Mirrors Python's `move_condition`.
*
* @throws a stale-write error when the op found removed a file the mount saw
* @throws an ENOTSUP error when the backend cannot condition the move
*/
export async function moveCondition(src, dst) {
const context = activeWriteContext(dst);
if (context === null)
return [null, null];
const [dstCached = null, srcCached = null] = await context.readVersions([dst, src]);
const cond = await settle(context, dst, 'copy', null, dstCached);
const source = await settle(context, src, 'delete', null, srcCached);
return [cond, conditionVersion(source)];
}
async function settle(context, path, kind, read, cached) {
if (read === OwnRead.UNKNOWN)
return condition(context, path, kind, null);
// The op's own read found no file: one the mount saw was removed since.
if (read === OwnRead.ABSENT && typeof cached === 'string' && cached !== '') {
throw await stale(path);
}
if (read === OwnRead.ABSENT && kind === 'put')
return ABSENT_WRITE;
const mine = read === OwnRead.ABSENT ? null : read;
const ownToken = mine === '' ? null : mine;
// The session last found no file here.
if (cached === OwnRead.ABSENT) {
if (ownToken === null && (context.creates ?? []).includes(kind))
return ABSENT_WRITE;
return condition(context, path, kind, ownToken);
}
const cachedToken = cached === '' ? null : cached;
return condition(context, path, kind, ownToken ?? cachedToken);
}
function condition(context, path, kind, version) {
// A put with no version goes out plain, so it needs no condition.
if (kind !== 'put' || version !== null)
require(context, path, kind);
return version !== null ? { kind: 'match', version } : ANY_WRITE;
}
function require(context, path, kind) {
if (!context.conditions.includes(kind))
throw enotsup(context.vfs, `conditional ${kind}`, path);
}
/**
* The native token a write sends in place of the held content token, null when
* the write goes plain. Mirrors Python's `native_condition`.
*/
export async function nativeCondition(path, cond, live, kind) {
if (cond?.kind === 'absent')
throw enotsup(activeWriteContext(path)?.vfs ?? 'native', `conditional ${kind} (absent)`, path);
const held = conditionVersion(cond);
if (held === null || held === '')
return null;
if (live?.content !== held)
throw await stale(path, { version: held });
if (live.native === null || live.native === '') {
throw enotsup(activeWriteContext(path)?.vfs ?? 'native', `conditional ${kind}`, path);
}
return live.native;
}
/**
* Keep the version of every file a walk left behind; refuse the first. Mirrors
* Python's `keep_refused`.
*/
export async function keepRefused(lost) {
const refusals = [];
for (const [spec, version] of lost)
refusals.push(await stale(spec, { version }));
return refusals[0] ?? null;
}
/**
* The version the mount holds for each path, in one store round trip. Mirrors
* Python's `held_versions`.
*/
export async function heldVersions(paths) {
const first = paths[0];
const context = first === undefined ? null : activeWriteContext(first);
if (context === null)
return paths.map(() => null);
return (await context.readVersions(paths)).map((token) => typeof token === 'string' ? token : null);
}
/**
* Whether the mount that owns `path` is a `write: conditional` one; true while
* overlapping lines leave the owner unclear. Mirrors Python's
* `writes_conditioned`.
*/
export function writesConditioned(path) {
const states = liveCallers().flatMap((caller) => (caller.write === null ? [] : [caller.write]));
if (agree(states, (state) => state.context))
return (states[0]?.context ?? null) !== null;
const owners = ownersOf(path, states);
return owners.length === 0 || owners.some((state) => state.context !== null);
}
/**
* Whether a `kind` on `path` goes out conditioned, for a prefix walk, which
* conditions each key itself and needs no version for the operand. Mirrors
* Python's `conditioned`.
*
* @throws an ENOTSUP error when the backend cannot condition this op
*/
export function conditioned(path, kind) {
const context = activeWriteContext(path);
if (context === null)
return false;
require(context, path, kind);
return true;
}
/**
* Drop the write context's cached copy of `path`, if there is one. `keep` is
* the version to keep without bytes, ABSENT for a refused create.
*/
export async function dropCached(path, keep = null) {
markLost(path, typeof keep === 'string' ? keep : null);
const context = activeWriteContext(path);
if (context === null)
return;
await context.drop(path);
if (keep !== null && keep !== '')
await context.keep(path, keep);
}
/**
* Drop `path`'s cached copy, keeping what a refused write sent. The condition
* stays, so a retry without a read sends it again and is refused again. That
* holds for a file found gone too: a peer may create it again before the
* retry. `version` is the version the write sent, ABSENT for a create; null
* keeps what the session last saw. Mirrors Python's `keep_sent`.
*/
export async function keepSent(path, version = null) {
const context = activeWriteContext(path);
let keep = version;
if (keep === null && context !== null)
keep = await context.readVersion(path);
await dropCached(path, keep);
}
/**
* The refusal for a lost condition, after `keepSent`. `landed` marks a move
* whose copy landed before its source's delete lost, which `mv` reports as a
* failed removal; `version` is the one the write sent, ABSENT for a create,
* when the line no longer names it (a move retracts both paths). Mirrors
* Python's `stale`.
*/
export async function stale(path, options = {}) {
const { landed = false, version = null } = options;
await keepSent(path, version);
return staleWrite(path, landed);
}
/**
* The versions a prefix walk under `root` measures its keys against: a key
* the agent read is held to the version it read, and the walk's own listing
* only stands in for keys it never saw. Mirrors Python's `known_versions`.
*/
export function knownVersions(root, keyPrefix) {
return async (keys) => {
const known = new Map();
if (keys.length === 0)
return known;
const context = activeWriteContext(root);
if (context === null)
return known;
const tokens = await context.readVersions(keys.map((key) => keyPath(root, keyPrefix, key)));
keys.forEach((key, i) => {
const token = tokens[i];
if (typeof token === 'string' && token !== '')
known.set(key, token);
});
return known;
};
}
//# sourceMappingURL=context.js.map