@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
106 lines • 4.61 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 { Delta } from "../../types.js";
import { mountPrefixOf } from "../../utils/key_prefix.js";
import { sortedJson } from "../../utils/sort.js";
import { changesUnder, justMade, settle } from "../../events/delta.js";
/** What a Drive change-feed checkpoint holds, null for a baseline. Mirrors Python `feed_state`. */
export function feedState(checkpoint) {
if (checkpoint === null)
return null;
const data = JSON.parse(checkpoint);
const state = data?.drive;
if (state === undefined || typeof state.token !== 'string')
return null;
return state;
}
/** Encode a Drive change-feed checkpoint. Mirrors Python `feed_checkpoint`. */
export function feedCheckpoint(state) {
return sortedJson({ drive: state });
}
/**
* Checkpointed delta of a Google app mount over Drive's change feed.
*
* Drive pings a channel with no body, so a ping reads `changes.list` from the
* page token the last read handed out, keeping the changes to the app's files.
* Each is placed as the app lists it: in `owned` or `shared` by its first
* owner, named by its title, id and modified day. The checkpoint keeps the
* path of each file a read placed, so an edit on another day or a new title
* moves it from the name it had, and a file deleted, trashed or no longer
* shared leaves it. A file seen for the first time is created when it is as
* Drive made it (its created and modified times agree) and updated otherwise;
* the name it had before is left to the corpus listing the update evicts.
*
* Mirrors Python `AppDeltaHook` (`core/google/events.py`).
*/
export class AppDeltaHook {
accessor;
mime;
makeFilename;
constructor(accessor, mime, makeFilename) {
this.accessor = accessor;
this.mime = mime;
this.makeFilename = makeFilename;
}
async pull(root, checkpoint) {
const drive = this.accessor.drive;
const observed = new Date();
const state = feedState(checkpoint);
if (state === null) {
const token = await drive.startPageToken();
return new Delta({
changes: [],
checkpoint: feedCheckpoint({ token, seen: {} }),
});
}
const [found, token] = await drive.listChanges(state.token);
const prefix = mountPrefixOf(root.virtual, root.vfsPath);
const seen = { ...(state.seen ?? {}) };
const changes = [];
for (const change of found) {
const itemId = change.fileId;
if (typeof itemId !== 'string')
continue;
const item = change.file;
if (item?.mimeType !== this.mime) {
if (change.removed === true)
settle(changes, seen, itemId, null, false);
continue;
}
const path = this.path(prefix, item);
if (change.removed === true || item.trashed === true) {
if (!Object.hasOwn(seen, itemId))
seen[itemId] = path;
settle(changes, seen, itemId, null, false);
continue;
}
settle(changes, seen, itemId, path, justMade(item.createdTime, item.modifiedTime));
}
return new Delta({
changes: changesUnder(root, changes, observed),
checkpoint: feedCheckpoint({ token, seen }),
});
}
/** Where the app lists `item`. */
path(prefix, item) {
const corpus = item.owners?.[0]?.me === true ? 'owned' : 'shared';
const name = this.makeFilename(item.name, item.id, item.modifiedTime ?? '');
return `${prefix}/${corpus}/${name}`;
}
}
/** The delta hook of a Google app mount (`AppDeltaHook`). Mirrors Python's `app_delta_hook`. */
export function appDeltaHook(accessor, mime, makeFilename) {
return new AppDeltaHook(accessor, mime, makeFilename);
}
//# sourceMappingURL=events.js.map