apodex/frontier-agent-demo
14
1"""Runtime discovery and prompt guidance for the baked document Node toolchain."""2 3from __future__ import annotations4 5import json6import os7import shutil8import subprocess9from dataclasses import dataclass10from pathlib import Path11 12from plugins.tools._sandbox import is_e2b_available13 14_DOCUMENT_PACKAGES = ("docx", "pptxgenjs")15_BWRAP_VISIBLE_ROOTS = (16 Path("/usr"),17 Path("/bin"),18 Path("/lib"),19 Path("/lib64"),20 Path("/etc"),21)22 23 24@dataclass(frozen=True)25class DocumentNodeToolchain:26 """Versions proven available to the active model-authored command backend."""27 28 node_version: str29 docx_version: str30 pptxgenjs_version: str31 32 33def _is_within(path: Path, roots: tuple[Path, ...]) -> bool:34 resolved = path.resolve()35 for root in roots:36 try:37 resolved.relative_to(root)38 return True39 except ValueError:40 continue41 return False42 43 44def _package_version(package: str, roots: tuple[Path, ...]) -> tuple[str, Path] | None:45 for root in roots:46 manifest = root / package / "package.json"47 if not manifest.is_file():48 continue49 try:50 data = json.loads(manifest.read_text(encoding="utf-8"))51 except (OSError, json.JSONDecodeError, TypeError):52 continue53 version = str(data.get("version") or "").strip()54 if version:55 return version, manifest56 return None57 58 59def discover_document_node_toolchain(60 *,61 sandbox_mode: str,62) -> DocumentNodeToolchain | None:63 """Return the document Node stack visible to model-authored commands.64 65 ``container`` executes in the current image. ``bwrap`` (and ``auto``66 without E2B) can see only paths mounted into the jail, so both the Node67 binary and module roots must live under its read-only system mounts.68 Explicit/auto E2B executes off-host and must never inherit a capability69 merely because the serving process itself has it installed.70 """71 mode = (sandbox_mode or "auto").strip().lower()72 if mode not in {"container", "native", "bwrap"} and is_e2b_available():73 return None74 75 node_path = os.environ.get("NODE_PATH", "").strip()76 path_env = os.environ.get("PATH", "").strip()77 if not node_path or not path_env:78 return None79 80 roots = tuple(81 Path(raw.strip()).expanduser()82 for raw in node_path.split(os.pathsep)83 if raw.strip()84 )85 node = shutil.which("node", path=path_env)86 if not roots or not node:87 return None88 89 packages = {90 package: _package_version(package, roots)91 for package in _DOCUMENT_PACKAGES92 }93 if any(value is None for value in packages.values()):94 return None95 96 if mode not in {"container", "native"}:97 visible_paths = [98 Path(node),99 *(value[1] for value in packages.values() if value is not None),100 ]101 if not all(_is_within(path, _BWRAP_VISIBLE_ROOTS) for path in visible_paths):102 return None103 104 try:105 result = subprocess.run(106 [node, "--version"],107 capture_output=True,108 text=True,109 timeout=2,110 check=False,111 env={"PATH": path_env, "NODE_PATH": node_path},112 )113 except (OSError, subprocess.SubprocessError):114 return None115 if result.returncode != 0:116 return None117 node_version = result.stdout.strip().removeprefix("v")118 if not node_version:119 return None120 121 docx = packages["docx"]122 pptxgenjs = packages["pptxgenjs"]123 assert docx is not None and pptxgenjs is not None124 return DocumentNodeToolchain(125 node_version=node_version,126 docx_version=docx[0],127 pptxgenjs_version=pptxgenjs[0],128 )129 130 131def render_document_node_toolchain_note(132 *,133 sandbox_mode: str,134 tool_names: list[str] | tuple[str, ...],135 audience: str = "agent",136) -> str:137 """Render a truthful prompt note for agents that can execute ``bash``."""138 names = {str(name) for name in tool_names}139 if "bash" not in names:140 return ""141 capability = discover_document_node_toolchain(sandbox_mode=sandbox_mode)142 if capability is None:143 return ""144 145 if "create_file" in names:146 fallback = (147 "Use this fallback order: (1) `create_file`; (2) Python libraries "148 "such as `python-docx` or `python-pptx` only after `create_file` "149 "explicitly reports the required operation unsupported; (3) these "150 "Node packages only when Python still cannot cover the operation, "151 "or when the user requires accurate preservation of an existing "152 "template that the Python path would not preserve."153 )154 else:155 fallback = (156 "`create_file` is not available in this tool set. Use Python "157 "libraries first; use these Node packages only when Python cannot "158 "cover the required operation, or when the user requires accurate "159 "preservation of an existing template that Python would not "160 "preserve."161 )162 scope = "sub-agent runtime" if audience == "coordinator" else "runtime-discovered"163 return (164 f"\n\nDOCUMENT TOOLCHAIN ({scope}): `bash` has Node.js "165 f"{capability.node_version}, `docx@{capability.docx_version}`, and "166 f"`pptxgenjs@{capability.pptxgenjs_version}`. `NODE_PATH` is already "167 "configured: load them as `require('docx')` and "168 "`require('pptxgenjs')`; do not hard-code `/usr/lib/node_modules`. "169 f"{fallback}"170 )171 