@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
162 lines • 6.54 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. =========
// The interpreter-init switches, keyed by CPython's own letter. These
// configure the interpreter rather than selecting what it runs, so they
// ride in the run's `ctx.flags` and each engine answers the ones it can. `-q`
// is deliberately absent: mirage prints no banner, so it is a no-op in every
// runtime. `-u` is absent too, since only a spawned CPython buffers its
// output: initArgv hands it back there, and every other engine already hands
// each write over as it comes, so none reports it.
const BOOL_FLAGS = ['B', 'E', 'I', 'P', 's', 'S'];
const COUNT_FLAGS = ['O', 'b'];
const LIST_FLAGS = ['W', 'X'];
// The one init switch CPython spells long. Its key is the parser's
// canonical spelling rather than a letter, since it has none.
const VALUE_FLAGS = {
check_hash_based_pycs: '--check-hash-based-pycs',
};
// The -X names CPython gives an effect beyond landing in
// sys._xoptions, so a warm interpreter that only populates that dict
// has not honored them. Every one is read out of the read-only
// sys.flags or installed at interpreter start (dev, utf8,
// warn_default_encoding, importtime, frozen_modules, cpu_count,
// no_debug_ranges, perf*, showrefcount) or needs a library call this
// wrapper does not make (faulthandler, int_max_str_digits,
// pycache_prefix, tracemalloc). An arbitrary name is deliberately
// absent: on CPython it does nothing but land in the dict either.
// Pinned against `python3 --help-xoptions` on 3.13.7.
const X_EFFECTS = new Set([
'cpu_count',
'dev',
'faulthandler',
'frozen_modules',
'importtime',
'int_max_str_digits',
'no_debug_ranges',
'perf',
'perf_jit',
'pycache_prefix',
'showrefcount',
'tracemalloc',
'utf8',
'warn_default_encoding',
]);
/**
* A counted switch's level; 0 when absent.
*
* @param value the switch's value.
*/
function count(value) {
return typeof value === 'number' ? value : 0;
}
/**
* A repeated switch's values; none when absent.
*
* @param value the switch's value.
*/
function words(value) {
return Array.isArray(value)
? value.map((item) => (typeof item === 'string' ? item : item.virtual))
: [];
}
/**
* CPython command-line switches for one run's interpreter flags.
*
* For the engines that spawn a real interpreter, honoring these is just
* handing them back to CPython in its own spelling. `-O` and `-b` repeat
* rather than taking a number, so level 2 is `-O -O`, which CPython reads
* exactly as `-OO`. `-u` goes back first: a spawned CPython buffers stdout
* on a pipe, as it would anywhere. Mirrors Python's init_argv.
*
* @param flags the run's interpreter switches (`ctx.flags`).
*/
export function initArgv(flags) {
const argv = flags.u === true ? ['-u'] : [];
for (const key of BOOL_FLAGS) {
if (flags[key] === true)
argv.push(`-${key}`);
}
for (const key of COUNT_FLAGS) {
for (let i = 0; i < count(flags[key]); i++)
argv.push(`-${key}`);
}
for (const key of LIST_FLAGS) {
for (const value of words(flags[key]))
argv.push(`-${key}`, value);
}
for (const [key, spelling] of Object.entries(VALUE_FLAGS)) {
const setting = flags[key];
// CPython parses this one by hand and rejects --opt=value, so it
// goes back as two words whatever the user typed.
if (typeof setting === 'string' && setting !== '')
argv.push(spelling, setting);
}
return argv;
}
/**
* The init switches present on a line that this engine did not act on.
*
* A runtime that cannot act on these reports them rather than dropping
* them silently, so a line behaving differently across runtimes says so
* instead of being discovered later. `honored` is the engine's own
* subset, by CPython letter; the default is none, which is the honest
* answer for an engine that is not CPython at all.
*
* @param flags the run's interpreter switches (`ctx.flags`).
* @param honored the letters this engine does act on.
*/
function unhonored(flags, honored) {
const present = [];
for (const key of [...BOOL_FLAGS, ...COUNT_FLAGS, ...LIST_FLAGS]) {
if (honored.includes(key))
continue;
const value = flags[key];
const set = Array.isArray(value) ? value.length > 0 : value === true || count(value) > 0;
if (set)
present.push(`-${key}`);
}
for (const [key, spelling] of Object.entries(VALUE_FLAGS)) {
if (honored.includes(key))
continue;
if (flags[key])
present.push(spelling);
}
// Reported per name rather than per letter: an engine can honor what
// -X means for sys._xoptions and still not act on the handful of
// names CPython gives a real effect.
for (const value of words(flags.X)) {
const optName = value.split('=')[0] ?? '';
if (X_EFFECTS.has(optName) && !honored.includes(`X:${optName}`)) {
present.push(`-X ${optName}`);
}
}
return present;
}
/**
* One stderr line per init switch this runtime cannot act on.
*
* A warning rather than a refusal: the line still runs and still
* reports the program's own exit code, so a script that works on every
* other runtime keeps working here, while the difference stays visible
* instead of being found later.
*
* @param flags the run's interpreter switches (`ctx.flags`).
* @param runtimeName the engine's registry name, for the message.
* @param honored the letters this engine does act on.
*/
export function unhonoredNotice(flags, runtimeName, honored = []) {
const lines = unhonored(flags, honored).map((spelling) => `python3: warning: ${spelling} is ignored by the '${runtimeName}' runtime\n`);
return new TextEncoder().encode(lines.join(''));
}
//# sourceMappingURL=flags.js.map