@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
210 lines • 7.21 kB
JavaScript
import { z } from 'zod';
import { closeQuietly, discardStreams } from "../../io/stream.js";
import { YieldBudget } from "../../io/yield_budget.js";
import { StreamRefusal } from "../../rs/errors.js";
import { openStream, openStreamSync } from "../../rs/stream.js";
import { encodeText } from "../../shell/bytes.js";
import { UsageError } from "../errors.js";
import { foldsCase } from "./rg_search.js";
const COMPILED = z.object({
terms: z
.object({ texts: z.array(z.string()), whole_word: z.boolean(), ignore_case: z.boolean() })
.nullable(),
pcre: z.boolean(),
});
const TALLY = z.object({
selected: z.boolean(),
binary: z.boolean(),
skipped: z.boolean(),
matches: z.number().nonnegative(),
matched_lines: z.number().nonnegative(),
searched: z.number().nonnegative(),
nul: z.number().nonnegative().nullable(),
error: z.string().nullable(),
});
const BLOCK_BYTES = 64 * 1024;
const DEC = new TextDecoder();
const ENC = new TextEncoder();
export class RgProgram {
stream;
constructor(stream) {
this.stream = stream;
}
}
export function latin1(text) {
const data = encodeText(text);
const pieces = [];
for (let at = 0; at < data.length; at += 8192)
pieces.push(String.fromCharCode(...data.subarray(at, at + 8192)));
return pieces.join('');
}
export function streamConfig(pattern, neverMatch, f, regexSizeLimit = null, dfaSizeLimit = null) {
// PCRE2 folds as -S reads the pattern here, which the default engine works
// out itself; --engine auto hands it the same answer.
const pcre = f.engine !== 'default';
return {
pattern,
never_match: neverMatch,
engine: f.engine,
ban_nul: !(f.text || f.nullData),
regex_size_limit: regexSizeLimit,
dfa_size_limit: dfaSizeLimit,
ignore_case: pcre ? foldsCase(pattern, f.fixedString, f) : f.ignoreCase,
smart_case: f.smartCase && !pcre,
whole_word: f.wholeWord,
line_regexp: f.lineRegexp,
fixed_string: f.fixedString,
unicode: f.unicode,
multiline: f.multiline === true,
multiline_dotall: f.multilineDotall === true,
crlf: f.crlf === true,
null_data: f.nullData,
invert: f.invert,
line_numbers: f.lineNumbers,
column: f.column,
vimgrep: f.vimgrep,
byte_offsets: f.byteOffsets,
only_matching: f.onlyMatching,
trim: f.trim,
max_columns_preview: f.maxColumnsPreview,
null: f.null,
quiet: f.quiet,
count_only: f.countOnly,
count_matches: f.countMatches,
include_zero: f.includeZero,
files_only: f.filesOnly,
files_without_match: f.filesWithoutMatch,
heading: f.heading,
passthru: f.passthru,
stop_on_nonmatch: f.stopOnNonmatch,
stats: f.stats === true,
json: f.json === true,
max_columns: f.maxColumns,
max_count: f.maxCount,
context_after: f.contextAfter,
context_before: f.contextBefore,
replace: f.replace === null ? null : latin1(f.replace),
context_separator: f.contextSeparator === null ? null : latin1(f.contextSeparator),
field_match_separator: latin1(f.fieldMatchSeparator),
field_context_separator: latin1(f.fieldContextSeparator),
};
}
export async function openRg(pattern, neverMatch, f, regexSizeLimit = null, dfaSizeLimit = null) {
try {
new TextDecoder('utf-8', { fatal: true }).decode(encodeText(pattern));
}
catch (error) {
if (error instanceof TypeError)
throw new UsageError('rg: pattern given is not valid UTF-8');
throw error;
}
try {
return new RgProgram(await openStream('rg', streamConfig(pattern, neverMatch, f, regexSizeLimit, dfaSizeLimit)));
}
catch (error) {
if (error instanceof StreamRefusal)
throw new UsageError(`rg: ${error.message}`);
throw error;
}
}
export function settle(stream, tally) {
const progress = TALLY.parse(JSON.parse(DEC.decode(stream.metadata())));
tally.selected = progress.selected;
tally.binary = progress.binary;
tally.skipped = progress.skipped;
tally.matches = progress.matches;
tally.matchedLines = progress.matched_lines;
tally.searched = progress.searched;
tally.nul = progress.nul;
if (progress.error !== null)
tally.error = progress.error;
}
async function* drained(stream, output, tally, budget) {
for (;;) {
if (output.byteLength > 0) {
settle(stream, tally);
yield output;
}
if (!stream.busy())
return;
await budget.run();
output = stream.drain();
}
}
export function formatMessage(message) {
const stream = openStreamSync('rg_format', {});
try {
return stream.feed(ENC.encode(JSON.stringify(message)));
}
finally {
stream.close();
}
}
export async function* streamHaystack(source, program, f, name, label, tally, signal, named, pipe) {
const stream = program.stream;
const binaryMode = f.text || f.nullData
? 'text'
: f.json === true && (named || f.binary)
? 'note'
: named || f.binary
? 'convert'
: 'quit';
stream.begin({
name: latin1(name),
label: label === null ? null : latin1(label),
binary_mode: binaryMode,
encoding: f.encoding ?? 'auto',
pipe,
});
const budget = new YieldBudget(signal);
try {
if (!stream.done()) {
for await (const chunk of source) {
for (let offset = 0; offset < chunk.byteLength; offset += BLOCK_BYTES) {
if (stream.done())
break;
await budget.run();
yield* drained(stream, stream.feed(chunk.subarray(offset, offset + BLOCK_BYTES)), tally, budget);
}
if (stream.done())
break;
}
}
yield* drained(stream, stream.finish(), tally, budget);
}
catch (error) {
await discardStreams(source);
throw error;
}
finally {
settle(stream, tally);
await closeQuietly(source);
}
}
export function formatOutput(program, message) {
return program.stream.feedInput(2, ENC.encode(JSON.stringify(message)));
}
export function totalsConfig(totals) {
return {
matches: totals.matches,
matched_lines: totals.matchedLines,
with_match: totals.withMatch,
searched: totals.searched,
printed: totals.printed,
bytes_searched: totals.bytesSearched,
elapsed: totals.elapsed,
};
}
export async function formatTotals(totals, kind, total) {
const stream = await openStream('rg_format', {});
try {
return stream.feed(ENC.encode(JSON.stringify({ kind, totals: totalsConfig(totals), total })));
}
finally {
stream.close();
}
}
export function compiledTerms(program) {
return COMPILED.parse(JSON.parse(DEC.decode(program.stream.metadata())));
}
//# sourceMappingURL=rg_stream.js.map