@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
82 lines • 3.52 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 { isFsError } from "../../errors/fs.js";
import { FileType } from "../../types.js";
import { mountedPath } from "../../utils/key_prefix.js";
import { stripSlash } from "../../utils/slash.js";
import { GoogleApiError } from "../google/client.js";
import { rootContext } from "./resolve.js";
import { PARENTS_PER_QUERY, iterTree } from "./tree.js";
/**
* The files under `under` Drive full-text search names. The folder is walked
* by id and Drive is asked which files in the walked folders hold the text.
* Null when a listing or the search fails or times out, nothing is found,
* Drive skips a corpus, or the scope is not a folder.
*
* Drive searches the text it extracted, not the bytes grep reads, so the
* answer can leave out a file that holds the text: a Docs, Sheets or Slides
* file (its JSON keys and ids), an office file, PDF or archive, the end of a
* large file, a word Drive splits differently (`foo.bar`), or a recent write.
* Mirrors Python's `files_containing`.
*/
export async function filesContaining(accessor, text, under, entry) {
let root;
let driveId;
if (stripSlash(under.vfsPath) === '') {
;
[root, driveId] = await rootContext(accessor);
}
else if (entry?.type !== FileType.DIRECTORY) {
return null;
}
else {
root = entry.extra.file_id;
const entryDriveId = entry.extra.drive_id;
driveId = typeof entryDriveId === 'string' ? entryDriveId : null;
}
const drive = accessor.drive;
const found = [];
try {
const folders = [root];
const files = new Map();
for await (const [rel, item, isDir] of iterTree(accessor, under, root, driveId)) {
if (isDir)
folders.push(item.id);
else
files.set(item.id, [...(files.get(item.id) ?? []), mountedPath(under, `/${rel}`)]);
}
for (let start = 0; start < folders.length; start += PARENTS_PER_QUERY) {
const parents = folders.slice(start, start + PARENTS_PER_QUERY);
const answer = await drive.searchFiles(text, parents);
if (!answer.complete)
return null;
for (const id of answer.ids)
found.push(...(files.get(id) ?? []));
}
}
catch (err) {
// fetch rejects with a TypeError when the connection fails and a
// DOMException when its timeout aborts it
const failed = isFsError(err) ||
err instanceof GoogleApiError ||
err instanceof TypeError ||
err instanceof DOMException;
if (!failed)
throw err;
console.debug(`drive search failed (${String(err)}); reading every file`);
return null;
}
return found.length > 0 ? found : null;
}
//# sourceMappingURL=search.js.map