__init__.py 6.5 KB

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