@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
91 lines • 3.96 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 { mountedPath } from "../../utils/key_prefix.js";
import { rowsOf } from "../hierarchy/probe.js";
import { groupName, rowStem } from "./naming.js";
import { fieldValue } from "./payload.js";
import { renderJson, renderText } from "./render.js";
import { rstripSlash } from "../../utils/slash.js";
/**
* A ranked point's path below its collection, and its body: the source text
* when the point has one, its `.json` otherwise.
*/
export function hit(accessor, row) {
const config = accessor.config;
const segments = [];
for (const column of config.groupBy) {
const value = fieldValue(row, column);
if (value !== null && value !== undefined) {
segments.push(groupName(value, config.basenameFields.includes(column)));
}
}
const stem = rowStem(row, config);
const text = config.textField !== null ? fieldValue(row, config.textField) : null;
if (text !== null && text !== undefined) {
return [[...segments, `${stem}.txt`], renderText(row, config)];
}
return [[...segments, `${stem}.json`], renderJson(row, config)];
}
/**
* The files under `under` that may hold `text` in any case.
*
* The listing's scroll carries every payload, so each `.json` and `.txt` row
* keeps its rendered text, lowercased, and a file is read only when that text
* holds `text`; a blob, or a file too large to keep, is always read. The walk
* lists each directory from the store, so the text is checked against the
* points as they are now. null for a text that is not ASCII, whose case a
* lowercased copy may not keep, or a listing that fails. Mirrors Python's
* `files_containing`.
*/
export async function filesContaining(readdir, accessor, text, under) {
if (/[\u0080-\uffff]/.test(text))
return null;
try {
return await found(readdir, accessor, text.toLowerCase(), under);
}
catch (err) {
// the client rejects with a QdrantClient error, or the fetcher's error
// with a status, when Qdrant refuses or times out, and fetch with a
// TypeError when the connection fails
const failed = err instanceof TypeError ||
(err instanceof Error &&
(err.name.startsWith('QdrantClient') ||
typeof err.status === 'number'));
if (!failed)
throw err;
console.debug(`qdrant listing failed (${String(err)}); reading every file`);
return null;
}
}
async function found(readdir, accessor, needle, under) {
const out = [];
const pending = [...under];
for (let current = pending.pop(); current !== undefined; current = pending.pop()) {
const parent = rstripSlash(current.mountPath);
for (const row of rowsOf(await readdir(accessor, current))) {
const child = mountedPath(current, `${parent}/${row.name}`);
if (row.type === FileType.DIRECTORY) {
pending.push(child);
continue;
}
const kept = row.extra.text;
if (typeof kept === 'string' && !kept.includes(needle))
continue;
out.push(child);
}
}
return out;
}
//# sourceMappingURL=search.js.map