test_bundled_runtime.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """Keyless boot tests for the production exe and development node carrier.
  2. Each carrier skips independently when absent. The dummy API key only satisfies
  3. adapter loading; initialize and shutdown do not call a model.
  4. """
  5. from __future__ import annotations
  6. from pathlib import Path
  7. import pytest
  8. from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
  9. from deepseek_harness.errors import TransportClosedError
  10. from deepseek_harness_runtime import resolve_bundled_launch_args
  11. _MODES = ("exe", "node")
  12. _REPO_ROOT = Path(__file__).parents[3]
  13. _MINIMAL_CONFIG = _REPO_ROOT / "examples" / "jsonrpc-agent" / "minimal.cordis.yml"
  14. # The config must include the JSON-RPC serving plugin.
  15. _CORDIS_YML = """\
  16. - id: sdk-jsonrpc-server
  17. name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
  18. - id: agent-core
  19. name: '@deepseek-ai/dsh-agent-spine-demo'
  20. config:
  21. workspaceContext: false
  22. - id: sessions
  23. name: '@deepseek-ai/dsh-session-persistence-jsonl'
  24. config:
  25. root: './sessions'
  26. - id: session-checkpoints
  27. name: '@deepseek-ai/dsh-session-checkpoint-policy'
  28. - id: subprocess
  29. name: '@deepseek-ai/dsh-subprocess-local'
  30. - id: bash
  31. name: '@deepseek-ai/dsh-bash-local'
  32. config:
  33. cwd: '.'
  34. - id: todo
  35. name: '@deepseek-ai/dsh-tool-todo'
  36. config:
  37. allowParallelInProgress: true
  38. """
  39. def _launch_args(mode: str) -> tuple[str, ...]:
  40. try:
  41. return resolve_bundled_launch_args(mode)
  42. except FileNotFoundError as exc:
  43. pytest.skip(f"bundled {mode}-mode runtime unavailable on this machine: {exc}")
  44. def _client(tmp_path: Path, launch_args: tuple[str, ...]) -> HarnessClient:
  45. return HarnessClient(
  46. HarnessConfig(
  47. launch_args_override=launch_args,
  48. cwd=str(tmp_path),
  49. env={
  50. "DSH_CORDIS_CONFIG": "./cordis.yml",
  51. "DSH_SESSION_ROOT": str(tmp_path / "sessions"),
  52. "DSH_CWD": str(tmp_path),
  53. # The lazily mounted adapter requires a key even without a model call.
  54. "DEEPSEEK_API_KEY": "sk-dummy-for-boot",
  55. "DEEPSEEK_BASE_URL": "http://127.0.0.1:9",
  56. },
  57. request_timeout_seconds=120,
  58. )
  59. )
  60. @pytest.mark.parametrize("mode", _MODES)
  61. def test_bundled_runtime_boots_a_cordis_config(tmp_path: Path, mode: str) -> None:
  62. launch_args = _launch_args(mode)
  63. (tmp_path / "cordis.yml").write_text(_CORDIS_YML)
  64. with _client(tmp_path, launch_args) as client:
  65. init = client.initialize(provider="deepseek-official", cwd=str(tmp_path), model="deepseek-v4-pro")
  66. assert init.serverInfo is not None
  67. assert init.serverInfo.name == "deepseek-harness-sdk-runtime"
  68. @pytest.mark.parametrize("mode", _MODES)
  69. def test_python_sdk_boots_minimal_jsonrpc_config(tmp_path: Path, mode: str) -> None:
  70. launch_args = _launch_args(mode)
  71. model = "minimal-environment-model"
  72. harness = DeepSeekHarness(
  73. model=model,
  74. cwd=str(tmp_path),
  75. session_root=str(tmp_path / "sessions"),
  76. cordis=str(_MINIMAL_CONFIG),
  77. env={
  78. "DSH_MODEL": model,
  79. "DSH_CONTEXT_WINDOW": "1000000",
  80. "DSH_SYSTEM_PROMPT": "You are the Python SDK minimal boot test agent.",
  81. },
  82. api_key="sk-dummy-for-boot",
  83. base_url="http://127.0.0.1:9",
  84. launch_args_override=launch_args,
  85. request_timeout_seconds=120,
  86. )
  87. with harness:
  88. pass
  89. @pytest.mark.parametrize("mode", _MODES)
  90. def test_bundled_runtime_surfaces_unbundled_plugin_failure(tmp_path: Path, mode: str) -> None:
  91. launch_args = _launch_args(mode)
  92. (tmp_path / "cordis.yml").write_text(
  93. "- id: missing\n name: '@deepseek-ai/dsh-does-not-exist'\n"
  94. )
  95. client = _client(tmp_path, launch_args)
  96. client.start()
  97. try:
  98. with pytest.raises((TransportClosedError, TimeoutError)) as excinfo:
  99. client.initialize(provider="deepseek-official", cwd=str(tmp_path), model="deepseek-v4-pro")
  100. finally:
  101. client.close()
  102. assert "@deepseek-ai/dsh-does-not-exist" in str(excinfo.value)
  103. @pytest.mark.parametrize("mode", _MODES)
  104. @pytest.mark.parametrize("ambient_config", [None, ""], ids=["unset", "empty-counts-as-absent"])
  105. def test_zero_config_run_injects_bundled_default_cordis_config(
  106. tmp_path: Path, mode: str, ambient_config: str | None, monkeypatch: pytest.MonkeyPatch
  107. ) -> None:
  108. _launch_args(mode) # skip early when this carrier is unavailable
  109. monkeypatch.setenv("DSH_RUNTIME_MODE", mode)
  110. if ambient_config is None:
  111. monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
  112. else:
  113. monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
  114. harness = DeepSeekHarness(
  115. model="deepseek-v4-pro",
  116. cwd=str(tmp_path),
  117. session_root=str(tmp_path / "sessions"),
  118. api_key="sk-dummy-for-boot",
  119. base_url="http://127.0.0.1:9",
  120. request_timeout_seconds=120,
  121. )
  122. with harness:
  123. pass