@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
68 lines • 3.3 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 { mountPrefixOf } from "../../utils/key_prefix.js";
import { rstripSlash, stripSlash } from "../../utils/slash.js";
import { floatOption, intOption, textOption, validateOptions } from "../../vfs/search.js";
const DEC = new TextDecoder();
function targetTable(pinned, paths) {
if (pinned !== null)
return pinned;
for (const path of paths) {
const key = stripSlash(path.mountPath);
if (key !== '')
return key.split('/')[0] ?? null;
}
return null;
}
/** Rank a table's rows and render each hit under its canonical path. */
export async function searchRowsOutput(tree, accessor, query, paths, topK, threshold, mountPrefix) {
if (query === '')
throw new Error('query is required');
if (topK <= 0)
throw new Error('top-k must be positive');
const pinned = tree.pinned(accessor);
const table = targetTable(pinned, paths);
if (table === null)
throw new Error('no table to search');
const blocks = [];
for (const row of await tree.searchRows(accessor, table, query, topK)) {
const rank = row[tree.rankKey];
const ranked = rank !== null && rank !== undefined;
if (threshold > 0 && ranked && tree.drops(Number(rank), threshold))
continue;
const [segments, body] = tree.hit(accessor, row);
const path = [rstripSlash(mountPrefix), ...(pinned === null ? [table] : []), ...segments].join('/');
const header = ranked ? `${path}:${Number(rank).toFixed(4)}` : path;
blocks.push([path, `${header}\n${DEC.decode(body).replace(/\n+$/, '')}`]);
}
return blocks;
}
/** Build a store's ranked search, one native ranking per batch. */
export function makeSearch(tree) {
async function searchMany(accessor, paths, query) {
validateOptions(query, ['top_k', 'threshold', 'method']);
const topK = intOption(query, 'top_k', tree.searchLimit(accessor));
const first = paths[0];
if (first === undefined)
throw new Error('at least one scope is required');
const method = textOption(query, 'method', 'semantic');
const threshold = floatOption(query, 'threshold', 0);
if (method !== 'semantic')
throw new Error("only the 'semantic' method is supported");
const output = await searchRowsOutput(tree, accessor, query.query, paths, topK, threshold, mountPrefixOf(first.virtual, first.vfsPath));
return output;
}
return { search: (accessor, path, query) => searchMany(accessor, [path], query), searchMany };
}
//# sourceMappingURL=search.js.map