@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
136 lines • 5.15 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 { ensureStream } from "../../io/stream.js";
import { VFSName } from "../../types.js";
import { concat } from "../../utils/bytes.js";
/**
* Stamp the caller's report: memory answered, no backend ran.
*
* Fires at the moment a warm file-cache hit or a synthetic namespace
* answer is in hand, before the post gate and any output cap, so
* whatever those throw cannot erase the fact. The value is
* `VFSName.RAM`, which is how a record says "this never crossed
* the network": `OpRecord.isCache` is defined as that string, and
* every network/cache total derives from it.
*/
export function memoryAnswered(report, moved = null) {
report?.served(VFSName.RAM, moved);
}
/**
* Stamp the caller's report: the owning mount answered. A report memory
* already stamped (a namespace answer for a missing path) keeps that.
* Mirrors Python's `_served`.
*/
export function served(report, result, moved = null) {
if (report?.completed === true)
return;
report?.served(null, moved ?? (result instanceof Uint8Array ? result.byteLength : null));
}
/**
* Pull a stream's first chunk now and answer the stream from there, so a read
* that fails at its start fails at the call, where the dispatcher handles it, rather
* than in the hands of whoever pulls it later. A live pipe's first byte may wait
* on the very reader about to ask for it, so its read is framed unpulled
* (`pull` false). Mirrors Python's `_primed`.
*/
export async function primed(stream, report, pull = true) {
const iterator = stream[Symbol.asyncIterator]();
let head = [];
if (pull) {
let first;
try {
first = await iterator.next();
}
catch (err) {
await iterator.return?.();
throw err;
}
if (first.done !== true)
head = [first.value];
}
const rest = resumed(head, iterator, report);
await rest.next();
return rest;
}
/**
* The chunks already pulled, then the rest of the stream. `primed` runs it to
* its empty first step, inside the `try`, so a caller that closes it before
* pulling still closes the stream. The stream completes when its last chunk is
* pulled or it is closed, so that is when the caller's report is stamped, with
* the bytes the store moved.
*/
async function* resumed(head, iterator, report) {
let moved = head.reduce((sum, chunk) => sum + chunk.byteLength, 0);
try {
yield new Uint8Array();
yield* head;
for (;;) {
const next = await iterator.next();
if (next.done === true)
return;
moved += next.value.byteLength;
yield next.value;
}
}
finally {
await iterator.return?.();
report?.served(null, moved);
}
}
/**
* A streamed write's bytes, pulled with its names let go. The producer may
* wait on another writer of the same name (`tee -a f | tee -a f`, a loop
* appending to the file it pipes into), so the write holds the name only
* while the store takes a chunk, and writers interleave between chunks as
* two descriptors of one file do: a replacement landing there leaves the
* stream's later chunks at its own offset, as Linux does. What the store
* took is kept while it fits the cache's drain budget, so the next read is
* served what was written, as a whole write's is. Mirrors Python's
* `_LentPayload`.
*/
export class LentPayload {
source;
holds;
budget;
moved = 0;
kept = [];
constructor(source, holds, budget) {
this.source = source;
this.holds = holds;
this.budget = budget;
}
whole() {
return this.kept === null ? null : concat(this.kept);
}
async *chunks() {
const iterator = ensureStream(this.source)[Symbol.asyncIterator]();
const pull = this.holds.reduce((inner, hold) => () => hold.released(inner), () => iterator.next());
try {
for (;;) {
const next = await pull();
if (next.done === true)
return;
this.moved += next.value.byteLength;
if (this.kept !== null && this.moved > this.budget)
this.kept = null;
this.kept?.push(next.value.slice());
yield next.value;
}
}
finally {
await iterator.return?.();
}
}
}
//# sourceMappingURL=stream.js.map