@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
200 lines • 8.08 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 { JqCompileError } from "./errors.js";
import { MemoryExhausted } from "../../io/errors.js";
import { memoryRoom } from "../../io/memory.js";
import { YieldBudget } from "../../io/yield_budget.js";
import { StreamRefusal } from "../../rs/errors.js";
import { closeStream, openStream } from "../../rs/stream.js";
import { TZ_VAR } from "../../utils/timezone.js";
import { systemNow } from "../../utils/clock.js";
const DEC = new TextDecoder('utf-8', { ignoreBOM: true });
const ENC = new TextEncoder();
export const FRAME_OUTPUT = 1;
export const FRAME_ERROR = 2;
export const FRAME_HALT = 3;
export const FRAME_DEBUG = 4;
export const FRAME_STDERR = 5;
// The last line of jq's report of a program it refuses, which jq_eval's
// refusal to compile such a program ends in. Mirrors Python's
// COMPILE_REPORT.
export const COMPILE_REPORT = /(?:^|\n)jq: [0-9]+ compile errors?$/;
// The error jq_eval ends a run with once it holds more than its memory, with
// the bytes. Mirrors Python's MEMORY_REPORT.
export const MEMORY_REPORT = 'jq memory limit of {} bytes exceeded';
/**
* The time zone jq's local time reads, as a `TZ` value: the command
* environment's, else the process's, else the host's own zone.
*/
export function localZone(env) {
const host = globalThis;
return (env?.[TZ_VAR] ?? host.process?.env?.[TZ_VAR] ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
}
/**
* Fold one drain of mirage-rs's jq_eval into a run: each output frame is a
* compact dump, pushed onto `outputs`, a `debug` or `stderr` frame is
* dropped, and an error or a halt frame is what stopped the run. A frame is a
* tag, a little-endian u32 length and its payload; an error's flag byte says
* whether it is a string (bit 0) and whether the program raised it with
* `error` itself (bit 1).
*/
export function readFrames(bytes, outputs) {
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
let stop = null;
let at = 0;
while (at < bytes.byteLength) {
if (at + 5 > bytes.byteLength)
throw new Error('jq_eval: truncated frame header');
const tag = view.getUint8(at);
const end = at + 5 + view.getUint32(at + 1, true);
if (end > bytes.byteLength)
throw new Error('jq_eval: truncated frame');
const payload = bytes.subarray(at + 5, end);
at = end;
if (tag === FRAME_OUTPUT) {
outputs.push(payload);
}
else if (tag === FRAME_ERROR) {
const flags = payload[0] ?? 0;
stop = {
kind: 'error',
text: DEC.decode(payload.subarray(1)),
string: (flags & 1) === 1,
own: (flags & 2) === 2,
};
}
else if (tag === FRAME_HALT) {
const halt = JSON.parse(DEC.decode(payload));
stop = { kind: 'halt', message: halt.message, string: halt.string, code: halt.code };
}
else if (tag !== FRAME_DEBUG && tag !== FRAME_STDERR) {
throw new Error(`jq_eval: unknown frame ${String(tag)}`);
}
}
return stop;
}
/**
* A program compiled once by mirage-rs's jq_eval and run on one value at a
* time on this thread, a bounded number of VM steps per drain, so a program
* that never ends still yields and the signal can stop it between two.
* `input` finds no more input, `$ENV` is the given environment, and what
* `debug` and `stderr` write is dropped, as `gh --jq` evaluates. Mirrors
* Python's Engine.
*/
export class Engine {
stream;
memory;
budget;
// What the workspace's max_memory had left when the program compiled, the
// bytes the run may hold; null for jq_eval's own.
constructor(stream, memory, signal) {
this.stream = stream;
this.memory = memory;
this.budget = new YieldBudget(signal);
}
/**
* Compile `expr`, as typed, with `env` (JSON text) as `$ENV` and `env` and
* `zone` (localZone) as the `TZ` local time reads. Throws JqCompileError,
* with jq's report, for a program jq itself refuses.
*/
static async open(expr, env, zone, signal) {
let stream;
const limit = memoryRoom();
try {
stream = await openStream('jq_eval', {
program: expr,
env,
zone,
...(limit === null ? {} : { memory: limit }),
});
}
catch (error) {
if (error instanceof StreamRefusal && COMPILE_REPORT.test(error.message)) {
throw new JqCompileError(error.message);
}
throw error;
}
return new Engine(stream, limit, signal);
}
/**
* Run the program on one value's JSON text, yielding each output's compact
* dump as a drain decodes it, and leave on `ending` how the run stopped. A
* run its caller stops reading is cancelled, so the next can begin.
*/
async *run(doc, ending) {
const stream = this.stream;
stream.begin({ filename: null, line: 0 });
const outputs = [];
let stop;
// Whether the caller stopped reading while the run was still going.
let abandoned = true;
try {
stop = readFrames(stream.feed(ENC.encode(doc)), outputs);
stop = readFrames(stream.finish(), outputs) ?? stop;
for (;;) {
for (const output of outputs)
yield DEC.decode(output);
outputs.length = 0;
let wire;
if (stream.busy()) {
const pending = this.budget.run();
if (pending !== undefined)
await pending;
wire = stream.drain();
}
else {
const asked = stream.request();
if (asked === null)
break;
if (asked[0].op === 'now') {
wire = stream.respond({ kind: 'now', now: systemNow() }, new Uint8Array());
}
else if (asked[0].op === 'input') {
wire = stream.respond({ kind: 'end' }, new Uint8Array());
}
else {
throw new Error(`jq_eval: unknown host request ${JSON.stringify(asked[0])}`);
}
}
stop = readFrames(wire, outputs) ?? stop;
}
abandoned = false;
}
catch (error) {
abandoned = false;
throw error;
}
finally {
if (abandoned && stream.cancel()) {
while (stream.busy()) {
const pending = this.budget.run();
if (pending !== undefined)
await pending;
stream.drain();
}
}
}
if (this.memory !== null &&
stop?.kind === 'error' &&
stop.text === MEMORY_REPORT.replace('{}', String(this.memory))) {
throw new MemoryExhausted();
}
ending.stop = stop;
}
/** Close the program; a run cut short is retired a bounded slice at a time. */
close() {
return closeStream(this.stream);
}
}
//# sourceMappingURL=eval.js.map