use std::collections::BTreeSet;use std::env;use std::fs;use std::io;use std::path::{Path, PathBuf};use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};use std::sync::{Arc, Mutex, OnceLock};use std::time::Duration; use agentos_runtime::RuntimeContext;use tokio::sync::Mutex as AsyncMutex;use tokio::time; pub(crate) const NODE_IMPORT_CACHE_DEBUG_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_DEBUG";pub(crate) const NODE_IMPORT_CACHE_METRICS_PREFIX: &str = "__AGENTOS_NODE_IMPORT_CACHE_METRICS__:";pub(crate) const NODE_IMPORT_CACHE_ASSET_ROOT_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_ASSET_ROOT"; const NODE_IMPORT_CACHE_PATH_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_PATH";const NODE_IMPORT_CACHE_LOADER_PATH_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_LOADER_PATH";const NODE_IMPORT_CACHE_SCHEMA_VERSION: &str = "1";const NODE_IMPORT_CACHE_LOADER_VERSION: &str = "8";// Upstream reached 104 while the reactor branch independently changed bundled// assets; use a new generation so no stale materialization survives the merge.const NODE_IMPORT_CACHE_ASSET_VERSION: &str = "105";const NODE_IMPORT_CACHE_DIR_PREFIX: &str = "agentos-node-import-cache";const DEFAULT_NODE_IMPORT_CACHE_MATERIALIZE_TIMEOUT: Duration = Duration::from_secs(30);const NODE_IMPORT_CACHE_BLOCKING_JOB_RESERVATION_BYTES: usize = 64 * 1024;const PYODIDE_DIST_DIR: &str = "pyodide-dist";const AGENTOS_BUILTIN_SPECIFIER_PREFIX: &str = "secure-exec:builtin/";const AGENTOS_POLYFILL_SPECIFIER_PREFIX: &str = "secure-exec:polyfill/";const BUNDLED_PYODIDE_MJS: &[u8] = include_bytes!("../assets/pyodide/pyodide.mjs");// Large Pyodide assets are excluded from the published crate and staged into// OUT_DIR by build.rs (copied from `assets/pyodide/` in-tree, or downloaded// from the release CDN when building the published crate).const BUNDLED_PYODIDE_ASM_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/pyodide.asm.js"));const BUNDLED_PYODIDE_ASM_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/pyodide.asm.wasm"));const BUNDLED_PYODIDE_LOCK: &[u8] = include_bytes!("../assets/pyodide/pyodide-lock.json");const BUNDLED_PYTHON_STDLIB_ZIP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/python_stdlib.zip"));const BUNDLED_NUMPY_WHL: &[u8] = include_bytes!(concat!( env!("OUT_DIR"), "/pyodide/numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl"));const BUNDLED_PANDAS_WHL: &[u8] = include_bytes!(concat!( env!("OUT_DIR"), "/pyodide/pandas-2.3.3-cp313-cp313-pyodide_2025_0_wasm32.whl"));const BUNDLED_PYTHON_DATEUTIL_WHL: &[u8] = include_bytes!("../assets/pyodide/python_dateutil-2.9.0.post0-py2.py3-none-any.whl");const BUNDLED_PYTZ_WHL: &[u8] = include_bytes!("../assets/pyodide/pytz-2025.2-py2.py3-none-any.whl");const BUNDLED_SIX_WHL: &[u8] = include_bytes!("../assets/pyodide/six-1.17.0-py2.py3-none-any.whl");const BUNDLED_MICROPIP_WHL: &[u8] = include_bytes!("../assets/pyodide/micropip-0.11.0-py3-none-any.whl");const BUNDLED_CLICK_WHL: &[u8] = include_bytes!("../assets/pyodide/click-8.3.1-py3-none-any.whl");const NODE_PYTHON_RUNNER_SOURCE: &str = include_str!("../assets/runners/python-runner.mjs"); static CLEANED_NODE_IMPORT_CACHE_ROOTS: OnceLock<Mutex<BTreeSet<PathBuf>>> = OnceLock::new();#[cfg(test)]static NODE_IMPORT_CACHE_TEST_MATERIALIZE_DELAY_MS: AtomicU64 = AtomicU64::new(0);#[cfg(test)]static NODE_IMPORT_CACHE_TEST_MATERIALIZE_DELAY_LOCK: Mutex<()> = Mutex::new(()); #[derive(Clone, Copy)]struct BundledPyodidePackageAsset { file_name: &'static str, bytes: &'static [u8],} const BUNDLED_PYODIDE_PACKAGE_ASSETS: &[BundledPyodidePackageAsset] = &[ BundledPyodidePackageAsset { file_name: "numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl", bytes: BUNDLED_NUMPY_WHL, }, BundledPyodidePackageAsset { file_name: "pandas-2.3.3-cp313-cp313-pyodide_2025_0_wasm32.whl", bytes: BUNDLED_PANDAS_WHL, }, BundledPyodidePackageAsset { file_name: "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", bytes: BUNDLED_PYTHON_DATEUTIL_WHL, }, BundledPyodidePackageAsset { file_name: "pytz-2025.2-py2.py3-none-any.whl", bytes: BUNDLED_PYTZ_WHL, }, BundledPyodidePackageAsset { file_name: "six-1.17.0-py2.py3-none-any.whl", bytes: BUNDLED_SIX_WHL, }, BundledPyodidePackageAsset { file_name: "micropip-0.11.0-py3-none-any.whl", bytes: BUNDLED_MICROPIP_WHL, }, BundledPyodidePackageAsset { file_name: "click-8.3.1-py3-none-any.whl", bytes: BUNDLED_CLICK_WHL, },];const NODE_IMPORT_CACHE_LOADER_TEMPLATE: &str = r#"import crypto from 'node:crypto';import fs from 'node:fs';import path from 'node:path';import { fileURLToPath, pathToFileURL } from 'node:url'; const GUEST_PATH_MAPPINGS = parseGuestPathMappings(process.env.AGENTOS_GUEST_PATH_MAPPINGS);const ALLOWED_BUILTINS = new Set(parseJsonArray(process.env.AGENTOS_ALLOWED_NODE_BUILTINS));const CACHE_PATH = process.env.__NODE_IMPORT_CACHE_PATH_ENV__;const CACHE_ROOT = CACHE_PATH ? path.dirname(CACHE_PATH) : null;const GUEST_INTERNAL_CACHE_ROOT = '/.agentos/node-import-cache';const HOST_CWD = process.cwd();const DEFAULT_GUEST_CWD = typeof process.env.PWD === 'string' && process.env.PWD.startsWith('/') ? path.posix.normalize(process.env.PWD) : typeof (globalThis.__agentOSVirtualOs||{}).homedir === 'string' && (globalThis.__agentOSVirtualOs||{}).homedir.startsWith('/') ? path.posix.normalize((globalThis.__agentOSVirtualOs||{}).homedir) : '/root';const UNMAPPED_GUEST_PATH = '/unknown';const PROJECTED_SOURCE_CACHE_ROOT = CACHE_PATH ? path.join(path.dirname(CACHE_PATH), 'projected-sources') : null;const ASSET_ROOT = process.env.__NODE_IMPORT_CACHE_ASSET_ROOT_ENV__;const DEBUG_ENABLED = process.env.__NODE_IMPORT_CACHE_DEBUG_ENV__ === '1';const CONTROL_PIPE_FD = parseControlPipeFd(process.env.AGENTOS_CONTROL_PIPE_FD);const SCHEMA_VERSION = '__NODE_IMPORT_CACHE_SCHEMA_VERSION__';const LOADER_VERSION = '__NODE_IMPORT_CACHE_LOADER_VERSION__';const ASSET_VERSION = '__NODE_IMPORT_CACHE_ASSET_VERSION__';const MAX_CACHE_RECORD_ENTRIES = 512;const MAX_CACHE_KEY_BYTES = 4096;const MAX_CACHE_VALUE_BYTES = 16 * 1024;const MAX_CACHE_STATE_BYTES = 4 * 1024 * 1024;const BUILTIN_PREFIX = '__AGENTOS_BUILTIN_SPECIFIER_PREFIX__';const POLYFILL_PREFIX = '__AGENTOS_POLYFILL_SPECIFIER_PREFIX__';const FS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}fs`;const FS_PROMISES_ASSET_SPECIFIER = `${BUILTIN_PREFIX}fs-promises`;const CHILD_PROCESS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}child-process`;const NET_ASSET_SPECIFIER = `${BUILTIN_PREFIX}net`;const DGRAM_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dgram`;const DNS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dns`;const DNS_PROMISES_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dns-promises`;const HTTP_ASSET_SPECIFIER = `${BUILTIN_PREFIX}http`;const HTTP2_ASSET_SPECIFIER = `${BUILTIN_PREFIX}http2`;const HTTPS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}https`;const TLS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}tls`;const OS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}os`;const DENIED_BUILTINS = new Set([ 'child_process', 'cluster', 'dgram', 'dns', 'dns/promises', 'http', 'http2', 'https', 'inspector', 'module', 'net', 'tls', 'trace_events', 'v8', 'vm', 'worker_threads',].filter((name) => !ALLOWED_BUILTINS.has(name))); let cacheState = loadCacheState();let dirty = false;let cacheWriteError = null;const metrics = { resolveHits: 0, resolveMisses: 0, packageTypeHits: 0, packageTypeMisses: 0, moduleFormatHits: 0, moduleFormatMisses: 0, sourceHits: 0, sourceMisses: 0,}; export async function resolve(specifier, context, nextResolve) { const guestResolvedPath = resolveGuestSpecifier(specifier, context); if (guestResolvedPath) { const guestUrl = pathToFileURL(guestResolvedPath).href; const format = lookupModuleFormat(guestUrl); flushCacheState(); emitMetrics(); return { shortCircuit: true, url: guestUrl, ...(format && format !== 'builtin' ? { format } : {}), }; } const key = createResolutionKey(specifier, context); const cached = cacheState.resolutions[key]; if (cached && validateResolutionEntry(cached)) { metrics.resolveHits += 1; const response = { shortCircuit: true, url: cached.resolvedUrl, }; if (cached.format) { response.format = cached.format; } flushCacheState(); emitMetrics(); return response; } metrics.resolveMisses += 1; const asset = resolveSecureExecAsset(specifier); if (asset) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: asset.url, format: 'module', resolvedFilePath: asset.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: asset.url, format: 'module', }; } const builtinAsset = resolveBuiltinAsset(specifier, context); if (builtinAsset) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: builtinAsset.url, format: 'module', resolvedFilePath: builtinAsset.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: builtinAsset.url, format: 'module', }; } const deniedBuiltin = resolveDeniedBuiltin(specifier); if (deniedBuiltin) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: deniedBuiltin.url, format: 'module', resolvedFilePath: deniedBuiltin.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: deniedBuiltin.url, format: 'module', }; } const translatedContext = translateContextParentUrl(context); let resolved; try { resolved = await nextResolve(specifier, translatedContext); } catch (error) { flushCacheState(); emitMetrics(); throw translateErrorToGuest(error); } const translatedUrl = translateResolvedUrlToGuest(resolved.url); const translatedResolved = translatedUrl === resolved.url ? resolved : { ...resolved, url: translatedUrl }; const entry = buildResolutionEntry(specifier, context, translatedResolved); if (entry) { cacheState.resolutions[key] = entry; dirty = true; } if (entry && entry.format && resolved.format == null) { flushCacheState(); emitMetrics(); return { ...translatedResolved, format: entry.format, }; } flushCacheState(); emitMetrics(); return translatedResolved;} export async function load(url, context, nextLoad) { try { const filePath = filePathFromUrl(url); const format = lookupModuleFormat(url) ?? context.format; if (!filePath || !format || format === 'builtin') { return await nextLoad(url, context); } const projectedPackageSource = loadProjectedPackageSource(url, filePath, format); if (projectedPackageSource != null) { flushCacheState(); emitMetrics(); return { shortCircuit: true, format, source: projectedPackageSource, }; } const source = format === 'wasm' ? fs.readFileSync(filePath) : rewriteBuiltinImports(fs.readFileSync(filePath, 'utf8'), filePath); return { shortCircuit: true, format, source, }; } catch (error) { flushCacheState(); emitMetrics(); throw translateErrorToGuest(error); }} function loadCacheState() { if (!CACHE_PATH) { return emptyCacheState(); } try { const stat = fs.statSync(CACHE_PATH); if (!stat.isFile() || stat.size > MAX_CACHE_STATE_BYTES) { return emptyCacheState(); } const parsed = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8')); if (!isCompatibleCacheState(parsed)) { return emptyCacheState(); } return normalizeCacheState(parsed); } catch { return emptyCacheState(); }} function flushCacheState() { if (!CACHE_PATH || !dirty) { return; } try { fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true }); let merged = cacheState; try { const existingStat = fs.statSync(CACHE_PATH); if (existingStat.isFile() && existingStat.size <= MAX_CACHE_STATE_BYTES) { const existing = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8')); if (isCompatibleCacheState(existing)) { merged = mergeCacheStates(normalizeCacheState(existing), cacheState); } } } catch { // Ignore missing or unreadable prior state and replace it with the in-memory view. } merged = pruneCacheState(merged); let serialized = JSON.stringify(merged); if (byteLengthUtf8(serialized) > MAX_CACHE_STATE_BYTES) { merged = pruneCacheState(merged, Math.floor(MAX_CACHE_RECORD_ENTRIES / 4)); serialized = JSON.stringify(merged); } if (byteLengthUtf8(serialized) > MAX_CACHE_STATE_BYTES) { merged = emptyCacheState(); serialized = JSON.stringify(merged); } const tempPath = `${CACHE_PATH}.${process.pid}.${Date.now()}.tmp`; fs.writeFileSync(tempPath, serialized); fs.renameSync(tempPath, CACHE_PATH); cacheState = merged; pruneProjectedSourceFiles(); dirty = false; } catch (error) { cacheWriteError = error instanceof Error ? error.message : String(error); }} function emitMetrics() { if (!DEBUG_ENABLED) { return; } const payload = cacheWriteError ? { ...metrics, cacheWriteError } : metrics; emitControlMessage({ type: 'node_import_cache_metrics', metrics: payload });} function parseControlPipeFd(value) { if (typeof value !== 'string' || value.trim() === '') { return null; } const parsed = Number.parseInt(value, 10); return Number.isInteger(parsed) && parsed >= 3 ? parsed : null;} function emitControlMessage(message) { if (CONTROL_PIPE_FD == null) { if ( message?.type === 'signal_state' && typeof process?.stdout?.write === 'function' ) { try { process.stdout.write(`__AGENTOS_WASM_SIGNAL_STATE__:${JSON.stringify(message)}\n`); } catch { // Ignore control-channel fallback failures during teardown. } } return; } try { fs.writeSync(CONTROL_PIPE_FD, `${JSON.stringify(message)}\n`); } catch { if ( message?.type === 'signal_state' && typeof process?.stdout?.write === 'function' ) { try { process.stdout.write(`__AGENTOS_WASM_SIGNAL_STATE__:${JSON.stringify(message)}\n`); } catch { // Ignore control-channel fallback failures during teardown. } } }} function emptyCacheState() { return { schemaVersion: SCHEMA_VERSION, loaderVersion: LOADER_VERSION, assetVersion: ASSET_VERSION, nodeVersion: process.version, resolutions: {}, packageTypes: {}, moduleFormats: {}, projectedSources: {}, };} function isCompatibleCacheState(value) { return ( isRecord(value) && value.schemaVersion === SCHEMA_VERSION && value.loaderVersion === LOADER_VERSION && value.assetVersion === ASSET_VERSION && value.nodeVersion === process.version );} function normalizeCacheState(value) { return pruneCacheState({ ...emptyCacheState(), ...value, resolutions: isRecord(value.resolutions) ? value.resolutions : {}, packageTypes: isRecord(value.packageTypes) ? value.packageTypes : {}, moduleFormats: isRecord(value.moduleFormats) ? value.moduleFormats : {}, projectedSources: isRecord(value.projectedSources) ? value.projectedSources : {}, });} function mergeCacheStates(base, current) { return pruneCacheState({ ...emptyCacheState(), resolutions: { ...base.resolutions, ...current.resolutions, }, packageTypes: { ...base.packageTypes, ...current.packageTypes,Lines 1–500 of 5037. Load more
use std::collections::BTreeSet; use std::env; use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Duration; use agentos_runtime::RuntimeContext; use tokio::sync::Mutex as AsyncMutex; use tokio::time; pub(crate) const NODE_IMPORT_CACHE_DEBUG_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_DEBUG"; pub(crate) const NODE_IMPORT_CACHE_METRICS_PREFIX: &str = "__AGENTOS_NODE_IMPORT_CACHE_METRICS__:"; pub(crate) const NODE_IMPORT_CACHE_ASSET_ROOT_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_ASSET_ROOT"; const NODE_IMPORT_CACHE_PATH_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_PATH"; const NODE_IMPORT_CACHE_LOADER_PATH_ENV: &str = "AGENTOS_NODE_IMPORT_CACHE_LOADER_PATH"; const NODE_IMPORT_CACHE_SCHEMA_VERSION: &str = "1"; const NODE_IMPORT_CACHE_LOADER_VERSION: &str = "8"; // Upstream reached 104 while the reactor branch independently changed bundled // assets; use a new generation so no stale materialization survives the merge. const NODE_IMPORT_CACHE_ASSET_VERSION: &str = "105"; const NODE_IMPORT_CACHE_DIR_PREFIX: &str = "agentos-node-import-cache"; const DEFAULT_NODE_IMPORT_CACHE_MATERIALIZE_TIMEOUT: Duration = Duration::from_secs(30); const NODE_IMPORT_CACHE_BLOCKING_JOB_RESERVATION_BYTES: usize = 64 * 1024; const PYODIDE_DIST_DIR: &str = "pyodide-dist"; const AGENTOS_BUILTIN_SPECIFIER_PREFIX: &str = "secure-exec:builtin/"; const AGENTOS_POLYFILL_SPECIFIER_PREFIX: &str = "secure-exec:polyfill/"; const BUNDLED_PYODIDE_MJS: &[u8] = include_bytes!("../assets/pyodide/pyodide.mjs"); // Large Pyodide assets are excluded from the published crate and staged into // OUT_DIR by build.rs (copied from `assets/pyodide/` in-tree, or downloaded // from the release CDN when building the published crate). const BUNDLED_PYODIDE_ASM_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/pyodide.asm.js")); const BUNDLED_PYODIDE_ASM_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/pyodide.asm.wasm")); const BUNDLED_PYODIDE_LOCK: &[u8] = include_bytes!("../assets/pyodide/pyodide-lock.json"); const BUNDLED_PYTHON_STDLIB_ZIP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pyodide/python_stdlib.zip")); const BUNDLED_NUMPY_WHL: &[u8] = include_bytes!(concat!( env!("OUT_DIR"), "/pyodide/numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl" )); const BUNDLED_PANDAS_WHL: &[u8] = include_bytes!(concat!( env!("OUT_DIR"), "/pyodide/pandas-2.3.3-cp313-cp313-pyodide_2025_0_wasm32.whl" )); const BUNDLED_PYTHON_DATEUTIL_WHL: &[u8] = include_bytes!("../assets/pyodide/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"); const BUNDLED_PYTZ_WHL: &[u8] = include_bytes!("../assets/pyodide/pytz-2025.2-py2.py3-none-any.whl"); const BUNDLED_SIX_WHL: &[u8] = include_bytes!("../assets/pyodide/six-1.17.0-py2.py3-none-any.whl"); const BUNDLED_MICROPIP_WHL: &[u8] = include_bytes!("../assets/pyodide/micropip-0.11.0-py3-none-any.whl"); const BUNDLED_CLICK_WHL: &[u8] = include_bytes!("../assets/pyodide/click-8.3.1-py3-none-any.whl"); const NODE_PYTHON_RUNNER_SOURCE: &str = include_str!("../assets/runners/python-runner.mjs"); static CLEANED_NODE_IMPORT_CACHE_ROOTS: OnceLock<Mutex<BTreeSet<PathBuf>>> = OnceLock::new(); #[cfg(test)] static NODE_IMPORT_CACHE_TEST_MATERIALIZE_DELAY_MS: AtomicU64 = AtomicU64::new(0); #[cfg(test)] static NODE_IMPORT_CACHE_TEST_MATERIALIZE_DELAY_LOCK: Mutex<()> = Mutex::new(()); #[derive(Clone, Copy)] struct BundledPyodidePackageAsset { file_name: &'static str, bytes: &'static [u8], } const BUNDLED_PYODIDE_PACKAGE_ASSETS: &[BundledPyodidePackageAsset] = &[ BundledPyodidePackageAsset { file_name: "numpy-2.2.5-cp313-cp313-pyodide_2025_0_wasm32.whl", bytes: BUNDLED_NUMPY_WHL, }, BundledPyodidePackageAsset { file_name: "pandas-2.3.3-cp313-cp313-pyodide_2025_0_wasm32.whl", bytes: BUNDLED_PANDAS_WHL, }, BundledPyodidePackageAsset { file_name: "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", bytes: BUNDLED_PYTHON_DATEUTIL_WHL, }, BundledPyodidePackageAsset { file_name: "pytz-2025.2-py2.py3-none-any.whl", bytes: BUNDLED_PYTZ_WHL, }, BundledPyodidePackageAsset { file_name: "six-1.17.0-py2.py3-none-any.whl", bytes: BUNDLED_SIX_WHL, }, BundledPyodidePackageAsset { file_name: "micropip-0.11.0-py3-none-any.whl", bytes: BUNDLED_MICROPIP_WHL, }, BundledPyodidePackageAsset { file_name: "click-8.3.1-py3-none-any.whl", bytes: BUNDLED_CLICK_WHL, }, ]; const NODE_IMPORT_CACHE_LOADER_TEMPLATE: &str = r#" import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const GUEST_PATH_MAPPINGS = parseGuestPathMappings(process.env.AGENTOS_GUEST_PATH_MAPPINGS); const ALLOWED_BUILTINS = new Set(parseJsonArray(process.env.AGENTOS_ALLOWED_NODE_BUILTINS)); const CACHE_PATH = process.env.__NODE_IMPORT_CACHE_PATH_ENV__; const CACHE_ROOT = CACHE_PATH ? path.dirname(CACHE_PATH) : null; const GUEST_INTERNAL_CACHE_ROOT = '/.agentos/node-import-cache'; const HOST_CWD = process.cwd(); const DEFAULT_GUEST_CWD = typeof process.env.PWD === 'string' && process.env.PWD.startsWith('/') ? path.posix.normalize(process.env.PWD) : typeof (globalThis.__agentOSVirtualOs||{}).homedir === 'string' && (globalThis.__agentOSVirtualOs||{}).homedir.startsWith('/') ? path.posix.normalize((globalThis.__agentOSVirtualOs||{}).homedir) : '/root'; const UNMAPPED_GUEST_PATH = '/unknown'; const PROJECTED_SOURCE_CACHE_ROOT = CACHE_PATH ? path.join(path.dirname(CACHE_PATH), 'projected-sources') : null; const ASSET_ROOT = process.env.__NODE_IMPORT_CACHE_ASSET_ROOT_ENV__; const DEBUG_ENABLED = process.env.__NODE_IMPORT_CACHE_DEBUG_ENV__ === '1'; const CONTROL_PIPE_FD = parseControlPipeFd(process.env.AGENTOS_CONTROL_PIPE_FD); const SCHEMA_VERSION = '__NODE_IMPORT_CACHE_SCHEMA_VERSION__'; const LOADER_VERSION = '__NODE_IMPORT_CACHE_LOADER_VERSION__'; const ASSET_VERSION = '__NODE_IMPORT_CACHE_ASSET_VERSION__'; const MAX_CACHE_RECORD_ENTRIES = 512; const MAX_CACHE_KEY_BYTES = 4096; const MAX_CACHE_VALUE_BYTES = 16 * 1024; const MAX_CACHE_STATE_BYTES = 4 * 1024 * 1024; const BUILTIN_PREFIX = '__AGENTOS_BUILTIN_SPECIFIER_PREFIX__'; const POLYFILL_PREFIX = '__AGENTOS_POLYFILL_SPECIFIER_PREFIX__'; const FS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}fs`; const FS_PROMISES_ASSET_SPECIFIER = `${BUILTIN_PREFIX}fs-promises`; const CHILD_PROCESS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}child-process`; const NET_ASSET_SPECIFIER = `${BUILTIN_PREFIX}net`; const DGRAM_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dgram`; const DNS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dns`; const DNS_PROMISES_ASSET_SPECIFIER = `${BUILTIN_PREFIX}dns-promises`; const HTTP_ASSET_SPECIFIER = `${BUILTIN_PREFIX}http`; const HTTP2_ASSET_SPECIFIER = `${BUILTIN_PREFIX}http2`; const HTTPS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}https`; const TLS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}tls`; const OS_ASSET_SPECIFIER = `${BUILTIN_PREFIX}os`; const DENIED_BUILTINS = new Set([ 'child_process', 'cluster', 'dgram', 'dns', 'dns/promises', 'http', 'http2', 'https', 'inspector', 'module', 'net', 'tls', 'trace_events', 'v8', 'vm', 'worker_threads', ].filter((name) => !ALLOWED_BUILTINS.has(name))); let cacheState = loadCacheState(); let dirty = false; let cacheWriteError = null; const metrics = { resolveHits: 0, resolveMisses: 0, packageTypeHits: 0, packageTypeMisses: 0, moduleFormatHits: 0, moduleFormatMisses: 0, sourceHits: 0, sourceMisses: 0, }; export async function resolve(specifier, context, nextResolve) { const guestResolvedPath = resolveGuestSpecifier(specifier, context); if (guestResolvedPath) { const guestUrl = pathToFileURL(guestResolvedPath).href; const format = lookupModuleFormat(guestUrl); flushCacheState(); emitMetrics(); return { shortCircuit: true, url: guestUrl, ...(format && format !== 'builtin' ? { format } : {}), }; } const key = createResolutionKey(specifier, context); const cached = cacheState.resolutions[key]; if (cached && validateResolutionEntry(cached)) { metrics.resolveHits += 1; const response = { shortCircuit: true, url: cached.resolvedUrl, }; if (cached.format) { response.format = cached.format; } flushCacheState(); emitMetrics(); return response; } metrics.resolveMisses += 1; const asset = resolveSecureExecAsset(specifier); if (asset) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: asset.url, format: 'module', resolvedFilePath: asset.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: asset.url, format: 'module', }; } const builtinAsset = resolveBuiltinAsset(specifier, context); if (builtinAsset) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: builtinAsset.url, format: 'module', resolvedFilePath: builtinAsset.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: builtinAsset.url, format: 'module', }; } const deniedBuiltin = resolveDeniedBuiltin(specifier); if (deniedBuiltin) { cacheState.resolutions[key] = { kind: 'explicit-file', resolvedUrl: deniedBuiltin.url, format: 'module', resolvedFilePath: deniedBuiltin.filePath, }; dirty = true; flushCacheState(); emitMetrics(); return { shortCircuit: true, url: deniedBuiltin.url, format: 'module', }; } const translatedContext = translateContextParentUrl(context); let resolved; try { resolved = await nextResolve(specifier, translatedContext); } catch (error) { flushCacheState(); emitMetrics(); throw translateErrorToGuest(error); } const translatedUrl = translateResolvedUrlToGuest(resolved.url); const translatedResolved = translatedUrl === resolved.url ? resolved : { ...resolved, url: translatedUrl }; const entry = buildResolutionEntry(specifier, context, translatedResolved); if (entry) { cacheState.resolutions[key] = entry; dirty = true; } if (entry && entry.format && resolved.format == null) { flushCacheState(); emitMetrics(); return { ...translatedResolved, format: entry.format, }; } flushCacheState(); emitMetrics(); return translatedResolved; } export async function load(url, context, nextLoad) { try { const filePath = filePathFromUrl(url); const format = lookupModuleFormat(url) ?? context.format; if (!filePath || !format || format === 'builtin') { return await nextLoad(url, context); } const projectedPackageSource = loadProjectedPackageSource(url, filePath, format); if (projectedPackageSource != null) { flushCacheState(); emitMetrics(); return { shortCircuit: true, format, source: projectedPackageSource, }; } const source = format === 'wasm' ? fs.readFileSync(filePath) : rewriteBuiltinImports(fs.readFileSync(filePath, 'utf8'), filePath); return { shortCircuit: true, format, source, }; } catch (error) { flushCacheState(); emitMetrics(); throw translateErrorToGuest(error); } } function loadCacheState() { if (!CACHE_PATH) { return emptyCacheState(); } try { const stat = fs.statSync(CACHE_PATH); if (!stat.isFile() || stat.size > MAX_CACHE_STATE_BYTES) { return emptyCacheState(); } const parsed = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8')); if (!isCompatibleCacheState(parsed)) { return emptyCacheState(); } return normalizeCacheState(parsed); } catch { return emptyCacheState(); } } function flushCacheState() { if (!CACHE_PATH || !dirty) { return; } try { fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true }); let merged = cacheState; try { const existingStat = fs.statSync(CACHE_PATH); if (existingStat.isFile() && existingStat.size <= MAX_CACHE_STATE_BYTES) { const existing = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8')); if (isCompatibleCacheState(existing)) { merged = mergeCacheStates(normalizeCacheState(existing), cacheState); } } } catch { // Ignore missing or unreadable prior state and replace it with the in-memory view. } merged = pruneCacheState(merged); let serialized = JSON.stringify(merged); if (byteLengthUtf8(serialized) > MAX_CACHE_STATE_BYTES) { merged = pruneCacheState(merged, Math.floor(MAX_CACHE_RECORD_ENTRIES / 4)); serialized = JSON.stringify(merged); } if (byteLengthUtf8(serialized) > MAX_CACHE_STATE_BYTES) { merged = emptyCacheState(); serialized = JSON.stringify(merged); } const tempPath = `${CACHE_PATH}.${process.pid}.${Date.now()}.tmp`; fs.writeFileSync(tempPath, serialized); fs.renameSync(tempPath, CACHE_PATH); cacheState = merged; pruneProjectedSourceFiles(); dirty = false; } catch (error) { cacheWriteError = error instanceof Error ? error.message : String(error); } } function emitMetrics() { if (!DEBUG_ENABLED) { return; } const payload = cacheWriteError ? { ...metrics, cacheWriteError } : metrics; emitControlMessage({ type: 'node_import_cache_metrics', metrics: payload }); } function parseControlPipeFd(value) { if (typeof value !== 'string' || value.trim() === '') { return null; } const parsed = Number.parseInt(value, 10); return Number.isInteger(parsed) && parsed >= 3 ? parsed : null; } function emitControlMessage(message) { if (CONTROL_PIPE_FD == null) { if ( message?.type === 'signal_state' && typeof process?.stdout?.write === 'function' ) { try { process.stdout.write(`__AGENTOS_WASM_SIGNAL_STATE__:${JSON.stringify(message)}\n`); } catch { // Ignore control-channel fallback failures during teardown. } } return; } try { fs.writeSync(CONTROL_PIPE_FD, `${JSON.stringify(message)}\n`); } catch { if ( message?.type === 'signal_state' && typeof process?.stdout?.write === 'function' ) { try { process.stdout.write(`__AGENTOS_WASM_SIGNAL_STATE__:${JSON.stringify(message)}\n`); } catch { // Ignore control-channel fallback failures during teardown. } } } } function emptyCacheState() { return { schemaVersion: SCHEMA_VERSION, loaderVersion: LOADER_VERSION, assetVersion: ASSET_VERSION, nodeVersion: process.version, resolutions: {}, packageTypes: {}, moduleFormats: {}, projectedSources: {}, }; } function isCompatibleCacheState(value) { return ( isRecord(value) && value.schemaVersion === SCHEMA_VERSION && value.loaderVersion === LOADER_VERSION && value.assetVersion === ASSET_VERSION && value.nodeVersion === process.version ); } function normalizeCacheState(value) { return pruneCacheState({ ...emptyCacheState(), ...value, resolutions: isRecord(value.resolutions) ? value.resolutions : {}, packageTypes: isRecord(value.packageTypes) ? value.packageTypes : {}, moduleFormats: isRecord(value.moduleFormats) ? value.moduleFormats : {}, projectedSources: isRecord(value.projectedSources) ? value.projectedSources : {}, }); } function mergeCacheStates(base, current) { return pruneCacheState({ ...emptyCacheState(), resolutions: { ...base.resolutions, ...current.resolutions, }, packageTypes: { ...base.packageTypes, ...current.packageTypes,