@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
177 lines (176 loc) • 7.42 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 { BoxAccessor } from "../../accessor/box.js";
import { capacity as boxCapacity } from "../../core/box/capacity.js";
import { checksum as boxChecksum } from "../../core/box/checksum.js";
import { BoxTokenManager } from "../../core/box/client.js";
import { CONDITION_SUPPORT } from "../../core/box/constants.js";
import { copy as boxCopy } from "../../core/box/copy.js";
import { create as boxCreate } from "../../core/box/create.js";
import { buildDeltaHook } from "../../core/box/events.js";
import { exists as boxExists } from "../../core/box/exists.js";
import { mkdir as boxMkdir } from "../../core/box/mkdir.js";
import { read as boxRead, readStream as boxStream } from "../../core/box/read.js";
import { readdir as boxReaddir } from "../../core/box/readdir.js";
import { rename as boxRename } from "../../core/box/rename.js";
import { rmdir as boxRmdir, rmR as boxRmR } from "../../core/box/rmdir.js";
import { filesContaining as boxFilesContaining } from "../../core/box/search.js";
import { stat as boxStat } from "../../core/box/stat.js";
import { truncate as boxTruncate } from "../../core/box/truncate.js";
import { unlink as boxUnlink } from "../../core/box/unlink.js";
import { replace as boxReplace, write as boxWrite } from "../../core/box/write.js";
import {} from "../../events/index.js";
import { VFSName } from "../../types.js";
import { BaseVFS } from "../base.js";
import { BoxConfig } from "./config.js";
export const VFS_MD = `\`\`\`text
{prefix}
<folder>/ # the Box folder hierarchy, mirrored
<name>.boxnote # Box Note: raw ProseMirror-style JSON
<name>.boxcanvas # Box Canvas: raw canvas JSON
<name>.gdoc # Box's Google Workspace files, stored as
<name>.gsheet # Office Open XML (docx, xlsx, pptx):
<name>.gslides # opaque binary
<file> # PDFs, images and the rest: raw bytes
\`\`\`
- Every item is served as its raw bytes; pipe the JSON ones to \`jq\`.
- This is a remote mount: prefer targeted reads over full scans.
- Box names folders by numeric id (the root is \`0\`). The path-to-id
mapping is cached, so a nested directory costs one API call per level
on first access. \`ls\` the parent before constructing a path.`;
export class BoxVFS extends BaseVFS {
get conditionSupport() {
return CONDITION_SUPPORT;
}
name = VFSName.BOX;
cachesReads = true;
// Box item listings carry an exact byte `size` for every file (0
// included); sizeless weblinks are filtered out of listings.
sizesAlwaysKnown = true;
// stat and every whole read stamp the file's sha1, which a listing row
// and GET /files/{id} carry. A download names no version, so a read
// checks its bytes against the row it resolved through.
readRevalidatable = true;
indexTtl = 86_400;
handed = {
readdir: ['entry'],
read: ['entry'],
stat: ['entry', 'index'],
checksum: ['entry'],
exists: ['entry'],
write: ['entry', 'parent'],
create: ['entry', 'parent'],
mkdir: ['parent'],
unlink: ['entry'],
rmdir: ['entry'],
rmR: ['entry'],
rename: ['entry', 'dstParent', 'dstEntry'],
copy: ['entry', 'dstParent', 'dstEntry'],
dirCopy: ['entry', 'dstParent', 'dstEntry'],
truncate: ['entry', 'parent'],
filesContaining: ['entry'],
};
vfsMd = VFS_MD;
wholeWordSearch = true;
config;
accessor;
constructor(config) {
super();
this.config = config;
// The whole config goes to the token manager, never a hand-picked
// subset: a field added to BoxConfig would silently stop reaching it
// (that is how gdrive lost apiBase and kept refreshing at the real
// Google endpoint against a fake server).
const tm = new BoxTokenManager(config);
this.accessor = new BoxAccessor({
tokenManager: tm,
...(config.rootFolderId !== undefined ? { rootFolderId: config.rootFolderId } : {}),
});
}
readsRanges = true;
readdir(path, handed) {
return boxReaddir(this.accessor, path, handed?.entry, handed?.limit);
}
read(path, handed, offset = 0, size = null) {
if (offset === 0 && size === null)
return boxStream(this.accessor, path, handed?.entry);
return boxRead(this.accessor, path, size === null ? { offset } : { offset, size }, handed?.entry);
}
stat(path, handed) {
return boxStat(this.accessor, path, handed?.entry, handed?.index);
}
checksum(path, algorithm, handed) {
return boxChecksum(this.accessor, path, algorithm, handed?.entry);
}
exists(path, handed) {
return boxExists(this.accessor, path, handed?.entry);
}
write(path, data, signal, handed = {}) {
return boxWrite(this.accessor, path, data, handed, signal);
}
_replace(request, signal, handed = {}) {
return boxReplace(this.accessor, request, handed, signal);
}
create(path, handed = {}) {
return boxCreate(this.accessor, path, handed);
}
mkdir(path, parents = false, handed = {}) {
return boxMkdir(this.accessor, path, parents, handed);
}
unlink(path, handed) {
return boxUnlink(this.accessor, path, handed);
}
rmdir(path, handed) {
return boxRmdir(this.accessor, path, handed);
}
rmR(path, handed = {}) {
return boxRmR(this.accessor, path, handed);
}
rename(src, dst, handed = {}) {
return boxRename(this.accessor, src, dst, handed);
}
copy(src, dst, _signal, handed = {}) {
return boxCopy(this.accessor, src, dst, handed);
}
dirCopy(src, dst, handed = {}) {
return boxCopy(this.accessor, src, dst, handed);
}
truncate(path, length, noCreate = false, handed = {}) {
return boxTruncate(this.accessor, path, length, noCreate, handed);
}
/**
* Whether this VFS answers `name`; with no walk limit it reads every
* file rather than ask the service's search.
*/
supports(name) {
if (name === 'files_containing' && this.walkLimit < 0)
return false;
return super.supports(name);
}
filesContaining(path, text, opts, handed) {
if (!opts.wholeWord || this.walkLimit < 0)
return Promise.resolve(null);
return boxFilesContaining(this.accessor, text, path, handed?.entry);
}
capacity() {
return boxCapacity(this.accessor);
}
deltaHook() {
return buildDeltaHook(this.accessor);
}
getState() {
return Promise.resolve(this.configState(BoxConfig, this.config));
}
}
//# sourceMappingURL=box.js.map