UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

594 lines • 29.8 kB
import type { Accessor } from '../../accessor/base.ts'; import { type IndexConfig } from '../../cache/index/config.ts'; import type { IndexCacheStore } from '../../cache/index/store.ts'; import type { CacheManager } from '../../cache/manager.ts'; import { type WriteContext } from '../../cache/types.ts'; import { CLI, type CLIHandler } from '../../commands/cli/types.ts'; import { Command, type CommandFnResult, type Context } from '../../commands/config.ts'; import { CommandSpec, type FlagValue } from '../../commands/spec/types.ts'; import type { DevicePipe } from '../../core/dev/pipe.ts'; import { type Egress } from '../../io/gateway.ts'; import { type ByteSource, IOResult } from '../../io/types.ts'; import { Limit, MountMode, PathSpec, type ReadSpec, type SearchQuery, WritePolicy } from '../../types.ts'; import { Activity } from '../../utils/activity.ts'; import { type IdSource } from '../../utils/ids.ts'; import type { BaseVFS } from '../../vfs/base.ts'; import type { OpKwargs, SessionAccess } from '../../view/types.ts'; /** * One way to answer an op on a mount: the scope keyed below the mount, the * op's positional arguments and its keywords. */ type OpCall = (scope: PathSpec, args: readonly unknown[], kwargs: OpKwargs) => unknown; export interface MountInit { prefix: string; vfs: BaseVFS; mode?: MountMode; /** How this mount's cached bytes are revalidated. */ read?: ReadSpec; /** Whether this mount's writes carry the version they were based on. */ write?: WritePolicy; index?: IndexCacheStore; indexConfig?: IndexConfig | undefined; inheritsIndex?: boolean; vfsRef?: string | null; /** Where the mount's id is drawn; omitted means the system source. */ ids?: IdSource; } export declare class MountEntry { visible: ((sessionId: string | null) => boolean) | null; readonly mountId: string; readonly prefix: string; readonly vfs: BaseVFS; readonly backendWalkLimit: number; calls: ReadonlySet<string>; mode: MountMode; readonly read: ReadSpec; readonly write: WritePolicy; readonly indexStore: IndexCacheStore; readonly indexConfig: IndexConfig | undefined; readonly inheritsIndex: boolean; readonly vfsRef: string | null; readonly activity: Activity; retiring: boolean; beforeUse: (() => Promise<void>) | null; private readonly readyLock; /** * Per-path revision pins installed at Workspace.load time. Read * functions consult these via the {@link revisionFor} contextvar * lookup; on a hit, the backend GET pins to the recorded revision so * replay serves the exact bytes the agent saw. Empty during normal * runs; populated only by the snapshot loader. */ readonly revisions: Map<string, string>; cacheManager: CacheManager | null; private writeContextFor; private readonly cmds; private readonly userKeys; private readonly generalCmds; private readonly trees; private readonly cmdSpecs; readonly commandLimits: Map<string, Limit>; /** * The functions the VFS answers itself, read when it is mounted and again * when its `walkLimit` is settled (a backend answers `files_containing` only * under a cap), plus `read_range` when a read fetches only the range asked * for. Mirrors Python's `answered_calls`. */ answeredCalls(): ReadonlySet<string>; /** The bottom layer of a command's limit on this mount: its backend's `walkLimit`. */ get defaultLimit(): Limit; private prefixIndex; constructor(init: MountInit); /** Whether this mount answers the op `name`. */ answers(name: string): boolean; /** * The VFS as the owner of `access` sees it: the device mount's view of that * session's descriptors, or the history view of its commands, which a * browser call cannot find any other way. */ private viewFor; /** The live pipe a read of `path` takes bytes from, which another task is still writing. Mirrors Python's `live_pipe`. */ livePipe(path: string, access?: SessionAccess): DevicePipe | null; /** Whether a live pipe sits at `path` or below it. Mirrors Python's `holds_live_pipe`. */ holdsLivePipe(path: string, access?: SessionAccess): boolean; /** Metadata access bound to this mount's ownership. */ get index(): IndexCacheStore; /** Finish deferred mount preparation before any backend or cache read. */ use<T>(fn: () => Promise<T>): Promise<T>; ensureReady(): Promise<void>; private checkActive; /** * This mount's mode narrowed by the owner's cap, or the live sessions' * when no owner is supplied. The configured mode is the ceiling; a * session's mode can only weaken it. */ effectiveMode(access?: SessionAccess): MountMode; register(cmd: Command): void; registerGeneral(cmd: Command): void; resolveCommand(cmdName: string, extension?: string | null): Command | null; /** * Whether a run of `cmdName` on an operand with `extension` can reach a * handler that declares it may write. Every handler the run's cascade tries * counts, the filetype one first, since one that declines hands the run to * the next. The directory probe a run makes (`pickHandlers`) is not made, * so a directory named like a file counts its filetype handler too. * `builtins` is false when the line's options leave mirage's own handlers * nothing to write (`LineFacts.writes`); a user command counts either way. * Mirrors Python's MountEntry.may_write. */ mayWrite(cmdName: string, extension?: string | null, builtins?: boolean): boolean; /** * How many leading words form a registered command name here. Command * names may span several words ("gws docs documents get"), git-style. * Returns the length of the longest registered name that is a prefix of * `words`, or 1 (the bare first token) if no multi-word name matches; 0 * for no words. */ longestCommandMatch(words: string[]): number; allCommands(): readonly Command[]; specFor(cmdName: string): CommandSpec | null; commands(): Record<string, (string | null)[]>; /** * Whether a ranged read of `path` fetches only that range. False where * the read that answers it reads the whole file and slices: a VFS with no * native range, or a rendered filetype. */ readsRanges(path: string): boolean; /** Whether the VFS declares the function `name` a write. */ /** * Refuse a write the mount's mode no longer grants at any path. Admission * judged the mode before the call waited for the mount and for its write * lock; `setMountMode` can make the mount read-only in between, so the mode * is read again as the backend call starts. A call that reaches a subtree * (a rename, a tree removal or copy) is refused by a read-only region * anywhere below its paths, and a copy only reads its source. Mirrors * Python's `require_writable`. */ requireWritable(name: string, path: PathSpec, values: readonly unknown[], access?: SessionAccess): void; /** Refuse a keyword `name` does not take, as a cold call would. Mirrors Python's `refuse_keywords`. */ refuseKeywords(name: string, kwargs: OpKwargs): void; /** * Whether the VFS renders a read of `filetype`. A rendered read is never * served from or kept in the file cache. */ renders(filetype: string | null): boolean; /** * Batch-register user commands. Mirrors Python's `Mount.register_commands(...)`. * Each one is this mount's own, keyed by name and filetype, so it wins * over the shared commands of that name, and is a user command * (`isUserCommand`): refused before it writes to a read-only one. Multi-VFS entries are filtered as * `ownCommands` says. A `CLI` serves its verbs as a command tree on this * mount, under its root's name: a line naming it walks the tree as an * installed CLI's does and runs the leaf on this mount's accessor * (`runLeaf`); a tree that declares an installation's own setting throws. */ registerCommands(items: readonly (Command | CLI)[]): void; /** * Register mirage's own commands for this mount's VFS (a builtin backend's * table), filtered as `registerCommands` filters; they are not user * commands. Mirrors Python's `Mount._register_builtins`. */ registerBuiltinCommands(items: readonly Command[]): void; /** * The entries of `items` this mount registers: multi-VFS entries (sharing * the same name across mounts) are filtered to this mount's VFS kind; if a * name has entries but none match this mount, throw. */ private ownCommands; /** * The command tree this mount serves under `cmdName`, null when the name * resolves to any other command here. Mirrors Python's * `MountEntry.cli_for`. */ cliFor(cmdName: string): CLI | null; /** * What answers `name` on this mount, in the order to try. * * A rendered filetype's renderer answers a read before `read` does, * window and all, and the first answer that is not null wins. The rest * is the dispatcher's own shape around the VFS's functions: a read takes a * window, `append` and `pwrite` are a rewrite where the VFS only writes * whole files, and `mkdir` refuses a taken name first. Only a method * marked `@vfsCall` is reachable by name, and a custom one is handed the * scope and the op's positional arguments. * Mirrors Python's `MountEntry._callers`. */ callers(name: string, filetype: string | null, vfs?: BaseVFS): OpCall[]; /** * A write or append of bytes or a stream, handed to the VFS's `write` / * `append` as it came, an append by rewrite where it has none. Every * `write` is told `atomic`; one that collects first already lands whole. * Mirrors Python's `MountEntry._write_op` / `_append_op`. */ private writeSource; private appendByRewrite; /** * A pwrite handed to the VFS's `pwrite`: bytes as they came, a stream a * window at a time at increasing offsets, so memory holds one window * whatever its length. Mirrors Python's `MountEntry._pwrite`. */ private pwriteSource; private pwriteByRewrite; private truncateByRewrite; private replaceAt; /** The VFS's read of a whole file, handed what is at its path. */ private readAt; /** * Fill `spool` with the VFS's read of a whole file, answering the version * the read carried, as `readAt` publishes it. Mirrors Python's `_read_into`. */ private readInto; /** * What `vfs`'s read of a whole file answers, called with the stats it * declares. Mirrors Python's `_answer`. */ private answer; /** The VFS's write of a whole file, handed what is at its path and the directory it goes in. */ private writeAt; private mkdir; /** * `mkdir -p` on a backend that makes a directory inside its parent's entry: * one level at a time, from the mount root down. The level above does not * exist yet when the one below needs it as its parent, so each level's * create answers its new stat and the index keeps it where the next level * looks for its parent, with its own listing, which is empty. A level that * is there already is passed, a directory or not: a file there is ENOTDIR * for the level below, as mkdir(2) finds. Mirrors Python's `_mkdir_levels`. */ private mkdirLevels; /** The index key of this mount's root, which no listing names. */ /** * Whether the VFS lists, so the mount keeps its listings as stats. Mirrors * Python's `_lists`. */ private get lists(); private get rootKey(); /** * The directory holding `scope`, keyed below this mount; a glob word stands * for the directory it lists, and the root holds itself. */ private folder; /** The stats the VFS's function `name` declares it is handed. */ private wants; /** * What is at `scope`, as its parent's listing names it, listing the parent * when no stored listing does. Under `fresh` the running command's check of * `scope` already asked the backend, so its answer stands in for the walk; * a check itself always walks. A miss right after a listing of the parent * landed, with no listing left now, says only that a check cleared the index * mid-lookup, so it is looked up once more. Mirrors Python's `_entry`. */ private entry; /** * The row the stored listings answer a stat of `key` with, null for one the * backend must answer, a miss they do not prove (`unproven`) included. * Mirrors Python's `_stored_row`. */ private storedRow; /** * Whether a complete listing of `folder` proves a name it leaves out absent: * always, but on a VFS that checks such a name again (`unproven`), only a * listing the running command fetched. Mirrors Python's `_proves`. */ private proves; /** Whether the VFS's stat asks by path, handed no stored row. */ private get asksByPath(); /** * Whether the stored listings leave `key` out without proving it absent * (`unprovenMiss`), on a VFS that checks such a name again: one whose stat * asks by path (one stat) or one that writes (its folder is listed again), * where a stale miss would let a write land over a file another client * made. Mirrors Python's `_unproven`. */ private unproven; /** Invalidate cached absences disproved by current backend answers. Mirrors Python's _observe. */ private observe; /** Observe existence once without pulling beyond the consumer's demand. */ private observeStream; /** * What is at a path its folder's stored listing leaves out without proving * it absent (a window, or a listing another command fetched): the backend's * stat of it, handed what is at its parent; null when nothing is. Mirrors * Python's `_beyond`. */ private beyond; /** * A directory's children, from its stored listing or listed through the * backend and kept. Finding the rows a listing is handed lists its parent, * whose fetch may have proved this listing too (a seed). Mirrors Python's * `_listed`. */ private listed; /** * One level of a subtree read: the directory's listing, as `readdir` * answers it, each child keyed by its name with the row its `stat` answers * from that listing. Null where a stat does not come from the listing: a * mount that keeps none, or whose index keeps no listing live (a zero ttl), * one under `read: fresh`, whose stats check the backend, a listing the * index does not hold live once listed, and a child whose row a stat would * finish (`whole`). A listing cut at the walk's limit answers for the * children it names, as their stats do. Mirrors Python's `_level`. */ private level; /** A stat of `path` on this mount, as `callKeyed` answers one. Mirrors Python's `_stat`. */ private statAt; /** * The stats the VFS's function `name` declares, each found in the stored * listings. Mirrors Python's `_handed`. */ private handedFor; /** * Run one level with the stats its function declares. A stat a stored * listing handed in may be stale: the backend answering ENOENT for it drops * the listings it came from, which lists them again, and the level runs once * more. A level handed nothing is not run again, nor a stat the listings * found nothing at: the backend agrees with them. `found` is the stats * already found for this level, null to find them here. Mirrors Python's * `_run`. */ private run; /** Drop the listings a stale stat was handed from. Mirrors Python's `_forget`. */ private forget; /** * A listing or a stat the stored listings answer, null for one the backend * must. Mirrors Python's `_stored`. */ private stored; /** * A stored row that answers a stat whole, null for one the backend must * finish: a file with no size, on a VFS whose stat sizes every file (chroma * sizes a folder's pages on its first stat). Mirrors Python's `_whole`. */ private whole; /** * Whether a kept listing vouches for its children's names only. A folder's * version moves when its names change, not when a child is edited in place, * so under `fresh` a mount whose listings are checked by folder answers a * child's stat from its listing only when the running command listed it. * Mirrors Python's `_names_only`. */ private get namesOnly(); /** * The VFS's read of `scope` as a stream, called on the first pull and * handed the stats it declares. Mirrors Python's `_streamed`. */ private streamed; /** * Run `name` on this mount's VFS over a scope already keyed below the * mount: each level in turn until one answers with something other than * null. A read resolves its renderer by `kwargs.filetype` when the caller * names one (null asks for the stored bytes) and by the path's extension * otherwise. A VFS with no `read` of its own (gdocs, gsheets, gslides) * stores the rendering, so its stored bytes are the renderer's. A * `read_tree` of one level (`level: true`) is the mount's own listing with * each child's row (`level`). */ callKeyed(name: string, scope: PathSpec, args?: readonly unknown[], kwargs?: OpKwargs, access?: SessionAccess): Promise<unknown>; /** * Keep a current stat the backend answered as the running command's * probe answer: the cache gate would ask the backend the same question * again. Only a stat that asked the backend counts: one that takes neither * an index of its own nor a row the mount hands it, either of which a * listing filled. A probe's own store records through the probe. Mirrors * Python's `_note_live`. */ private noteLive; /** * What a listing or a subtree read answered, kept as listings; a listing * answers its children's paths once kept. Mirrors Python's `_kept`. */ private kept; /** * The VFS's streamed read of a scope keyed below the mount, framed for later * pulls. The backend runs on each pull, after this frame is gone, so every * pull gets what a call gets around it: the caller's context (session, * recorder, cache scope), the mount's recording context, its write context * (a read hands its token on only where a write will send it) and its * revision pins. The mount is held until the stream ends or is closed. A read that * is neither async nor handed stats is called as the stream opens, so what * it binds (a pipe) is bound before the path can be released. Mirrors * Python's `read_stream`. */ readStream(scope: PathSpec, access?: SessionAccess): AsyncIterable<Uint8Array>; private resolveCascade; /** * What a write through this mount must carry, null when its writes are * unconditional. Bound by both entry points whatever the policy, so an * unconditional mount clears a context an outer command's mount set. * Mirrors Python's `MountEntry.write_context`. */ writeContext(): WriteContext | null; /** * `caller` running on this mount: its records name this mount, its writes * are checked against this mount's write context, its reads keep this * mount's revision pins and a connection it opens names this mount. * Mirrors Python's `MountEntry._on_mount`. */ private onMount; runCommand(cmdName: string, paths: PathSpec[], texts: string[], flags: Record<string, FlagValue>, ctx?: Partial<Context>): Promise<[ByteSource | null, IOResult]>; /** * The handlers to try in order. * * A filetype handler is selected from the operand's NAME, and a * directory can carry any extension, so the cascade would hand a * renderer a directory to read. One stat settles it, and only when a * handler for this exact extension exists, so a mount with no filetype * registrations never reaches the probe. The built-in is what a * directory should get: it owns GNU's `Is a directory` wording, and the * renderer owns nothing but its own format. The DISPATCHER's stat, not * the backend's, so a mount root and a namespace-only directory answer * too; null means neither plane saw anything, in which case the renderer * reports its own miss. Mirrors Python's MountEntry._pick_handlers. */ private pickHandlers; /** `p` with this mount's backend key stamped on. */ private keyed; /** * Stamp this mount's backend key onto each path operand, for a handler that * addresses its backend through the mount's accessor (a user command, a * command tree's leaf); mirage's own commands get every path keyed by its * whole virtual path (`virtualKey`) and reach it through their files. A * stdin `-` routed nowhere, so it rides on whichever mount runs the line, * beside the operands that chose it. Mirrors Python's * MountEntry._keyed_paths. */ private keyedPaths; /** * Stamp this mount's backend key onto path-shaped flag values so backend * reads can address them: a single PathSpec (awk -f, tar -f) or a list * (repeated grep -f, jq's --rawfile pairs). Everything else passes * through unchanged. Mirrors Python's MountEntry._keyed_flags. */ private keyedFlags; /** * The one typed bag a handler reads, built here and nowhere else. * * A pattern operand travels to the handler whole. The handler resolves it * once, through its files, which is where the namespace facts (links, * nested mount roots, a trailing slash) are in view, so expanding here * would lose what the handler needs. Python's dispatcher never expands * either. The working directory is keyed below `prefix`: this mount's for a * handler `keyedPaths` keys the operands of, empty for mirage's own * commands, whose paths keep their whole virtual path. Mirrors Python's * MountEntry._command_ctx. */ private commandCtx; /** * Run `fn` with what a handler's backend calls read bound: the mode the * command tier's mode guard holds each write to (its own region's mode), * what the command tier's walk guard proves an operand's `.` and `..` * with, the recorder's mount, the mount's cache manager and write context, * and the snapshot revision pins. Mirrors Python's MountEntry._command_scope. */ private inCommandScope; /** * Refuse a user handler on a hidden mount, or a writer on a read-only one. * * A user handler can reach its accessor directly, so a hidden mount's * handler must never run, even when an explicit operand or cwd selected it. * It answers an unknown command without revealing the hidden mount's name. * * Mirage's own commands write through the dispatcher, which refuses each * write in the command's own GNU voice, because only the write knows * whether a line writes: `gzip -c`, `tar -t` and `split -n 1/2` read a * read-only mount like any reader, and `gzip f` is refused at the write of * `f.gz`. A user command (`isUserCommand`) may reach its service some other * way, which no gate would see, so one that writes is refused here, before * it runs. strongestModeUnder, not effectiveMode: a mount whose only * writable region is a show entry still runs it. Only wrapper-owned * responses (an injected help or version) bypass it: a handler that * declares `--help` itself is handed the flag and runs. The trailing * newline is load-bearing: stderr accumulates across a line. `served` is * the command this mount serves `cmd` under, which says whether it is a * user command: a tree's root for one of its leaves, which is built for its * one run and never registered. Mirrors Python's * MountEntry._command_refusal. */ private commandRefusal; /** * Whether `cmd` is a user command: one this mount serves that is not * mirage's own, registered through `registerCommands` (a VFS's handed * commands among them) rather than as a builtin. Mirrors Python's * MountEntry._is_user_command. */ private isUserCommand; /** * Run one handler through the runner a CLI leaf shares, in this mount's * voice. `typed` are the operands as the line typed them, which a file * error names. A user * command that declares a write reaches its backend through its own * handle, past the dispatcher's per-path invalidation, so this mount drops * its caches once it has run; mirage's own commands write through the * dispatcher and drop nothing. Mirrors Python's MountEntry._run_one. */ private runOne; /** * Run one leaf of a command tree this mount serves. * * The executor walked the line and parsed the leaf's words, as it does an * installed CLI's; the leaf runs here as a command does: on this mount's * accessor, in its scope, under the read-only refusal for its own `write` * and, being a user command that holds the raw handle, the refusal a * restricting session gets. Its failures speak in a CLI's voice. Mirrors * Python's `MountEntry.run_leaf`. */ runLeaf(prog: string, cli: CLI, handler: CLIHandler, paths: PathSpec[], texts: string[], ctx: Context): Promise<CommandFnResult>; /** * Run a handler body through the shared runner with what this mount binds * around it. The runner abandons the promise, it cannot cancel it; the * context's signal (a background job's kill channel, the line's deadline) * lets a runtime kill what it spawned (python cancels the task instead). */ private runIn; /** * Drop what this mount keeps after a write that went past the dispatcher: * its listings and bodies (`CacheManager.drop`), and the answers its client * keeps of its own (`Accessor.forget`). Mirrors Python's * `MountEntry.drop_caches`. * * @param bodies drop the bodies too; false when the registry clears the * whole file cache itself. */ dropCaches(bodies?: boolean): Promise<void>; /** The client this mount's VFS reaches its backend with. */ get accessor(): Accessor; /** * Run an op on this mount's VFS. A read tries a rendered filetype's * renderer first, then the VFS's own function; the first answer that is * not null wins. A caller may name the filetype, and null asks for the * stored bytes even where the VFS renders the filetype: a * read-modify-write hands whatever it read straight back to `write`, * which always stores, so reading a rendered form would store the * rendering over the file. Mirrors Python's `MountEntry.call`. */ call(name: string, path: string, args?: readonly unknown[], kwargs?: OpKwargs, access?: SessionAccess): Promise<unknown>; /** `path` as this mount's VFS addresses it. Mirrors Python's _scope. */ private scope; /** * Whether this mount's store may copy `src` from `source`'s, store to * store. Not when this mount's VFS cannot copy from that one, nor when the * store's object is not what a move's read and write would carry: a cached, * pinned or rendered source reads other bytes, and a conditional mount on * either end holds a version for the write or for `mv`'s delete. Asked * before the copy's deadline starts. Mirrors Python's `can_copy_from`. */ canCopyFrom(source: MountEntry, src: string): Promise<boolean>; /** * Copy a file from `source`'s store into this mount's, store to store. * For `cp` and `mv` between two mounts, * once the dispatcher has judged `src` as a read and `dst` as a create and * `canCopyFrom` has said yes. The VFS rejects EXDEV when it cannot make the * copy after all, and the caller moves the bytes itself. `onCall` receives * the store's own call once it starts. * Mirrors Python's `copy_from`. */ copyFrom(source: MountEntry, src: string, dst: string, onCall?: (storeCall: Promise<unknown>) => void): Promise<void>; /** * Read several whole files on this mount in one request. For the * dispatcher's read-ahead, once it has walked and * followed each path; keyed by virtual path, a file left out is read on its * own later. Mirrors Python's `read_many`. */ readMany(paths: readonly string[], limit: number): Promise<Map<string, Uint8Array>>; /** * Run one backend call as `call` frames its own: as the caller on this * mount (`onMount`), an answer that streams wrapped to keep that frame and * `owner`'s egress. Public for the dispatcher, which keys and calls its ops * itself. Mirrors Python's `_framed`. */ framed<T>(work: () => Promise<T>, owner?: Egress | null): Promise<T>; /** * The VFS's batch read, handed what is at each path and at its folder as * the stored listings name them, where it declares them. Mirrors Python's * `_read_many`. */ private batchRead; /** * Rank several scopes on this mount as one query. For the dispatcher, once it has walked, followed and * admitted each scope as it does a `search`'s one. Mirrors Python's * `search_many`. */ searchMany(paths: readonly string[], query: SearchQuery): Promise<[string, string][] | null>; } export {}; //# sourceMappingURL=mount.d.ts.map