test_client.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. from __future__ import annotations
  2. import json
  3. import sys
  4. import threading
  5. import time
  6. from pathlib import Path
  7. import pytest
  8. from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
  9. def test_high_level_sdk_runs_turn_and_collects_final_response(tmp_path: Path) -> None:
  10. script = tmp_path / "fake_runtime.py"
  11. env_dump = tmp_path / "env.json"
  12. script.write_text(
  13. """
  14. import json
  15. import os
  16. import sys
  17. env_dump = os.environ["ENV_DUMP"]
  18. json.dump({
  19. "DEEPSEEK_API_KEY": os.environ.get("DEEPSEEK_API_KEY"),
  20. "DEEPSEEK_BASE_URL": os.environ.get("DEEPSEEK_BASE_URL"),
  21. "DSH_CWD": os.environ.get("DSH_CWD"),
  22. "DSH_SESSION_ROOT": os.environ.get("DSH_SESSION_ROOT"),
  23. "DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG"),
  24. }, open(env_dump, "w"))
  25. for line in sys.stdin:
  26. msg = json.loads(line)
  27. method = msg.get("method")
  28. if method == "initialize":
  29. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  30. elif method == "session/prompt":
  31. params = msg.get("params") or {}
  32. print(json.dumps({
  33. "jsonrpc": "2.0",
  34. "method": "session.event",
  35. "params": {
  36. "sessionId": params["sessionId"],
  37. "event": {
  38. "type": "assistant/message",
  39. "data": {"content": [{"type": "text", "text": "hello from runtime"}]},
  40. },
  41. },
  42. }), flush=True)
  43. print(json.dumps({
  44. "jsonrpc": "2.0",
  45. "method": "session.finished",
  46. "params": {"sessionId": params["sessionId"], "status": "ok"},
  47. }), flush=True)
  48. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  49. elif method == "shutdown":
  50. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  51. break
  52. """.strip()
  53. )
  54. with DeepSeekHarness(
  55. model="deepseek-v4-flash",
  56. cwd=str(tmp_path),
  57. cordis=str(tmp_path / "cordis.yml"),
  58. session_root=str(tmp_path / "sessions"),
  59. launch_args_override=(sys.executable, str(script)),
  60. env={
  61. "ENV_DUMP": str(env_dump),
  62. "DEEPSEEK_API_KEY": "env-key",
  63. "DEEPSEEK_BASE_URL": "http://127.0.0.1:4321",
  64. },
  65. ) as harness:
  66. result = harness.run("say hello", session_id="main")
  67. assert result.status == "ok"
  68. assert result.final_response == "hello from runtime"
  69. assert result.events[0]["type"] == "assistant/message"
  70. dumped_env = json.loads(env_dump.read_text())
  71. assert dumped_env["DEEPSEEK_API_KEY"] == "env-key"
  72. assert dumped_env["DEEPSEEK_BASE_URL"] == "http://127.0.0.1:4321"
  73. assert dumped_env["DSH_CWD"] == str(tmp_path)
  74. assert dumped_env["DSH_SESSION_ROOT"] == str(tmp_path / "sessions")
  75. assert dumped_env["DSH_CORDIS_CONFIG"] == str(tmp_path / "cordis.yml")
  76. def test_session_run_invokes_notification_callback_before_returning(tmp_path: Path) -> None:
  77. script = tmp_path / "fake_runtime.py"
  78. script.write_text(
  79. """
  80. import json
  81. import sys
  82. for line in sys.stdin:
  83. msg = json.loads(line)
  84. method = msg.get("method")
  85. if method == "initialize":
  86. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  87. elif method == "session/prompt":
  88. print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
  89. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
  90. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  91. elif method == "shutdown":
  92. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  93. break
  94. """.strip()
  95. )
  96. seen: list[str] = []
  97. with DeepSeekHarness(
  98. launch_args_override=(sys.executable, str(script)),
  99. cwd=str(tmp_path),
  100. ) as harness:
  101. session = harness.start_session("main")
  102. result = session.run(
  103. "spawn a helper",
  104. on_notification=lambda notification: seen.append(notification.method),
  105. )
  106. assert result.status == "ok"
  107. assert seen == ["subagent.started", "session.finished"]
  108. def test_session_run_includes_subagent_finished_for_parent_session(tmp_path: Path) -> None:
  109. script = tmp_path / "fake_runtime.py"
  110. script.write_text(
  111. """
  112. import json
  113. import sys
  114. for line in sys.stdin:
  115. msg = json.loads(line)
  116. method = msg.get("method")
  117. if method == "initialize":
  118. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  119. elif method == "session/prompt":
  120. print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
  121. print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": "main", "childSessionId": "child", "status": "ok", "stopReason": "completed"}}), flush=True)
  122. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
  123. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  124. elif method == "shutdown":
  125. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  126. break
  127. """.strip()
  128. )
  129. with DeepSeekHarness(
  130. launch_args_override=(sys.executable, str(script)),
  131. cwd=str(tmp_path),
  132. ) as harness:
  133. result = harness.run("spawn a helper", session_id="main")
  134. assert result.status == "ok"
  135. assert [notification.method for notification in result.notifications] == [
  136. "subagent.started",
  137. "subagent.finished",
  138. "session.finished",
  139. ]
  140. def test_session_run_ignores_notifications_for_other_sessions(tmp_path: Path) -> None:
  141. script = tmp_path / "fake_runtime.py"
  142. script.write_text(
  143. """
  144. import json
  145. import sys
  146. for line in sys.stdin:
  147. msg = json.loads(line)
  148. method = msg.get("method")
  149. if method == "initialize":
  150. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  151. elif method == "session/prompt":
  152. params = msg.get("params") or {}
  153. print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "other", "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "wrong session"}]}}}}), flush=True)
  154. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "other", "status": "ok"}}), flush=True)
  155. print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "right session"}]}}}}), flush=True)
  156. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
  157. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  158. elif method == "shutdown":
  159. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  160. break
  161. """.strip()
  162. )
  163. with DeepSeekHarness(
  164. launch_args_override=(sys.executable, str(script)),
  165. cwd=str(tmp_path),
  166. ) as harness:
  167. result = harness.run("stay in your lane", session_id="main")
  168. assert result.status == "ok"
  169. assert result.final_response == "right session"
  170. assert [notification.payload.get("sessionId") for notification in result.notifications] == ["main", "main"]
  171. def test_high_level_session_run_does_not_accumulate_global_notifications(tmp_path: Path) -> None:
  172. script = tmp_path / "fake_runtime.py"
  173. script.write_text(
  174. """
  175. import json
  176. import sys
  177. for line in sys.stdin:
  178. msg = json.loads(line)
  179. method = msg.get("method")
  180. if method == "initialize":
  181. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  182. elif method == "session/prompt":
  183. params = msg.get("params") or {}
  184. print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "ok"}]}}}}), flush=True)
  185. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
  186. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  187. elif method == "shutdown":
  188. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  189. break
  190. """.strip()
  191. )
  192. with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
  193. result = harness.run("one turn", session_id="main")
  194. assert result.status == "ok"
  195. assert harness.client._notifications.qsize() == 0
  196. def test_session_run_waits_for_late_finished_without_replaying_stale_notifications(tmp_path: Path) -> None:
  197. script = tmp_path / "fake_runtime.py"
  198. script.write_text(
  199. """
  200. import json
  201. import sys
  202. import time
  203. turn = 0
  204. for line in sys.stdin:
  205. msg = json.loads(line)
  206. method = msg.get("method")
  207. if method == "initialize":
  208. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
  209. elif method == "session/prompt":
  210. turn += 1
  211. params = msg.get("params") or {}
  212. session_id = params["sessionId"]
  213. if turn == 1:
  214. print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": session_id, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "first"}]}}}}), flush=True)
  215. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
  216. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  217. else:
  218. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  219. time.sleep(0.05)
  220. print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": session_id, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "second"}]}}}}), flush=True)
  221. print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
  222. elif method == "shutdown":
  223. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  224. break
  225. """.strip()
  226. )
  227. with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
  228. first = harness.run("first turn", session_id="main")
  229. second = harness.run("second turn", session_id="main")
  230. assert first.final_response == "first"
  231. assert second.final_response == "second"
  232. assert [notification.payload.get("sessionId") for notification in second.notifications] == ["main", "main"]
  233. def test_client_starts_subprocess_sends_requests_and_routes_notifications(tmp_path: Path) -> None:
  234. script = tmp_path / "fake_bridge.py"
  235. script.write_text(
  236. """
  237. import json
  238. import sys
  239. for line in sys.stdin:
  240. msg = json.loads(line)
  241. method = msg.get("method")
  242. if method == "initialize":
  243. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  244. elif method == "session/prompt":
  245. params = msg.get("params") or {}
  246. print(json.dumps({"jsonrpc": "2.0", "method": "llm/request", "params": {"requestId": "req-1", "sessionId": params["sessionId"], "model": "dsagent", "messages": []}}), flush=True)
  247. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
  248. elif method == "shutdown":
  249. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  250. break
  251. """.strip()
  252. )
  253. with HarnessClient(
  254. HarnessConfig(launch_args_override=(sys.executable, str(script)))
  255. ) as client:
  256. init = client.initialize(cwd="/workspace", model="dsagent")
  257. assert init.serverInfo.name == "fake-dsh"
  258. client.session_prompt("main", [{"type": "text", "text": "fix it"}], profile="build")
  259. notification = client.next_notification()
  260. assert notification.method == "llm/request"
  261. assert notification.payload["requestId"] == "req-1"
  262. assert notification.payload["sessionId"] == "main"
  263. def test_client_keeps_unmatched_notifications_available_globally_while_subscribed() -> None:
  264. client = HarnessClient()
  265. with client.subscribe_session_notifications("main"):
  266. client._handle_message({
  267. "jsonrpc": "2.0",
  268. "method": "session.event",
  269. "params": {"sessionId": "other", "event": {"type": "assistant/message"}},
  270. })
  271. assert client._notifications.qsize() == 1
  272. notification = client._notifications.get_nowait()
  273. assert not isinstance(notification, BaseException)
  274. assert notification.method == "session.event"
  275. assert notification.payload["sessionId"] == "other"
  276. def test_client_rejects_unaccepted_session_prompt_response(tmp_path: Path) -> None:
  277. script = tmp_path / "fake_bridge.py"
  278. script.write_text(
  279. """
  280. import json
  281. import sys
  282. for line in sys.stdin:
  283. msg = json.loads(line)
  284. method = msg.get("method")
  285. if method == "initialize":
  286. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  287. elif method == "session/prompt":
  288. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": False}}), flush=True)
  289. elif method == "shutdown":
  290. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  291. break
  292. """.strip()
  293. )
  294. with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
  295. client.initialize(cwd="/workspace", model="dsagent")
  296. with pytest.raises(ValueError):
  297. client.session_prompt("main", [{"type": "text", "text": "fix it"}], profile="build")
  298. def test_client_routes_bridge_requests_and_sends_responses(tmp_path: Path) -> None:
  299. script = tmp_path / "fake_bridge.py"
  300. script.write_text(
  301. """
  302. import json
  303. import sys
  304. for line in sys.stdin:
  305. msg = json.loads(line)
  306. method = msg.get("method")
  307. if method == "initialize":
  308. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  309. print(json.dumps({"jsonrpc": "2.0", "id": "bridge-req-1", "method": "llm.request", "params": {"requestId": "req-1", "sessionId": "main", "model": "dsagent", "messages": []}}), flush=True)
  310. elif "id" in msg and "method" not in msg:
  311. print(json.dumps({"jsonrpc": "2.0", "method": "response/seen", "params": {"result": msg.get("result")}}), flush=True)
  312. elif method == "shutdown":
  313. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  314. break
  315. """.strip()
  316. )
  317. with HarnessClient(
  318. HarnessConfig(launch_args_override=(sys.executable, str(script)))
  319. ) as client:
  320. client.initialize(cwd="/workspace", model="dsagent")
  321. request = client.next_request()
  322. assert request.id == "bridge-req-1"
  323. assert request.method == "llm.request"
  324. assert request.payload["requestId"] == "req-1"
  325. client.respond(request.id, {"content_blocks": [{"type": "text", "text": "done"}]})
  326. notification = client.next_notification()
  327. assert notification.method == "response/seen"
  328. assert notification.payload["result"]["content_blocks"][0]["text"] == "done"
  329. def test_client_ignores_non_json_stdout_lines(tmp_path: Path) -> None:
  330. script = tmp_path / "fake_bridge.py"
  331. script.write_text(
  332. """
  333. import json
  334. import sys
  335. print("node warning: experimental loader", flush=True)
  336. for line in sys.stdin:
  337. msg = json.loads(line)
  338. if msg.get("method") == "initialize":
  339. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  340. elif msg.get("method") == "shutdown":
  341. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  342. break
  343. """.strip()
  344. )
  345. with HarnessClient(
  346. HarnessConfig(launch_args_override=(sys.executable, str(script)))
  347. ) as client:
  348. init = client.initialize(cwd="/workspace", model="dsagent")
  349. assert init.serverInfo.name == "fake-dsh"
  350. def test_client_request_times_out_when_bridge_does_not_respond(tmp_path: Path) -> None:
  351. script = tmp_path / "fake_bridge.py"
  352. script.write_text(
  353. """
  354. import time
  355. time.sleep(60)
  356. """.strip()
  357. )
  358. with HarnessClient(
  359. HarnessConfig(
  360. launch_args_override=(sys.executable, str(script)),
  361. request_timeout_seconds=0.1,
  362. )
  363. ) as client:
  364. start = time.monotonic()
  365. try:
  366. client.initialize(cwd="/workspace", model="dsagent")
  367. except TimeoutError:
  368. assert time.monotonic() - start < 2
  369. else:
  370. raise AssertionError("initialize should time out")
  371. def test_client_close_times_out_when_shutdown_does_not_respond(tmp_path: Path) -> None:
  372. script = tmp_path / "fake_bridge.py"
  373. script.write_text(
  374. """
  375. import json
  376. import sys
  377. import time
  378. for line in sys.stdin:
  379. msg = json.loads(line)
  380. if msg.get("method") == "initialize":
  381. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  382. elif msg.get("method") == "shutdown":
  383. time.sleep(60)
  384. """.strip()
  385. )
  386. client = HarnessClient(
  387. HarnessConfig(
  388. launch_args_override=(sys.executable, str(script)),
  389. shutdown_timeout_seconds=0.1,
  390. )
  391. )
  392. client.start()
  393. client.initialize(cwd="/workspace", model="dsagent")
  394. start = time.monotonic()
  395. client.close()
  396. assert time.monotonic() - start < 2
  397. def test_client_close_is_idempotent_before_and_after_start(tmp_path: Path) -> None:
  398. HarnessClient().close()
  399. script = tmp_path / "fake_bridge.py"
  400. script.write_text(
  401. """
  402. import json
  403. import sys
  404. for line in sys.stdin:
  405. msg = json.loads(line)
  406. if msg.get("method") == "initialize":
  407. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  408. elif msg.get("method") == "shutdown":
  409. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  410. break
  411. """.strip()
  412. )
  413. client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
  414. client.start()
  415. client.initialize(cwd="/workspace", model="dsagent")
  416. client.close()
  417. client.close()
  418. def test_runtime_closed_error_includes_stderr_tail(tmp_path: Path) -> None:
  419. script = tmp_path / "crashing_runtime.py"
  420. script.write_text(
  421. """
  422. import sys
  423. print("fatal bridge exploded", file=sys.stderr, flush=True)
  424. sys.exit(42)
  425. """.strip()
  426. )
  427. with HarnessClient(
  428. HarnessConfig(
  429. launch_args_override=(sys.executable, str(script)),
  430. request_timeout_seconds=2,
  431. )
  432. ) as client:
  433. with pytest.raises(Exception, match="fatal bridge exploded"):
  434. client.initialize(cwd="/workspace", model="dsagent")
  435. def test_client_serializes_concurrent_writes(tmp_path: Path) -> None:
  436. script = tmp_path / "fake_bridge.py"
  437. output = tmp_path / "seen.jsonl"
  438. script.write_text(
  439. """
  440. import json
  441. import os
  442. import sys
  443. with open(os.environ["SEEN"], "w") as seen:
  444. for line in sys.stdin:
  445. seen.write(line)
  446. seen.flush()
  447. msg = json.loads(line)
  448. if "id" in msg and msg.get("method") == "initialize":
  449. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
  450. elif "id" in msg and msg.get("method") == "shutdown":
  451. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  452. break
  453. """.strip()
  454. )
  455. with HarnessClient(
  456. HarnessConfig(
  457. launch_args_override=(sys.executable, str(script)),
  458. env={"SEEN": str(output)},
  459. )
  460. ) as client:
  461. client.initialize(cwd="/workspace", model="dsagent")
  462. threads = [
  463. threading.Thread(target=client.notify, args=(f"notice-{index}", {"index": index}))
  464. for index in range(50)
  465. ]
  466. for thread in threads:
  467. thread.start()
  468. for thread in threads:
  469. thread.join()
  470. for line in output.read_text().splitlines():
  471. json.loads(line)
  472. def test_client_uses_bundled_runtime_package_by_default(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
  473. runtime = tmp_path / "dsh-jsonrpc-agent"
  474. runtime.write_text(
  475. """#!/usr/bin/env python3
  476. import json
  477. import sys
  478. for line in sys.stdin:
  479. msg = json.loads(line)
  480. if msg.get("method") == "initialize":
  481. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "bundled-runtime"}}}), flush=True)
  482. elif msg.get("method") == "shutdown":
  483. print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
  484. break
  485. """.strip()
  486. )
  487. runtime.chmod(0o755)
  488. module_dir = tmp_path / "deepseek_harness_runtime"
  489. module_dir.mkdir()
  490. (module_dir / "__init__.py").write_text(
  491. f"""
  492. def resolve_bundled_launch_args(mode=None):
  493. return ({str(runtime)!r},)
  494. """.strip()
  495. )
  496. monkeypatch.syspath_prepend(str(tmp_path))
  497. monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
  498. with HarnessClient() as client:
  499. init = client.initialize(cwd="/workspace", model="deepseek-v4-pro")
  500. assert init.serverInfo.name == "bundled-runtime"
  501. def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.MonkeyPatch) -> None:
  502. monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
  503. monkeypatch.setattr(sys, "path", [])
  504. with pytest.raises(FileNotFoundError, match="Install deepseek-harness-runtime-bin"):
  505. HarnessClient().start()