@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
98 lines • 4.31 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 { IOResult } from "../../../../io/types.js";
import { defaultPaths } from "../../../builtin/utils/paths.js";
import { UsageError } from "../../../errors.js";
import { FlagView } from "../../../spec/flag_view.js";
import { Argument, CommandSpec } from "../../../spec/types.js";
import { CLI, CLIHandler } from "../../types.js";
/**
* The options a line gives the mount's search, only those it names. The
* mount answers an absent one with its own default and refuses a value it
* cannot take, before it sends any request; a `--top-k` below 1 is a usage
* error under `prog`, the name the CLI is installed under. Mirrors Python's
* `search_options`.
*/
export function searchOptions(fl, prog) {
const options = {};
const topK = fl.asInt('top_k');
if (topK !== undefined) {
if (topK < 1)
throw new UsageError(`${prog}: top-k must be positive`);
options.top_k = topK;
}
const threshold = fl.asFloat('threshold');
if (threshold !== undefined)
options.threshold = threshold;
const method = fl.asStr('method');
if (method !== undefined)
options.method = method;
return options;
}
/**
* Rank the records under every scope for one query and print each hit's
* record: one search through the dispatcher, the scopes after the first
* riding along as `scopes`, which ranks them as one where the mount serving
* them all ranks a batch, and searches each and joins the hits in scope
* order otherwise. A declined query fails rather than reading as no
* matches. Mirrors Python's `search`.
*/
export async function search(_accessor, paths, texts, ctx) {
const prog = ctx.spec?.name ?? 'search';
const text = texts[0];
if (text === undefined || text === '')
throw new UsageError(`${prog}: query is required`);
if (ctx.vfs === undefined)
throw new Error('no workspace files');
const query = {
query: text,
options: searchOptions(new FlagView(ctx.flags, ctx.spec), prog),
};
const [first, ...scopes] = defaultPaths(paths, ctx.cwd.virtual);
if (first === undefined)
throw new Error('no scope to search');
const [answer] = await ctx.vfs.dispatch('search', first, [query], scopes.length > 0 ? { scopes } : {});
if (answer === null)
throw new Error('the mount declined the query');
const records = answer;
const out = records.map(([, record]) => `${record}\n`).join('');
return [new TextEncoder().encode(out), new IOResult()];
}
export const SEARCH = new CLI({
spec: new CommandSpec({
name: 'search',
description: 'Rank the records of a search mount for a query',
arguments: [
new Argument('--method', {
help: 'Ranking method: semantic (default); Dify also takes fulltext, hybrid and keyword',
}),
new Argument('--top-k', {
type: 'int',
help: "Most hits to print; the mount's default when absent",
}),
new Argument('--threshold', {
type: 'float',
help: 'Lowest score a hit needs, or on LanceDB the largest distance it may have; 0 keeps every hit',
}),
new Argument('query', { nargs: '?', help: 'Text to rank records by' }),
new Argument('paths', {
type: 'path',
nargs: '*',
help: 'Folders or files to search; the working directory when none is given',
}),
],
}),
handlers: { '': new CLIHandler({ fn: search }) },
});
//# sourceMappingURL=index.js.map