fix: download optional Python runtime resources explicitly

This commit is contained in:
yudshj committed 2026-10-09 12:06:50 +08:00
1 parent 15c10e0e87
commit 89f35fe2e9
34 files changed
+1159 -259

No files matched your search

@@ -10,7 +10,7 @@ Two runtime carriers coexist under ``runtime/``, both injected by the repo's
Node installation.
- **node (dev-only)**: the full deploy closure under ``runtime/node/``
(``package.json`` + ``node_modules/``), executed as ``node
runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`` on a
runtime/node/runtime-bootstrap.mjs`` on a
system Node >= 22.19. It is the current checkout's source build, never
selected automatically, and excluded from wheel/sdist distributions.
@@ -28,7 +28,7 @@ import shutil
import subprocess
import sys
from pathlib import Path
from ._resources import validate_resources
from ._resources import validate_downloads, office_launch_args
PACKAGE_METADATA_FILENAME = "deepseek-harness-runtime.json"
@@ -59,10 +59,9 @@ def bundled_runtime_path() -> Path:
"""Absolute path of the bundled single-file runtime executable for the current platform.
Raises FileNotFoundError when the platform is unsupported, the executable
has not been placed into this package, the ripgrep or Office sidecar is
missing, the authoring resources are incomplete, or the required macOS
spawn helper is missing. Invalid authoring metadata or Python permissions
raise ValueError. Missing executable/Office messages name
has not been placed into this package, the ripgrep sidecar or download
manifest is missing, or the required macOS spawn helper is missing.
Invalid download metadata raises ValueError. Missing executable messages name
the acquisition routes (acquisition strategy is deliberately separate from
this lookup interface, so an on-demand download can replace it without
touching callers).
@@ -92,22 +91,7 @@ def bundled_runtime_path() -> Path:
f"deepseek-harness-runtime-bin is missing the node-pty spawn helper at {helper}. "
+ _EXE_ACQUISITION_HINT
)
office = path.with_name(f"{path.name.removesuffix('.exe')}-office")
adapter = office / "node_modules/@deepseek-ai/libreoffice-kit/package.json"
if not adapter.is_file():
raise FileNotFoundError(
f"deepseek-harness-runtime-bin is missing the Office sidecar at {office}. "
+ _EXE_ACQUISITION_HINT
)
native = tag.replace("win-", "win32-").replace("macos-", "darwin-")
declared = json.loads(adapter.read_text(encoding="utf-8")).get("optionalDependencies", {})
engine = native if f"@deepseek-ai/libreoffice-kit-{native}" in declared else "wasm"
if not (office / "node_modules" / f"@deepseek-ai/libreoffice-kit-{engine}/prebuilds.json").is_file():
raise FileNotFoundError(
f"deepseek-harness-runtime-bin is missing the Office sidecar engine {engine} at {office}. "
+ _EXE_ACQUISITION_HINT
)
validate_resources(path.with_name(tag), tag)
validate_downloads(path.with_name(tag), tag)
return path
@@ -152,14 +136,7 @@ def _current_platform_tag() -> str:
def _node_launch_args() -> tuple[str, str]:
node_root = bundled_package_dir() / "runtime" / "node"
bin_js = (
node_root
/ "node_modules"
/ "@deepseek-ai"
/ "dsh"
/ "lib"
/ "bin.js"
)
bin_js = node_root / "runtime-bootstrap.mjs"
if not bin_js.is_file():
raise FileNotFoundError(
f"the dev-only node runtime closure is missing at {node_root} "
@@ -176,6 +153,43 @@ def _node_launch_args() -> tuple[str, str]:
return (node, str(bin_js))
def _download(flag: str) -> Path:
result = subprocess.run((*resolve_bundled_launch_args(), flag), capture_output=True, text=True)
if result.returncode:
raise RuntimeError(result.stderr.strip() or f"resource download failed with exit code {result.returncode}")
path = json.loads(result.stdout)
if not isinstance(path, str) or not Path(path).is_absolute():
raise ValueError("runtime returned an invalid resource path")
return Path(path)
def download_office() -> Path:
"""Download the locked npm Office sidecar and standalone Node into the resource cache.
Return its absolute directory. Reuse completed downloads; failures leave no
published partial installation. DSH_RESOURCE_CACHE selects the cache root.
This explicit operation requires network access on a cache miss, not a
Harness home, agent, or model. Other SDK operations never trigger downloads.
"""
return _download("--download-office")
def download_primary_runtime() -> Path:
"""Download locked CPython, libraries, Node, pnpm and skills into the resource cache.
Return the absolute primary-runtime directory. This is independent of the
Office engine download and does not copy resources into a Harness home.
Completed downloads are reused; DSH_RESOURCE_CACHE selects the cache root.
"""
return _download("--download-primary-runtime")
def resolve_office_launch_args() -> tuple[str, str]:
"""Locate the explicitly downloaded Office CLI and its standalone Node; never download."""
tag = _current_platform_tag()
return office_launch_args(bundled_runtime_path().with_name(tag), tag)
def main() -> None:
"""Launch the CLI with explicit DSH_HOME; wait on Windows, replace the process on POSIX."""
if not os.environ.get("DSH_HOME", "").strip():
@@ -197,6 +211,9 @@ __all__ = [
"RUNTIME_MODE_ENV_VAR",
"bundled_package_dir",
"bundled_runtime_path",
"download_office",
"download_primary_runtime",
"resolve_office_launch_args",
"main",
"resolve_bundled_launch_args",
]
@@ -3,6 +3,10 @@
from __future__ import annotations
import json
import os
import signal
import subprocess
from collections.abc import Sequence
import re
import stat
import zipfile
@@ -41,3 +45,71 @@ def validate_resources(root: Path | zipfile.Path, target: str) -> None:
if isinstance(binary, zipfile.Path) else binary.stat().st_mode)
if mode & stat.S_IXUSR == 0:
raise ValueError(f"runtime interpreter lost its executable bit: {binary}")
def validate_downloads(root: Path | zipfile.Path, target: str) -> dict:
"""Validate the lightweight download manifest carried by one platform wheel."""
path = root / "downloads.json"
value = json.loads(path.read_text(encoding="utf-8"))
if (not isinstance(value, dict) or value.get("target") != target.replace("macos-", "mac-")
or not isinstance(value.get("identity"), str)
or not re.fullmatch(r"[a-f0-9]{64}", value["identity"])
or not isinstance(value.get("office"), list) or not value["office"]
or not isinstance(value.get("pnpm"), dict)):
raise ValueError(f"invalid runtime download manifest: {path}")
for file in ["scripts/check_office.py", *(f"office-{kind}/SKILL.md" for kind in ("docx", "pptx", "xlsx"))]:
if not (root / "office-skills" / file).is_file():
raise FileNotFoundError(f"runtime Office skill is missing: {root / 'office-skills' / file}")
return value
def office_launch_args(root: Path, target: str) -> tuple[str, str]:
"""Require an explicitly installed sidecar, including its independent Node interpreter."""
metadata = validate_downloads(root, target)
cache = Path(os.environ.get("DSH_RESOURCE_CACHE", str(Path.home() / ".cache/deepseek-harness/resources")))
if not cache.is_absolute():
raise ValueError("DSH_RESOURCE_CACHE must be an absolute directory")
office = Path(os.environ.get("DSH_OFFICE_SIDECAR", str(cache / metadata["identity"] / "office")))
if not office.is_absolute():
raise ValueError("DSH_OFFICE_SIDECAR must be an absolute directory")
node = office / "node/bin" / ("node.exe" if target == "win-x64" else "node")
cli = office / "node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js"
for path in (node, cli):
if not path.is_file():
raise FileNotFoundError(f"Office resource is missing: {path}. Call deepseek_harness_runtime.download_office() first.")
return str(node), str(cli)
def run_office_process(arguments: Sequence[str], *, capture_output: bool) -> subprocess.CompletedProcess[bytes]:
"""Run Office with captured or inherited streams; a second interrupt kills its process tree."""
taskkill = os.path.join(os.environ.get("SystemRoot", ""), "System32", "taskkill.exe")
if os.name == "nt" and not os.path.isabs(taskkill):
raise ValueError("Office operations on Windows require an absolute SystemRoot environment value.")
with subprocess.Popen(
arguments,
stdin=subprocess.DEVNULL if capture_output else None,
stdout=subprocess.PIPE if capture_output else None,
stderr=subprocess.PIPE if capture_output else None,
start_new_session=os.name != "nt",
) as process:
try:
stdout, stderr = process.communicate()
except KeyboardInterrupt:
# Windows console Ctrl+C already reaches children in the same console.
if os.name != "nt":
process.send_signal(signal.SIGINT)
try:
process.communicate()
except KeyboardInterrupt:
if os.name == "nt":
subprocess.run([taskkill, "/PID", str(process.pid), "/T", "/F"],
stdin=subprocess.DEVNULL, capture_output=True, check=False)
else:
try:
os.killpg(process.pid, signal.SIGKILL)
except ProcessLookupError as error:
# The CLI and its children can finish before the second interrupt.
pass
process.communicate()
raise
return subprocess.CompletedProcess(arguments, process.returncode, stdout, stderr)