mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
feat(python-sdk): launch dsh profiles from explicit homes
Replace complete-config, session_root, runtime-bin, bridge-bin, and public argv override options with dsh_bin, profile, ordered patches, and dsh_home. Resolve executable/home/patch/cwd paths before spawn, select the sdk profile by default, and fail before launch unless dsh_home or non-empty DSH_HOME is explicit; Python never inherits ~/.dsh silently. Remove Python-owned DSH_CORDIS_CONFIG, DSH_SESSION_ROOT, and DSH_CWD injection and drop session_root from RunResult. Keep arbitrary argv only as an underscore-prefixed fake-runtime adapter, retain provider/model/token and process controls, and append subprocess stderr to initialization JSON-RPC errors so profile boot failures name their actual plugin cause. Unit and carrier tests cover both exe and Node modes.
This commit is contained in:
@@ -9,7 +9,8 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig, Notification, SdkProtocolError
|
||||
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig, Notification, RunResult, SdkProtocolError
|
||||
from deepseek_harness.errors import JsonRpcError
|
||||
|
||||
|
||||
def test_high_level_sdk_runs_turn_and_collects_final_response(tmp_path: Path) -> None:
|
||||
@@ -95,9 +96,7 @@ for line in sys.stdin:
|
||||
model="deepseek-v4-flash",
|
||||
max_tokens=4096,
|
||||
cwd=str(tmp_path),
|
||||
cordis=str(tmp_path / "cordis.yml"),
|
||||
session_root=str(tmp_path / "sessions"),
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
env={
|
||||
"ENV_DUMP": str(env_dump),
|
||||
"INIT_DUMP": str(init_dump),
|
||||
@@ -113,9 +112,9 @@ for line in sys.stdin:
|
||||
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"
|
||||
assert dumped_env["DSH_CWD"] == str(tmp_path)
|
||||
assert dumped_env["DSH_SESSION_ROOT"] == str(tmp_path / "sessions")
|
||||
assert dumped_env["DSH_CORDIS_CONFIG"] == str(tmp_path / "cordis.yml")
|
||||
assert dumped_env["DSH_CWD"] is None
|
||||
assert dumped_env["DSH_SESSION_ROOT"] is None
|
||||
assert dumped_env["DSH_CORDIS_CONFIG"] is None
|
||||
assert json.loads(init_dump.read_text()) == {
|
||||
"cwd": str(tmp_path),
|
||||
"provider": "deepseek-official",
|
||||
@@ -150,7 +149,7 @@ for line in sys.stdin:
|
||||
|
||||
seen: list[str] = []
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
session = harness.start_session("main")
|
||||
@@ -188,7 +187,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
with pytest.raises(
|
||||
@@ -224,7 +223,7 @@ for line in sys.stdin:
|
||||
with DeepSeekHarness(
|
||||
cwd=".",
|
||||
runtime_cwd=".",
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
env={"CAPTURE": str(capture)},
|
||||
):
|
||||
pass
|
||||
@@ -232,7 +231,7 @@ for line in sys.stdin:
|
||||
expected = str(tmp_path.resolve())
|
||||
assert json.loads(capture.read_text()) == {
|
||||
"process": expected,
|
||||
"environment": expected,
|
||||
"environment": None,
|
||||
"wire": expected,
|
||||
}
|
||||
|
||||
@@ -263,7 +262,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
result = harness.run("spawn a helper", session_id="main")
|
||||
@@ -312,7 +311,7 @@ for line in sys.stdin:
|
||||
|
||||
seen: list[str] = []
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
result = harness.run(
|
||||
@@ -367,7 +366,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with DeepSeekHarness(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
cwd=str(tmp_path),
|
||||
) as harness:
|
||||
result = harness.run("stay in your lane", session_id="main")
|
||||
@@ -401,7 +400,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
|
||||
with DeepSeekHarness(_launch_args=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
|
||||
result = harness.run("one turn", session_id="main")
|
||||
assert harness.client._notifications.qsize() == 0
|
||||
|
||||
@@ -441,7 +440,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with DeepSeekHarness(launch_args_override=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
|
||||
with DeepSeekHarness(_launch_args=(sys.executable, str(script)), cwd=str(tmp_path)) as harness:
|
||||
first = harness.run("first turn", session_id="main")
|
||||
second = harness.run("second turn", session_id="main")
|
||||
|
||||
@@ -473,7 +472,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
@@ -612,7 +611,7 @@ for line in sys.stdin:
|
||||
def broken_filter(_notification: object) -> bool:
|
||||
raise RuntimeError("bad notification filter")
|
||||
|
||||
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
|
||||
with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
with (
|
||||
client.subscribe_notifications(broken_filter) as broken,
|
||||
@@ -649,7 +648,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
|
||||
with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
with pytest.raises(ValueError):
|
||||
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
|
||||
@@ -677,7 +676,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
|
||||
@@ -711,7 +710,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
@@ -731,7 +730,7 @@ time.sleep(60)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
request_timeout_seconds=0.1,
|
||||
)
|
||||
) as client:
|
||||
@@ -767,7 +766,7 @@ for line in sys.stdin:
|
||||
|
||||
client = HarnessClient(
|
||||
HarnessConfig(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
shutdown_timeout_seconds=0.1,
|
||||
)
|
||||
)
|
||||
@@ -792,6 +791,7 @@ import sys
|
||||
for line in sys.stdin:
|
||||
msg = json.loads(line)
|
||||
if msg.get("method") == "initialize":
|
||||
print("initialize diagnostic", file=sys.stderr, flush=True)
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32000, "message": "bad initialize"}}), flush=True)
|
||||
elif msg.get("method") == "shutdown":
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
|
||||
@@ -799,14 +799,16 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
|
||||
client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
|
||||
client.start()
|
||||
proc = client._proc
|
||||
assert proc is not None
|
||||
|
||||
with pytest.raises(Exception, match="bad initialize"):
|
||||
with pytest.raises(JsonRpcError, match="bad initialize") as excinfo:
|
||||
client.initialize(provider="deepseek-official", cwd=".", model="dsagent")
|
||||
|
||||
assert excinfo.value.code == -32000
|
||||
assert "initialize diagnostic" in str(excinfo.value)
|
||||
assert proc.wait(timeout=1) is not None
|
||||
assert client._proc is None
|
||||
|
||||
@@ -824,6 +826,16 @@ def test_public_signatures_omit_unsupported_wire_parameters() -> None:
|
||||
assert "max_tokens" in inspect.signature(HarnessClient.initialize).parameters
|
||||
assert "client_name" not in HarnessConfig.__dataclass_fields__
|
||||
assert "client_version" not in HarnessConfig.__dataclass_fields__
|
||||
assert {"dsh_bin", "profile", "patches", "dsh_home"} <= set(
|
||||
DeepSeekHarnessConfig.__dataclass_fields__
|
||||
)
|
||||
assert {"dsh_bin", "profile", "patches", "dsh_home"} <= set(
|
||||
HarnessConfig.__dataclass_fields__
|
||||
)
|
||||
for removed in ("cordis", "session_root", "runtime_bin", "bridge_bin", "launch_args_override"):
|
||||
assert removed not in DeepSeekHarnessConfig.__dataclass_fields__
|
||||
assert removed not in HarnessConfig.__dataclass_fields__
|
||||
assert "session_root" not in RunResult.__dataclass_fields__
|
||||
|
||||
|
||||
def test_client_close_is_idempotent_before_and_after_start(tmp_path: Path) -> None:
|
||||
@@ -845,7 +857,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
|
||||
client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
|
||||
client.start()
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
client.close()
|
||||
@@ -865,7 +877,7 @@ sys.exit(42)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
request_timeout_seconds=2,
|
||||
)
|
||||
) as client:
|
||||
@@ -897,7 +909,7 @@ with open(os.environ["SEEN"], "w") as seen:
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
launch_args_override=(sys.executable, str(script)),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
env={"SEEN": str(output)},
|
||||
)
|
||||
) as client:
|
||||
@@ -915,21 +927,22 @@ with open(os.environ["SEEN"], "w") as seen:
|
||||
json.loads(line)
|
||||
|
||||
|
||||
def _install_fake_bundled_runtime(
|
||||
def _install_fake_bundled_dsh(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> Path:
|
||||
"""Install a fake runtime package that records config and serves lifecycle calls.
|
||||
|
||||
Returns the fake bundled default config path.
|
||||
"""
|
||||
runtime = tmp_path / "dsh-jsonrpc-agent"
|
||||
) -> None:
|
||||
"""Install a fake runtime package that records dsh argv and serves lifecycle calls."""
|
||||
runtime = tmp_path / "dsh.py"
|
||||
runtime.write_text(
|
||||
"""#!/usr/bin/env python3
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
json.dump({"DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG")}, open(os.environ["ENV_DUMP"], "w"))
|
||||
json.dump({
|
||||
"argv": sys.argv[1:],
|
||||
"DSH_HOME": os.environ.get("DSH_HOME"),
|
||||
"DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG"),
|
||||
}, open(os.environ["ENV_DUMP"], "w"))
|
||||
for line in sys.stdin:
|
||||
msg = json.loads(line)
|
||||
if msg.get("method") == "initialize":
|
||||
@@ -939,58 +952,75 @@ for line in sys.stdin:
|
||||
break
|
||||
""".strip()
|
||||
)
|
||||
runtime.chmod(0o755)
|
||||
|
||||
default_config = tmp_path / "default-cordis.yml"
|
||||
module_dir = tmp_path / "deepseek_harness_runtime"
|
||||
module_dir.mkdir()
|
||||
(module_dir / "__init__.py").write_text(
|
||||
f"""
|
||||
def resolve_bundled_launch_args(mode=None):
|
||||
return ({str(runtime)!r},)
|
||||
|
||||
|
||||
def bundled_default_config_path():
|
||||
return {str(default_config)!r}
|
||||
return ({sys.executable!r}, {str(runtime)!r})
|
||||
""".strip()
|
||||
)
|
||||
|
||||
monkeypatch.syspath_prepend(str(tmp_path))
|
||||
monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
|
||||
return default_config
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ambient_config", [None, ""], ids=["unset", "empty-counts-as-absent"])
|
||||
def test_client_default_launch_uses_bundled_runtime_and_injects_default_config(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ambient_config: str | None
|
||||
) -> None:
|
||||
env_dump = tmp_path / "env.json"
|
||||
default_config = _install_fake_bundled_runtime(tmp_path, monkeypatch)
|
||||
if ambient_config is None:
|
||||
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
|
||||
|
||||
with HarnessClient(HarnessConfig(env={"ENV_DUMP": str(env_dump)})) as client:
|
||||
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)
|
||||
|
||||
|
||||
def test_client_respects_explicit_config_over_bundled_default(
|
||||
def test_client_default_launch_uses_bundled_dsh_sdk_profile_and_explicit_home(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
env_dump = tmp_path / "env.json"
|
||||
_install_fake_bundled_runtime(tmp_path, monkeypatch)
|
||||
home = tmp_path / "home"
|
||||
patch = tmp_path / "sdk.patch.yml"
|
||||
patch.write_text("[]\n")
|
||||
_install_fake_bundled_dsh(tmp_path, monkeypatch)
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.setenv("DSH_HOME", str(tmp_path / "ambient-home"))
|
||||
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
|
||||
|
||||
with HarnessClient(HarnessConfig(
|
||||
profile="sdk",
|
||||
patches=("sdk.patch.yml",),
|
||||
dsh_home=str(home),
|
||||
env={"ENV_DUMP": str(env_dump), "DSH_HOME": str(tmp_path / "env-home")},
|
||||
)) as client:
|
||||
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()) == {
|
||||
"argv": ["--profile", "sdk", "--patch", str(patch)],
|
||||
"DSH_HOME": str(home),
|
||||
"DSH_CORDIS_CONFIG": None,
|
||||
}
|
||||
|
||||
|
||||
def test_client_accepts_explicit_environment_dsh_home(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
env_dump = tmp_path / "env.json"
|
||||
home = tmp_path / "environment-home"
|
||||
_install_fake_bundled_dsh(tmp_path, monkeypatch)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(env={"ENV_DUMP": str(env_dump), "DSH_CORDIS_CONFIG": "./explicit.yml"})
|
||||
HarnessConfig(profile="custom", env={"ENV_DUMP": str(env_dump), "DSH_HOME": str(home)})
|
||||
) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="deepseek-v4-pro")
|
||||
|
||||
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"
|
||||
assert json.loads(env_dump.read_text()) == {
|
||||
"argv": ["--profile", "custom"],
|
||||
"DSH_HOME": str(home),
|
||||
"DSH_CORDIS_CONFIG": None,
|
||||
}
|
||||
|
||||
|
||||
def test_client_rejects_an_implicit_default_dsh_home(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
_install_fake_bundled_dsh(tmp_path, monkeypatch)
|
||||
monkeypatch.delenv("DSH_HOME", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="explicit dsh_home or non-empty DSH_HOME"):
|
||||
HarnessClient(HarnessConfig(env={})).start()
|
||||
|
||||
|
||||
def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -998,4 +1028,4 @@ def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.M
|
||||
monkeypatch.setattr(sys, "path", [])
|
||||
|
||||
with pytest.raises(FileNotFoundError, match="Install deepseek-harness-runtime-bin"):
|
||||
HarnessClient().start()
|
||||
HarnessClient(HarnessConfig(dsh_home="/explicit/home")).start()
|
||||
|
||||
Reference in New Issue
Block a user