__init__.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. """Locate the bundled DeepSeek Harness SDK runtime shipped with this package.
  2. Two runtime carriers coexist under ``runtime/``, both injected by the repo's
  3. ``scripts/build-exe-for-python-sdk.ts`` build (neither is checked into git):
  4. - **exe (production)**: single-file executables named
  5. ``dsh-jsonrpc-agent-pkg-<platform>-<arch>`` (platform in {linux, macos}, arch in
  6. {x64, arm64}); the target machine needs no Node installation.
  7. - **node (dev-only)**: the full deploy closure under ``runtime/node/``
  8. (``package.json`` + ``node_modules/``), executed as ``node
  9. runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js`` on a
  10. system Node >= 22.19. It is the current checkout's source build, never
  11. selected automatically, and excluded from wheel/sdist distributions.
  12. ``runtime/cordis.yml`` IS checked in: it is the default agent configuration
  13. the client SDK injects via ``$DSH_CORDIS_CONFIG`` for zero-config runs — the
  14. runtime itself always requires an explicit config and has no built-in
  15. fallback.
  16. """
  17. from __future__ import annotations
  18. import os
  19. import platform
  20. import shutil
  21. import sys
  22. from pathlib import Path
  23. PACKAGE_METADATA_FILENAME = "deepseek-harness-runtime.json"
  24. RUNTIME_MODE_ENV_VAR = "DSH_RUNTIME_MODE"
  25. _PLATFORM_TAGS = {"linux": "linux", "darwin": "macos"}
  26. _ARCH_TAGS = {"x86_64": "x64", "amd64": "x64", "arm64": "arm64", "aarch64": "arm64"}
  27. _EXE_ACQUISITION_HINT = (
  28. "Two ways to get the executable: run `scripts/build-exe-for-python-sdk.ts` (via tsx) in a "
  29. "deepseek-harness checkout, or install the matching `deepseek-harness-runtime-bin` platform "
  30. "wheel retained by the `build-exe-for-python-sdk` CI workflow. For local development "
  31. "against a repo source build, explicitly select the dev-only node carrier with "
  32. f"{RUNTIME_MODE_ENV_VAR}=node (or resolve_bundled_launch_args('node'))."
  33. )
  34. def bundled_package_dir() -> Path:
  35. """Root directory of the installed runtime package data (the directory of this module)."""
  36. root = Path(__file__).resolve().parent
  37. metadata = root / PACKAGE_METADATA_FILENAME
  38. if not metadata.is_file():
  39. raise FileNotFoundError(f"deepseek-harness-runtime-bin is missing {metadata}")
  40. return root
  41. def bundled_default_config_path() -> Path:
  42. """Path of the checked-in default runtime configuration (``runtime/cordis.yml``).
  43. The client SDK injects this path via ``$DSH_CORDIS_CONFIG`` when the caller
  44. supplies no config and the launch resolves to the bundled runtime — the
  45. runtime binary itself always demands an explicit config.
  46. """
  47. path = bundled_package_dir() / "runtime" / "cordis.yml"
  48. if not path.is_file():
  49. raise FileNotFoundError(
  50. f"deepseek-harness-runtime-bin is missing the default runtime config at {path}"
  51. )
  52. return path
  53. def bundled_runtime_path() -> Path:
  54. """Absolute path of the bundled single-file runtime executable for the current platform.
  55. Raises FileNotFoundError when the platform is unsupported or the executable
  56. has not been placed into this package; the message names the acquisition
  57. routes (acquisition strategy is deliberately separate from this lookup
  58. interface, so an on-demand download can replace it without touching
  59. callers).
  60. """
  61. tag = _current_platform_tag()
  62. path = bundled_package_dir() / "runtime" / f"dsh-jsonrpc-agent-pkg-{tag}"
  63. if not path.is_file():
  64. raise FileNotFoundError(
  65. f"deepseek-harness-runtime-bin is missing the runtime executable at {path}. "
  66. + _EXE_ACQUISITION_HINT
  67. )
  68. return path
  69. def resolve_bundled_launch_args(mode: str | None = None) -> tuple[str, ...]:
  70. """The argv tuple that launches the bundled runtime.
  71. Mode selection: the explicit ``mode`` argument wins, then the
  72. ``DSH_RUNTIME_MODE`` environment variable (``exe`` | ``node``), then
  73. automatic resolution. Automatic resolution finds the production exe ONLY —
  74. the dev-only node carrier must be selected explicitly so a production
  75. deployment can never silently ride on a source build. Returns
  76. ``(exe_path,)`` in exe mode and ``(node_path, bin_js_path)`` in node mode;
  77. raises FileNotFoundError when the selected carrier is unavailable and
  78. ValueError for an unknown mode value.
  79. """
  80. selected = mode if mode is not None else os.environ.get(RUNTIME_MODE_ENV_VAR)
  81. if selected is None or selected == "exe":
  82. return (str(bundled_runtime_path()),)
  83. if selected == "node":
  84. return _node_launch_args()
  85. raise ValueError(
  86. f"unsupported DeepSeek Harness runtime mode {selected!r}: expected 'exe' or 'node' "
  87. f"(explicit argument or ${RUNTIME_MODE_ENV_VAR})"
  88. )
  89. def _current_platform_tag() -> str:
  90. plat = _PLATFORM_TAGS.get(sys.platform)
  91. arch = _ARCH_TAGS.get(platform.machine().lower())
  92. if plat is None or arch is None:
  93. raise FileNotFoundError(
  94. "no bundled dsh-jsonrpc-agent executable exists for this platform "
  95. f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: "
  96. "linux/macos on x64/arm64. " + _EXE_ACQUISITION_HINT
  97. )
  98. return f"{plat}-{arch}"
  99. def _node_launch_args() -> tuple[str, str]:
  100. node_root = bundled_package_dir() / "runtime" / "node"
  101. bin_js = (
  102. node_root / "node_modules" / "@deepseek-ai" / "dsh-jsonrpc-agent" / "lib" / "bin.js"
  103. )
  104. if not bin_js.is_file():
  105. raise FileNotFoundError(
  106. f"the dev-only node runtime closure is missing at {node_root} "
  107. f"(no {bin_js}); run `scripts/build-exe-for-python-sdk.ts` in a deepseek-harness "
  108. "checkout, which builds and copies the deploy closure here. The node carrier "
  109. "is for repo-local development only — production uses the single-file exe."
  110. )
  111. node = shutil.which("node")
  112. if node is None:
  113. raise FileNotFoundError(
  114. "the node runtime mode needs a system `node` (>=22.19) on PATH; "
  115. "install Node.js or use the exe mode"
  116. )
  117. return (node, str(bin_js))
  118. __all__ = [
  119. "PACKAGE_METADATA_FILENAME",
  120. "RUNTIME_MODE_ENV_VAR",
  121. "bundled_default_config_path",
  122. "bundled_package_dir",
  123. "bundled_runtime_path",
  124. "resolve_bundled_launch_args",
  125. ]