UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

159 lines • 6.81 kB
// ========= 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