Merge remote-tracking branch 'origin/master' into feat/todo-multi-in-progress

This commit is contained in:
Chinesezjc
2026-08-06 11:25:28 +08:00
3992 changed files with 241706 additions and 57716 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write python/sdk/README.md
README.md: f1e16e724efd6f71f63e475e47d7e4d704b8ceac
README.zh.md: 11ff235a0eea23acab5f1737537c799021c35bea
README.md: 2d545688c58a2f1b755e647d7cda9555249e41c4
README.zh.md: 653256584f06c0e63b725953f7f27bc91fc198cc
+5 -3
View File
@@ -25,7 +25,7 @@ By default, the SDK launches the bundled single-file `dsh-jsonrpc-agent` executa
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
provider="deepseek",
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
cordis="examples/jsonrpc-agent/cordis.yml",
@@ -33,9 +33,11 @@ with DeepSeekHarness(
result = harness.run("Make the requested code change.")
```
`provider` selects a provider route registered by the chosen Cordis composition; `model` is the model id resolved by that adapter. `max_tokens` is an optional positive per-request output-token cap for the root agent and its in-process descendants; omission leaves the provider default in control. Compaction summaries keep the separate limit configured by their compaction plugin. The bundled default composition registers `deepseek`. A custom composition can mount `llm-pi-ai`, configure provider-specific credentials/endpoints there, and select any provider/model present in pi-ai's installed catalog.
`provider` selects a provider route registered by the chosen Cordis composition; `model` is the model id resolved by that adapter. `max_tokens` is an optional positive per-request output-token cap for the root agent and its in-process descendants; omission leaves the provider default in control. Compaction summaries keep the separate limit configured by their compaction plugin. The bundled default composition registers `deepseek-official`. A custom composition can mount `llm-pi-ai`, configure provider-specific credentials/endpoints there, and select any provider/model present in pi-ai's installed catalog.
`HarnessClient` retains discovered subagent ancestry for the lifetime of the runtime process. During each `Session.run()`, `TurnResult.notifications` and `on_notification` receive the root session and all known descendant notifications in wire order, including nested subagent lifecycle and session events. `TurnResult.events` remains the root session's complete event stream, and `TurnResult.final_response` is the text content from its last `assistant/message`; descendant messages therefore cannot replace the root response.
`Session.run()` owns an activity interval from its prompt's durable inbox receipt through the next whole-agent idle and returns `RunResult(session_id, final_response, events, notifications, session_root)`. The result has no prompt-level status or turn reason: `final_response` is the last committed root-session assistant text in the interval, not an output causally assigned to the prompt. Steering, injected context, and other queued work may contribute before idle.
`HarnessClient` retains discovered subagent ancestry for the lifetime of the runtime process. During each `Session.run()`, `RunResult.notifications` and `on_notification` receive the root session and all known descendant notifications in wire order, including nested subagent lifecycle and session events. `RunResult.events` contains root-session events only, so descendant messages cannot replace the root response. The low-level `session_prompt()` returns the queued `MessageId` immediately; callers that bypass `Session.run()` own any later activity boundary themselves.
The same behavior can be selected for the runtime subprocess with `DSH_CORDIS_CONFIG`. The injection lives in `HarnessClient.start()`, so the low-level client's default launch gets it too: when the launch resolves to the bundled runtime and neither `cordis` nor a non-empty `DSH_CORDIS_CONFIG` is set (the runtime treats an empty value as absent, and so does the injection check), the bundled default configuration is used; an explicit `runtime_bin`, `bridge_bin`, or `launch_args_override` disables the injection entirely. See the [sdk-runtime README](../sdk-runtime/README.md) for the runtime carriers (production exe vs dev-only node closure) and how to obtain them.
+5 -3
View File
@@ -21,7 +21,7 @@ with DeepSeekHarness() as harness:
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
provider="deepseek",
provider="deepseek-official",
model="deepseek-v4-flash",
max_tokens=49_152,
cordis="examples/jsonrpc-agent/cordis.yml",
@@ -29,9 +29,11 @@ with DeepSeekHarness(
result = harness.run("Make the requested code change.")
```
`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。`max_tokens` 是可选的正整数,用于限制根 agent 及其进程内后代每次请求的输出 token;省略时由提供方默认值控制。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。
`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。`max_tokens` 是可选的正整数,用于限制根 agent(智能体)及其进程内后代每次请求的输出 token;省略时由提供方默认值控制。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek-official`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。
`HarnessClient` 会在运行时进程的生命周期内保留已发现的 subagent(子 agent)祖先关系。每次执行 `Session.run()` 时,`TurnResult.notifications``on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`TurnResult.events` 仍只保存根会话的完整事件流,`TurnResult.final_response` 则取该会话最后一个 `assistant/message` 的文本内容,因此后代消息不会覆盖根会话回复
`Session.run()` 拥有一个从提示词的持久 inbox 回执开始、到整个 agent 下一次进入 idle 为止的活动区间,并返回 `RunResult(session_id, final_response, events, notifications, session_root)`。结果不携带提示词级状态或轮次原因:`final_response` 是该区间内根会话最后提交的助手文本,并非因果上归属于该提示词的输出。steering(中途引导)、注入的上下文和其他排队工作都可能在 idle 前参与其中
`HarnessClient` 会在运行时进程的生命周期内保留已发现的 subagent(子 agent)祖先关系。每次执行 `Session.run()` 时,`RunResult.notifications``on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`RunResult.events` 只包含根会话事件,因此后代消息不会覆盖根会话回复。底层 `session_prompt()` 会立即返回已排队消息的 `MessageId`;绕过 `Session.run()` 的调用方必须自行负责后续的活动边界。
同样的行为也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程选定。注入逻辑位于 `HarnessClient.start()`,因此底层客户端的默认启动也具有此行为:当启动解析到内置运行时,且 `cordis` 与非空的 `DSH_CORDIS_CONFIG` 均未设置时(运行时把空值视为缺省,注入检查与之一致),使用内置的默认配置;显式给出 `runtime_bin``bridge_bin``launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
+2 -2
View File
@@ -1,4 +1,4 @@
from .api import DeepSeekHarness, DeepSeekHarnessConfig, Session, TurnResult
from .api import DeepSeekHarness, DeepSeekHarnessConfig, RunResult, Session
from .client import HarnessClient, HarnessConfig
from .models import IncomingRequest, InitializeResponse, JsonObject, Notification, ServerInfo
@@ -6,7 +6,7 @@ __all__ = [
"DeepSeekHarness",
"DeepSeekHarnessConfig",
"Session",
"TurnResult",
"RunResult",
"HarnessClient",
"HarnessConfig",
"IncomingRequest",
+34 -17
View File
@@ -18,7 +18,7 @@ class DeepSeekHarnessConfig:
intentionally override or inject variables for a subprocess.
"""
provider: str = "deepseek"
provider: str = "deepseek-official"
model: str = "deepseek-v4-flash"
max_tokens: int | None = None
cwd: str | None = None
@@ -35,9 +35,8 @@ class DeepSeekHarnessConfig:
@dataclass(slots=True)
class TurnResult:
class RunResult:
session_id: str
status: str
final_response: str
events: list[JsonObject]
notifications: list[Notification]
@@ -119,7 +118,7 @@ class DeepSeekHarness:
*,
session_id: str | None = None,
on_notification: Callable[[Notification], None] | None = None,
) -> TurnResult:
) -> RunResult:
return self.start_session(session_id).run(input, on_notification=on_notification)
@@ -133,15 +132,12 @@ class Session:
input: str | list[JsonObject],
*,
on_notification: Callable[[Notification], None] | None = None,
) -> TurnResult:
) -> RunResult:
content_blocks = normalize_input(input)
notifications: list[Notification] = []
events: list[JsonObject] = []
status = "error"
finished = False
def collect(notification: Notification) -> None:
nonlocal finished, status
notifications.append(notification)
if on_notification is not None:
on_notification(notification)
@@ -152,25 +148,31 @@ class Session:
event = notification.payload.get("event")
if isinstance(event, dict):
events.append(event)
if notification.method == "session.finished" and notification.payload.get("sessionId") == self.id:
status = str(notification.payload.get("status") or "ok")
finished = True
with self.harness.client.subscribe_session_notifications(self.id) as subscription:
self.harness.client.session_prompt(
message_id = self.harness.client.session_prompt(
self.id,
content_blocks,
on_notification=collect,
notification_subscription=subscription,
)
while not finished:
received = False
while True:
notification = subscription.next()
if not received:
if not _is_inbox_receipt(notification, self.id, message_id):
continue
received = True
collect(notification)
if (
notification.method == "session.status"
and notification.payload.get("sessionId") == self.id
and notification.payload.get("status") == "idle"
):
break
return TurnResult(
return RunResult(
session_id=self.id,
status=status,
final_response=final_response(events),
events=events,
notifications=notifications,
@@ -178,6 +180,19 @@ class Session:
)
def _is_inbox_receipt(notification: Notification, session_id: str, message_id: str) -> bool:
if notification.method != "session.event" or notification.payload.get("sessionId") != session_id:
return False
event = notification.payload.get("event")
if not isinstance(event, dict) or event.get("type") != "agent/inbox/spliced":
return False
data = event.get("data")
inserted = data.get("inserted") if isinstance(data, dict) else None
return isinstance(inserted, list) and any(
isinstance(message, dict) and message.get("id") == message_id for message in inserted
)
def normalize_input(input: str | list[JsonObject]) -> list[JsonObject]:
if isinstance(input, str):
return [{"type": "text", "text": input}]
@@ -191,7 +206,9 @@ def final_response(events: list[JsonObject]) -> str:
data = event.get("data")
if not isinstance(data, dict):
continue
content = data.get("content")
message = data.get("message")
content_owner = message if isinstance(message, dict) else data
content = content_owner.get("content")
if not isinstance(content, list):
continue
parts: list[str] = []
+5 -4
View File
@@ -10,7 +10,7 @@ import uuid
from collections import deque
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Literal, TypeAlias, TypeVar
from typing import Callable, TypeAlias, TypeVar
from pydantic import BaseModel
@@ -142,9 +142,9 @@ class HarnessClient:
*,
on_notification: Callable[[Notification], None] | None = None,
notification_subscription: "NotificationSubscription | None" = None,
) -> None:
) -> str:
payload: JsonObject = {"sessionId": session_id, "contentBlocks": content_blocks}
self.request(
response = self.request(
"session/prompt",
payload,
response_model=_SessionPromptResponse,
@@ -152,6 +152,7 @@ class HarnessClient:
notification_filter=self._notification_belongs_to_session_tree(session_id),
notification_subscription=notification_subscription,
)
return response.messageId
def request(
self,
@@ -536,7 +537,7 @@ class NotificationSubscription:
class _SessionPromptResponse(BaseModel):
accepted: Literal[True]
messageId: str
class _ShutdownResponse(BaseModel):
@@ -73,9 +73,7 @@ def run_smoke(repo_root: Path, keep_sessions: bool) -> None:
"Please reply with a short confirmation and do not call tools.",
session_id="sdk-smoke-main",
)
print(f"turn_status={result.status}")
print(f"final_response={result.final_response}")
assert result.status == "ok", result
assert "configured HTTP model endpoint" in result.final_response
assert len(MockCompletionHandler.requests) == 1
request = MockCompletionHandler.requests[0]
+2 -2
View File
@@ -74,7 +74,7 @@ def test_bundled_runtime_boots_a_cordis_config(tmp_path: Path, mode: str) -> Non
(tmp_path / "cordis.yml").write_text(_CORDIS_YML)
with _client(tmp_path, launch_args) as client:
init = client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
init = client.initialize(provider="deepseek-official", cwd=str(tmp_path), model="deepseek-v4-pro")
assert init.serverInfo is not None
assert init.serverInfo.name == "deepseek-harness-sdk-runtime"
@@ -91,7 +91,7 @@ def test_bundled_runtime_surfaces_unbundled_plugin_failure(tmp_path: Path, mode:
client.start()
try:
with pytest.raises((TransportClosedError, TimeoutError)) as excinfo:
client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
client.initialize(provider="deepseek-official", cwd=str(tmp_path), model="deepseek-v4-pro")
finally:
client.close()
+66 -49
View File
@@ -39,6 +39,9 @@ for line in sys.stdin:
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": params["sessionId"], "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
print(json.dumps({
"jsonrpc": "2.0",
"method": "session.event",
@@ -46,16 +49,20 @@ for line in sys.stdin:
"sessionId": params["sessionId"],
"event": {
"type": "assistant/message",
"data": {"content": [{"type": "text", "text": "hello from runtime"}]},
"data": {
"message": {
"role": "assistant",
"content": [{"type": "text", "text": "hello from runtime"}],
},
},
},
},
}), flush=True)
print(json.dumps({
"jsonrpc": "2.0",
"method": "session.finished",
"params": {"sessionId": params["sessionId"], "status": "ok"},
"method": "session.status",
"params": {"sessionId": params["sessionId"], "status": "idle"},
}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -78,9 +85,8 @@ for line in sys.stdin:
) as harness:
result = harness.run("say hello", session_id="main")
assert result.status == "ok"
assert result.final_response == "hello from runtime"
assert result.events[0]["type"] == "assistant/message"
assert result.events[-1]["type"] == "assistant/message"
dumped_env = json.loads(env_dump.read_text())
assert dumped_env["DEEPSEEK_API_KEY"] == "env-key"
assert dumped_env["DEEPSEEK_BASE_URL"] == "http://127.0.0.1:4321"
@@ -89,7 +95,7 @@ for line in sys.stdin:
assert dumped_env["DSH_CORDIS_CONFIG"] == str(tmp_path / "cordis.yml")
assert json.loads(init_dump.read_text()) == {
"cwd": str(tmp_path),
"provider": "deepseek",
"provider": "deepseek-official",
"model": "deepseek-v4-flash",
"maxTokens": 4096,
}
@@ -108,9 +114,11 @@ for line in sys.stdin:
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "main", "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": "main", "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": "main", "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -128,8 +136,7 @@ for line in sys.stdin:
on_notification=lambda notification: seen.append(notification.method),
)
assert result.status == "ok"
assert seen == ["subagent.started", "session.finished"]
assert seen == ["session.event", "session.status", "subagent.started", "session.status"]
def test_relative_cwd_is_absolute_in_process_environment_and_wire(
@@ -184,10 +191,12 @@ for line in sys.stdin:
if method == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "main", "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": "main", "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "main", "childSessionId": "child"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": "main", "childSessionId": "child", "status": "ok", "stopReason": "completed"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "main", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": "main", "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -200,11 +209,12 @@ for line in sys.stdin:
) as harness:
result = harness.run("spawn a helper", session_id="main")
assert result.status == "ok"
assert [notification.method for notification in result.notifications] == [
"session.event",
"session.status",
"subagent.started",
"subagent.finished",
"session.finished",
"session.status",
]
@@ -224,6 +234,9 @@ for line in sys.stdin:
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
root = (msg.get("params") or {})["sessionId"]
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": root, "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": root, "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": root, "childSessionId": "child"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": "child", "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "child response"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.started", "params": {"parentSessionId": "child", "childSessionId": "grandchild"}}), flush=True)
@@ -231,8 +244,7 @@ for line in sys.stdin:
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": "child", "childSessionId": "grandchild", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "subagent.finished", "params": {"parentSessionId": root, "childSessionId": "child", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": root, "event": {"type": "assistant/message", "data": {"content": [{"type": "text", "text": "root response"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": root, "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": root, "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -251,10 +263,11 @@ for line in sys.stdin:
)
assert harness.client._notifications.qsize() == 0
assert result.status == "ok"
assert result.final_response == "root response"
assert [event["data"]["content"][0]["text"] for event in result.events] == ["root response"]
assert [event["data"]["content"][0]["text"] for event in result.events if event["type"] == "assistant/message"] == ["root response"]
assert [notification.method for notification in result.notifications] == [
"session.event",
"session.status",
"subagent.started",
"session.event",
"subagent.started",
@@ -262,7 +275,7 @@ for line in sys.stdin:
"subagent.finished",
"subagent.finished",
"session.event",
"session.finished",
"session.status",
]
assert seen == [notification.method for notification in result.notifications]
@@ -282,10 +295,12 @@ for line in sys.stdin:
elif method == "session/prompt":
params = msg.get("params") or {}
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)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": "other", "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": "other", "status": "idle"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": params["sessionId"], "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
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)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": params["sessionId"], "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -298,9 +313,8 @@ for line in sys.stdin:
) as harness:
result = harness.run("stay in your lane", session_id="main")
assert result.status == "ok"
assert result.final_response == "right session"
assert [notification.payload.get("sessionId") for notification in result.notifications] == ["main", "main"]
assert [notification.payload.get("sessionId") for notification in result.notifications] == ["main"] * 4
def test_high_level_session_run_does_not_accumulate_global_notifications(tmp_path: Path) -> None:
@@ -317,9 +331,11 @@ for line in sys.stdin:
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-runtime"}}}), flush=True)
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": params["sessionId"], "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": "message-1"}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": params["sessionId"], "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
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)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": params["sessionId"], "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": params["sessionId"], "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -328,11 +344,10 @@ for line in sys.stdin:
with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
result = harness.run("one turn", session_id="main")
assert result.status == "ok"
assert harness.client._notifications.qsize() == 0
def test_session_run_waits_for_late_finished_without_replaying_stale_notifications(tmp_path: Path) -> None:
def test_session_run_waits_for_late_idle_without_replaying_stale_notifications(tmp_path: Path) -> None:
script = tmp_path / "fake_runtime.py"
script.write_text(
"""
@@ -350,15 +365,17 @@ for line in sys.stdin:
turn += 1
params = msg.get("params") or {}
session_id = params["sessionId"]
message_id = f"message-{turn}"
print(json.dumps({"jsonrpc": "2.0", "method": "session.event", "params": {"sessionId": session_id, "event": {"type": "agent/inbox/spliced", "data": {"target": "next-turn", "start": 0, "inserted": [{"id": message_id}]}}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": session_id, "status": "running"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": message_id}}), flush=True)
if turn == 1:
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)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": session_id, "status": "idle"}}), flush=True)
else:
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
time.sleep(0.05)
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)
print(json.dumps({"jsonrpc": "2.0", "method": "session.finished", "params": {"sessionId": session_id, "status": "ok"}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "session.status", "params": {"sessionId": session_id, "status": "idle"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -371,7 +388,7 @@ for line in sys.stdin:
assert first.final_response == "first"
assert second.final_response == "second"
assert [notification.payload.get("sessionId") for notification in second.notifications] == ["main", "main"]
assert [notification.payload.get("sessionId") for notification in second.notifications] == ["main"] * 4
def test_client_starts_subprocess_sends_requests_and_routes_notifications(tmp_path: Path) -> None:
@@ -389,7 +406,7 @@ for line in sys.stdin:
elif method == "session/prompt":
params = msg.get("params") or {}
print(json.dumps({"jsonrpc": "2.0", "method": "llm/request", "params": {"requestId": "req-1", "sessionId": params["sessionId"], "model": "dsagent", "messages": []}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -399,7 +416,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
@@ -526,7 +543,7 @@ for line in sys.stdin:
elif method in {"emit-first", "emit-second"}:
print(json.dumps({"jsonrpc": "2.0", "method": "tick", "params": {"source": method}}), flush=True)
elif method == "session/prompt":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"accepted": True}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"messageId": "message-1"}}), flush=True)
elif method == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
@@ -537,7 +554,7 @@ for line in sys.stdin:
raise RuntimeError("bad notification filter")
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
with (
client.subscribe_notifications(broken_filter) as broken,
client.subscribe_notifications(lambda notification: notification.method == "tick") as healthy,
@@ -574,7 +591,7 @@ for line in sys.stdin:
)
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
with pytest.raises(ValueError):
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
@@ -603,7 +620,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
request = client.next_request()
assert request.id == "bridge-req-1"
@@ -637,7 +654,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
@@ -659,7 +676,7 @@ time.sleep(60)
) as client:
start = time.monotonic()
try:
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
except TimeoutError:
assert time.monotonic() - start < 2
else:
@@ -695,7 +712,7 @@ for line in sys.stdin:
client.start()
proc = client._proc
assert proc is not None
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
start = time.monotonic()
client.close()
assert time.monotonic() - start < 2
@@ -726,7 +743,7 @@ for line in sys.stdin:
assert proc is not None
with pytest.raises(Exception, match="bad initialize"):
client.initialize(provider="deepseek", cwd=".", model="dsagent")
client.initialize(provider="deepseek-official", cwd=".", model="dsagent")
assert proc.wait(timeout=1) is not None
assert client._proc is None
@@ -768,7 +785,7 @@ for line in sys.stdin:
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
client.start()
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
client.close()
client.close()
@@ -791,7 +808,7 @@ sys.exit(42)
)
) as client:
with pytest.raises(Exception, match="fatal bridge exploded"):
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
def test_client_serializes_concurrent_writes(tmp_path: Path) -> None:
@@ -822,7 +839,7 @@ with open(os.environ["SEEN"], "w") as seen:
env={"SEEN": str(output)},
)
) as client:
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
threads = [
threading.Thread(target=client.notify, args=(f"notice-{index}", {"index": index}))
for index in range(50)
@@ -893,7 +910,7 @@ def test_client_default_launch_uses_bundled_runtime_and_injects_default_config(
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
with HarnessClient(HarnessConfig(env={"ENV_DUMP": str(env_dump)})) as client:
init = client.initialize(provider="deepseek", cwd="/workspace", model="deepseek-v4-pro")
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="deepseek-v4-pro")
assert init.serverInfo.name == "bundled-runtime"
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == str(default_config)
@@ -909,7 +926,7 @@ def test_client_respects_explicit_config_over_bundled_default(
with HarnessClient(
HarnessConfig(env={"ENV_DUMP": str(env_dump), "DSH_CORDIS_CONFIG": "./explicit.yml"})
) as client:
client.initialize(provider="deepseek", cwd="/workspace", model="deepseek-v4-pro")
client.initialize(provider="deepseek-official", cwd="/workspace", model="deepseek-v4-pro")
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"
+21
View File
@@ -37,3 +37,24 @@ def test_repository_version_rejects_non_stable_versions(tmp_path: Path) -> None:
with pytest.raises(ValueError, match="must be stable X.Y.Z"):
build_python_release.repository_version(tmp_path)
@pytest.mark.parametrize(("target", "with_helper"), [("linux-x64", False), ("macos-arm64", True)])
def test_stage_runtime_copies_platform_payload(
tmp_path: Path, target: str, with_helper: bool
) -> None:
executable = tmp_path / f"dsh-jsonrpc-agent-pkg-{target}"
executable.write_bytes(b"runtime")
executable.chmod(0o755)
expected = {executable.name: b"runtime"}
if with_helper:
spawn_helper = Path(f"{executable}-spawn-helper")
spawn_helper.write_bytes(b"helper")
spawn_helper.chmod(0o755)
expected[spawn_helper.name] = b"helper"
destination = tmp_path / "staging"
build_python_release.stage_runtime(destination, "1.2.3", executable, executable.name)
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
assert {path.name: path.read_bytes() for path in runtime_dir.glob("dsh-jsonrpc-agent-pkg-*")} == expected
@@ -2,6 +2,9 @@
from __future__ import annotations
from pathlib import Path
import deepseek_harness_runtime as runtime
import pytest
from deepseek_harness_runtime import (
@@ -39,3 +42,20 @@ def test_explicit_mode_wins_over_env_mode(monkeypatch: pytest.MonkeyPatch) -> No
except FileNotFoundError:
return # explicit 'exe' was honored; only the artifact is missing
assert args[0].endswith(("-x64", "-arm64"))
def test_runtime_requires_spawn_helper_only_on_macos(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir()
linux = runtime_dir / "dsh-jsonrpc-agent-pkg-linux-x64"
linux.touch()
(runtime_dir / "dsh-jsonrpc-agent-pkg-macos-arm64").touch()
monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "macos-arm64")
with pytest.raises(FileNotFoundError, match="node-pty spawn helper"):
runtime.bundled_runtime_path()
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
assert runtime.bundled_runtime_path() == linux