Files

455 lines
21 KiB
Python

"""Unified blocking input, native command results and independent run observation."""
from __future__ import annotations
import json
import sys
import threading
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import pytest
from pydantic import BaseModel, ValidationError
from deepseek_harness.models import CommandExecution
from deepseek_harness import DeepSeekHarness, Notification, RunResult, SdkProtocolError
from deepseek_harness.errors import JsonRpcError
class Ack(BaseModel):
pass
@pytest.fixture
def peer(tmp_path: Path) -> Path:
script = tmp_path / "queries.py"
script.write_text('''import json
import os
import sys
serial = 0
pending_commands = []
pending_prompts = []
pending_plans = []
def send(frame):
print(json.dumps({"jsonrpc": "2.0", **frame}), flush=True)
def event(session, type, data):
send({"method": "session.event", "params": {"sessionId": session, "event": {"type": type, "data": data}}})
def input_text(frame):
return "".join(block["text"] for block in frame["params"]["contentBlocks"] if block["type"] == "text")
def command(frame, number):
session = frame["params"]["sessionId"]
identity = "command-" + str(number)
event(session, "command/run", {"commandId": identity})
event(session, "command/done", {"commandId": identity, "kind": "success"})
send({"method": "session.query.completed", "params": {"sessionId": session, "commandId": identity,
"finalResponse": "command completed", "finishReason": "completed"}})
send({"id": frame["id"], "result": {"kind": "command", "execution": {
"commandId": identity, "result": {"kind": "success", "text": "command completed"}}}})
def receipt(frame, number):
session = frame["params"]["sessionId"]
send({"method": "session.status", "params": {"sessionId": session, "status": "idle"}})
event(session, "agent/inbox/spliced", {"inserted": [{"id": "input-" + str(number)}]})
def finish(frame):
session = frame["params"]["sessionId"]
event(session, "assistant/message", {"message": {"content": [
{"type": "text", "text": "answer for " + input_text(frame)}]}})
event(session, "turn/end", {"turn": 1, "reason": {"kind": "completed"}})
send({"method": "session.status", "params": {"sessionId": session, "status": "idle"}})
for line in sys.stdin:
frame = json.loads(line)
with open(os.environ["REQUEST_LOG"], "a", encoding="utf-8") as log:
log.write(line)
method = frame["method"]
if method == "initialize":
send({"id": frame["id"], "result": {"serverInfo": {"name": "query-peer"}}})
elif method == "session/prompt":
send({"id": frame["id"], "result": {"messageId": "literal-input"}})
elif method == "session/query":
serial += 1
params = frame["params"]
assert set(params) == {"sessionId", "contentBlocks"}
text = input_text(frame)
if text == "/plan hold":
identity = "plan-" + str(serial)
event(params["sessionId"], "command/run", {"commandId": identity})
event(params["sessionId"], "command/done", {"commandId": identity, "kind": "success"})
send({"id": frame["id"], "result": {"kind": "command", "execution": {
"commandId": identity, "result": {"kind": "success", "text": "Plan enabled"}}}})
pending_plans.append((frame, identity))
elif text == "hold-command":
pending_commands.append((frame, serial))
elif text in ("/compact", "/plan Describe this image."):
command(frame, serial)
elif text in ("missing-run", "missing-done", "missing-both", "unrelated-done", "invalid-done"):
if text not in ("missing-run", "missing-both"):
event(params["sessionId"], "command/run", {"commandId": "silent"})
if text == "invalid-done":
event(params["sessionId"], "command/done", None)
elif text not in ("missing-done", "missing-both"):
event(params["sessionId"], "command/done", {
"commandId": "another-command" if text == "unrelated-done" else "silent", "kind": "success"})
send({"method": "session.query.completed", "params": {"sessionId": params["sessionId"],
"commandId": "silent", "finalResponse": "", "finishReason": "completed"}})
send({"id": frame["id"], "result": {"kind": "command", "execution": {
"commandId": "silent", "result": {"kind": "success"}}}})
elif text == "rpc-error":
send({"id": frame["id"], "error": {"code": -32602, "message": "query rejected"}})
elif text == "custom-response":
response = json.loads(os.environ["QUERY_RESPONSE"])
execution = response.get("execution") if isinstance(response, dict) else None
if isinstance(execution, dict) and isinstance(execution.get("result"), dict):
event(params["sessionId"], "command/run", {"commandId": execution.get("commandId")})
if os.environ.get("QUERY_LIFECYCLE") != "late-done":
event(params["sessionId"], "command/done", {"commandId": execution.get("commandId"), **execution["result"]})
send({"method": "session.query.completed", "params": {"sessionId": params["sessionId"],
"commandId": execution.get("commandId"), "finalResponse": execution["result"].get("text", ""),
"finishReason": "error" if execution["result"].get("kind") == "error" else "completed",
**json.loads(os.environ.get("QUERY_COMPLETION", "{}"))}})
if os.environ.get("QUERY_LIFECYCLE") == "late-done":
event(params["sessionId"], "command/done", {"commandId": execution.get("commandId"), **execution["result"]})
send({"id": frame["id"], "result": response})
elif text == "bad-route":
send({"id": frame["id"], "result": {"kind": "unknown"}})
elif text == "bad-execution":
send({"id": frame["id"], "result": {"kind": "command", "execution": {"commandId": "c"}}})
else:
result = {"kind": "prompt", "messageId": "input-" + str(serial)}
if text == "fast-prompt":
receipt(frame, serial)
finish(frame)
send({"id": frame["id"], "result": result})
else:
send({"id": frame["id"], "result": result})
receipt(frame, serial)
if text == "hold-prompt":
pending_prompts.append(frame)
else:
finish(frame)
elif method == "release":
for original, identity in pending_plans:
finish(original)
send({"method": "session.query.completed", "params": {"sessionId": original["params"]["sessionId"],
"commandId": identity, "finalResponse": "Generated plan", "finishReason": "completed"}})
pending_plans.clear()
for original, number in pending_commands:
command(original, number)
pending_commands.clear()
for original in pending_prompts:
finish(original)
pending_prompts.clear()
send({"id": frame["id"], "result": {}})
elif method == "barrier":
send({"id": frame["id"], "result": {}})
elif method == "disconnect":
send({"id": frame["id"], "result": {}})
break
elif method == "shutdown":
send({"id": frame["id"], "result": {}})
break
else:
raise AssertionError(method)
''', encoding="utf-8")
return script
@pytest.fixture
def sdk(peer: Path, tmp_path: Path):
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"), request_timeout_seconds=5,
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl")},
) as harness:
yield harness
def release(sdk: DeepSeekHarness) -> None:
sdk.client.request("release", {}, response_model=Ack, timeout_seconds=5)
def assert_released(sdk: DeepSeekHarness) -> None:
assert sdk.client._responses == {}
assert sdk.client._notification_subscribers == {}
@pytest.mark.parametrize("entry", ["session", "harness"])
def test_run_returns_command_without_waiting_for_idle(sdk: DeepSeekHarness, entry: str) -> None:
notifications = []
result = (sdk.run("/compact", session_id="main", on_notification=notifications.append)
if entry == "harness" else sdk.start_session("main").run("/compact", on_notification=notifications.append))
assert isinstance(result, RunResult)
assert isinstance(result, RunResult)
assert result.session_id == "main"
assert result.finish_reason == "completed"
assert [notice.payload["event"]["type"] for notice in notifications if notice.method == "session.event"] == ["command/run", "command/done"]
assert_released(sdk)
def test_top_level_command_results_identify_distinct_sessions_and_allow_continuing_one(
sdk: DeepSeekHarness, tmp_path: Path,
) -> None:
first = sdk.run("/compact")
second = sdk.run("/compact")
assert isinstance(first, RunResult) and isinstance(second, RunResult)
assert first.session_id and second.session_id and first.session_id != second.session_id
continued = sdk.run("continue the first session", session_id=first.session_id)
assert isinstance(continued, RunResult)
assert continued.session_id == first.session_id
sent = [json.loads(line) for line in (tmp_path / "requests.jsonl").read_text(encoding="utf-8").splitlines()]
queries = [frame["params"] for frame in sent if frame["method"] == "session/query"]
assert [query["sessionId"] for query in queries] == [first.session_id, second.session_id, first.session_id]
assert_released(sdk)
@pytest.mark.parametrize("result", [
{"kind": "success"},
{"kind": "success", "text": "Compacted.", "sourceEventSeq": 0},
{"kind": "success", "sourceEventSeq": 2**53 - 1},
{"kind": "error", "text": "The agent is busy."},
])
def test_run_adds_session_identity_without_changing_command_results(peer: Path, tmp_path: Path, result: dict) -> None:
execution = {"commandId": "custom-command", "result": result}
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"), request_timeout_seconds=5,
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"), "QUERY_RESPONSE": json.dumps({
"kind": "command", "execution": execution,
})},
) as sdk:
returned = sdk.run("custom-response")
assert isinstance(returned, RunResult)
assert returned.session_id
assert returned.final_response == result.get("text", "")
assert returned.finish_reason == ("error" if result["kind"] == "error" else "completed")
low_level = sdk.client._query(returned.session_id, "custom-response")
assert isinstance(low_level, CommandExecution)
assert low_level.model_dump(by_alias=True, exclude_none=True) == execution
assert_released(sdk)
@pytest.mark.parametrize("text", ["ordinary", "fast-prompt", "/model", " /compact extra "])
def test_run_preserves_ordinary_text_and_result(sdk: DeepSeekHarness, text: str) -> None:
notifications = []
result = sdk.start_session("main").run(text, on_notification=notifications.append)
assert isinstance(result, RunResult)
assert result.final_response == "answer for " + text
assert result.finish_reason == "completed"
assert result.notifications == notifications
assert notifications[0].payload["event"]["type"] == "agent/inbox/spliced"
assert_released(sdk)
@pytest.mark.parametrize("entry,text", [
("harness", "/plan Describe this image."),
("session", "/plan Describe this image."),
("harness", "Explain this image."),
("session", "/unknown-slash"),
("session", ""),
])
def test_run_forwards_content_blocks_with_the_routed_result_and_notifications(
sdk: DeepSeekHarness, tmp_path: Path, entry: str, text: str,
) -> None:
blocks = [
{"type": "text", "text": text},
{"type": "image", "data": "AQ==", "mimeType": "image/png", "name": "diagram.png"},
]
notifications = []
result = (
sdk.run(blocks, session_id="main", on_notification=notifications.append)
if entry == "harness" else sdk.start_session("main").run(
blocks, on_notification=notifications.append,
)
)
if text == "/plan Describe this image.":
assert isinstance(result, RunResult)
assert [notice.payload["event"]["type"] for notice in notifications if notice.method == "session.event"] == ["command/run", "command/done"]
else:
assert isinstance(result, RunResult)
assert result.final_response == "answer for " + text
assert result.notifications == notifications
sent = [json.loads(line) for line in (tmp_path / "requests.jsonl").read_text(encoding="utf-8").splitlines()]
assert sent[-1]["method"] == "session/query"
assert sent[-1]["params"] == {
"sessionId": "main", "contentBlocks": blocks,
}
assert_released(sdk)
def test_run_normalizes_strings_without_changing_their_text(sdk: DeepSeekHarness, tmp_path: Path) -> None:
result = sdk.run(" preserve whitespace ", session_id="main")
assert isinstance(result, RunResult)
sent = [json.loads(line) for line in (tmp_path / "requests.jsonl").read_text(encoding="utf-8").splitlines()]
assert sent[-1]["params"] == {
"sessionId": "main", "contentBlocks": [{"type": "text", "text": " preserve whitespace "}],
}
def test_concurrent_runs_keep_their_own_results(sdk: DeepSeekHarness) -> None:
session = sdk.start_session("main")
entered = threading.Event()
with ThreadPoolExecutor(max_workers=2) as workers:
first = workers.submit(session.run, "hold-prompt", on_notification=lambda notice: entered.set())
try:
assert entered.wait(5), "the first run must observe its receipt"
second = workers.submit(session.run, "/compact")
assert isinstance(second.result(timeout=5), RunResult)
finally:
release(sdk)
result = first.result(timeout=5)
assert isinstance(result, RunResult)
assert result.final_response == "answer for hold-prompt"
assert_released(sdk)
@pytest.mark.parametrize("text", ["missing-run", "missing-done", "missing-both", "unrelated-done", "invalid-done"])
def test_command_result_rejects_incomplete_lifecycle(sdk: DeepSeekHarness, text: str) -> None:
session = sdk.start_session("main")
with pytest.raises(SdkProtocolError, match="lifecycle"):
session.run(text)
assert_released(sdk)
assert session.run("after").final_response == "answer for after"
assert_released(sdk)
def test_failure_completion_can_precede_handler_settlement(peer: Path, tmp_path: Path) -> None:
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"), request_timeout_seconds=5,
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"), "QUERY_LIFECYCLE": "late-done",
"QUERY_RESPONSE": json.dumps({"kind": "command", "execution": {
"commandId": "early-failure", "result": {"kind": "success"}}}),
"QUERY_COMPLETION": json.dumps({"finalResponse": "goal service unloaded", "finishReason": "error"})},
) as sdk:
result = sdk.start_session("main").run("custom-response")
assert result.final_response == "goal service unloaded" and result.finish_reason == "error"
assert [event["type"] for event in result.events] == ["command/run", "command/done"]
assert_released(sdk)
@pytest.mark.parametrize("text,error", [("rpc-error", JsonRpcError), ("bad-route", SdkProtocolError), ("bad-execution", SdkProtocolError)])
def test_run_reports_routing_failures_without_retry(sdk: DeepSeekHarness, text: str, error: type[Exception]) -> None:
session = sdk.start_session("main")
with pytest.raises(error):
session.run(text)
assert_released(sdk)
assert isinstance(session.run("/compact"), RunResult)
@pytest.mark.parametrize("text", ["ordinary", "/compact"])
def test_observer_error_releases_run_subscription(sdk: DeepSeekHarness, text: str) -> None:
def reject(_notice: Notification) -> None:
raise RuntimeError("observer failed")
with pytest.raises(RuntimeError, match="observer failed"):
sdk.start_session("main").run(text, on_notification=reject)
assert_released(sdk)
assert isinstance(sdk.start_session("main").run("after observer"), RunResult)
def test_command_timeout_keeps_runtime_responsive(sdk: DeepSeekHarness) -> None:
sdk.client.config.request_timeout_seconds = 0.05
with pytest.raises(TimeoutError, match="session/query timed out"):
sdk.start_session("main").run("hold-command")
sdk.client.config.request_timeout_seconds = 5
assert_released(sdk)
release(sdk)
assert isinstance(sdk.start_session("main").run("/compact"), RunResult)
@pytest.mark.parametrize("response", [
None, [], {"kind": "unknown"}, {"kind": "prompt"}, {"kind": "prompt", "messageId": ""},
{"kind": "prompt", "messageId": 4},
{"kind": "command", "execution": {"commandId": "c"}},
{"kind": "command", "execution": {"commandId": "c", "result": {"kind": "success", "sourceEventSeq": True}}},
])
def test_run_rejects_invalid_routing_responses_as_protocol_errors(peer: Path, tmp_path: Path, response: object) -> None:
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"), request_timeout_seconds=5,
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"), "QUERY_RESPONSE": json.dumps(response)},
) as sdk:
with pytest.raises(SdkProtocolError) as raised:
sdk.start_session("main").run("custom-response")
if isinstance(response, dict) and response.get("kind") == "command":
assert isinstance(raised.value.__cause__, ValidationError)
assert_released(sdk)
assert isinstance(sdk.start_session("main").run("/compact"), RunResult)
def test_plan_run_waits_for_model_output_after_the_command_receipt(sdk: DeepSeekHarness) -> None:
admitted = threading.Event()
session = sdk.start_session("plan")
def observe(notice: Notification) -> None:
if notice.method == "session.event" and notice.payload["event"]["type"] == "command/done":
admitted.set()
with ThreadPoolExecutor(max_workers=1) as workers:
running = workers.submit(session.run, "/plan hold", on_notification=observe)
try:
assert admitted.wait(5)
assert not running.done()
finally:
release(sdk)
result = running.result(timeout=5)
assert isinstance(result, RunResult)
assert result.final_response == "Generated plan" and result.finish_reason == "completed"
assert_released(sdk)
@pytest.mark.parametrize("completion", [{"commandId": None}, {"finalResponse": 1}, {"finishReason": None}, {"finishReason": ""}])
def test_run_rejects_malformed_completion_and_releases_its_subscription(
peer: Path, tmp_path: Path, completion: dict,
) -> None:
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"),
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"),
"QUERY_COMPLETION": json.dumps(completion),
"QUERY_RESPONSE": json.dumps({"kind": "command", "execution": {
"commandId": "c", "result": {"kind": "success"}}})},
) as sdk:
with pytest.raises(SdkProtocolError):
sdk.run("custom-response")
assert_released(sdk)
@pytest.mark.parametrize("execution", [
{},
{"commandId": "command-1"},
{"commandId": "", "result": {"kind": "success"}},
{"commandId": "command-1", "result": {"kind": "unknown"}},
{"commandId": "command-1", "result": {"kind": "error"}},
{"commandId": "command-1", "result": {"kind": "error", "text": ""}},
{"commandId": "command-1", "result": {"kind": "error", "text": " \t\n"}},
{"commandId": "command-1", "result": {"kind": "success", "text": None}},
{"commandId": "command-1", "result": {"kind": "success", "sourceEventSeq": None}},
{"commandId": "command-1", "result": {"kind": "success", "sourceEventSeq": "4"}},
{"commandId": "command-1", "result": {"kind": "success", "sourceEventSeq": True}},
{"commandId": "command-1", "result": {"kind": "success", "sourceEventSeq": -1}},
{"commandId": "command-1", "result": {"kind": "success", "sourceEventSeq": 2**53}},
])
def test_run_rejects_malformed_native_results(peer: Path, tmp_path: Path, execution: dict) -> None:
with DeepSeekHarness(
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"),
_launch_args=(sys.executable, str(peer)),
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"), "QUERY_RESPONSE": json.dumps({
"kind": "command", "execution": execution,
})},
) as sdk:
with pytest.raises(SdkProtocolError) as raised:
sdk.run("custom-response")
assert isinstance(raised.value.__cause__, ValidationError)
assert_released(sdk)
def test_low_level_prompt_keeps_slash_text_as_an_ordinary_prompt(sdk: DeepSeekHarness, tmp_path: Path) -> None:
blocks = [{"type": "text", "text": "/compact"}]
assert sdk.client.session_prompt("main", blocks) == "literal-input"
sent = [json.loads(line) for line in (tmp_path / "requests.jsonl").read_text(encoding="utf-8").splitlines()]
assert sent[-1]["method"] == "session/prompt"
assert sent[-1]["params"] == {"sessionId": "main", "contentBlocks": blocks}