@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
159 lines • 6.81 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 { FileType } from "../../types.js";
import { record, recordStream, recordingActive, revisionFor, startOp, } from "../../observe/context.js";
import { NATIVE_RESOURCE_TYPES, driveFingerprint } from "./fingerprint.js";
import { Md5, md5HexAsync } from "../../utils/hash.js";
import { eisdir, enoent } from "../../errors/fs.js";
import { sliceWindow, windowFor } from "../../utils/ranges.js";
// Whether a read returned the whole object rather than a window. A token
// describes the whole object, so a window stamped with one would read as
// fresh for the life of the entry.
function wholeFile(offset, size) {
return offset === 0 && size === null;
}
// Whether Drive's md5 names other bytes than the ones just read. The metadata
// and the download are two requests, so a write between them leaves metadata
// that predates the bytes.
async function staleMd5(md5, data) {
return typeof md5 === 'string' && md5 !== '' && (await md5HexAsync(data)) !== md5;
}
// Download a binary file honouring snapshot revision pins. A pinned path
// reads that revision's content. Otherwise the token comes from a capture when
// a recorder is bound and from the listed row when none is, so it never
// depends on the recorder; the row's revision is not pinned, since it can be a
// TTL old. Either md5 is checked against the bytes, and a stale one drops the
// token and the revision with it.
export async function readFileVersioned(drive, fileId, virtual, entry, offset = 0, size = null) {
const pinned = revisionFor(virtual);
const window = windowFor(offset, size);
const whole = wholeFile(offset, size);
const timer = startOp();
let fingerprint = null;
let revision = pinned;
let data;
if (pinned !== null) {
data = await drive.downloadRevision(fileId, pinned, window);
}
else if (recordingActive()) {
const [md5, captured] = await drive.captureFileMetadata(fileId);
revision = captured;
data = await drive.downloadFile(fileId, window);
if (whole && (await staleMd5(md5, data)))
revision = null;
else if (whole)
fingerprint = driveFingerprint(entry.extra.resource_type, md5, revision, entry.modified);
}
else {
data = await drive.downloadFile(fileId, window);
if (whole && !(await staleMd5(entry.extra.md5_checksum, data)))
fingerprint = entry.fingerprint;
}
record('read', virtual, 'gdrive', data.length, timer, { fingerprint, revision });
return data;
}
/**
* What is at the file `path`, as its parent's listing named it. Rejects
* ENOENT when nothing is there and EISDIR when a folder is. Mirrors Python's
* `file_entry`.
*/
export function fileEntry(path, entry) {
if (entry === null || entry === undefined)
throw enoent(path.virtual);
if (entry.type === FileType.DIRECTORY)
throw eisdir(path.virtual);
return entry;
}
/**
* Read a Drive file, optionally only a byte range of it.
*
* Only a binary file has a remote range to ask for. A google-apps file
* is rendered here into JSON, so its bytes do not exist until we make
* them and the window can only be taken afterwards.
*
* Args:
* accessor: Drive accessor.
* path: the path to read.
* options: `{offset, size}`, the byte window, or absent for the whole file.
* entry: what is at the path.
*/
export async function read(accessor, path, options, entry) {
const offset = options?.offset ?? 0;
const size = options?.size ?? null;
const found = fileEntry(path, entry);
const fileId = found.extra.file_id;
const rt = found.extra.resource_type;
if (!NATIVE_RESOURCE_TYPES.has(rt))
return readFileVersioned(accessor.drive, fileId, path.virtual, found, offset, size);
const timer = startOp();
let rendered;
if (rt === 'gdrive/gdoc')
rendered = await accessor.docs.readDoc(fileId);
else if (rt === 'gdrive/gsheet')
rendered = await accessor.sheets.readSpreadsheet(fileId);
else
rendered = await accessor.slides.readPresentation(fileId);
const sliced = sliceWindow(rendered, offset, size);
// No revision: a pin would replace the drift check a render relies on.
record('read', path.virtual, 'gdrive', sliced.length, timer, {
fingerprint: wholeFile(offset, size) ? found.fingerprint : null,
revision: null,
});
return sliced;
}
/**
* Stream a Drive file as it downloads. A google-apps file exists only once
* rendered, so it comes in one chunk, as `read` gives it. A binary file is
* stamped as `readFileVersioned` stamps a whole read, once the last chunk is
* in and checked against the md5 Drive gave: a stream abandoned part-way
* stamps nothing. Mirrors Python's `read_stream`.
*/
export async function* readStream(accessor, path, entry) {
const found = fileEntry(path, entry);
const fileId = found.extra.file_id;
if (NATIVE_RESOURCE_TYPES.has(found.extra.resource_type)) {
yield await read(accessor, path, undefined, found);
return;
}
const drive = accessor.drive;
const pinned = revisionFor(path.virtual);
const rec = recordStream('read', path.virtual, 'gdrive', { revision: pinned });
let md5 = null;
let revision = null;
let chunks;
if (pinned !== null) {
chunks = drive.downloadRevisionStream(fileId, pinned);
}
else {
if (rec !== null)
[md5, revision] = await drive.captureFileMetadata(fileId);
chunks = drive.downloadFileStream(fileId);
}
const digest = md5 !== null && md5 !== '' ? new Md5() : null;
for await (const chunk of chunks) {
digest?.update(chunk);
if (rec !== null)
rec.bytes += chunk.byteLength;
yield chunk;
}
if (rec !== null)
rec.complete = true;
if (rec === null || pinned !== null)
return;
if (digest !== null && digest.digest() !== md5)
return;
rec.revision = revision;
rec.fingerprint = driveFingerprint(found.extra.resource_type, md5, revision, found.modified);
}
//# sourceMappingURL=read.js.map