fix(python-sdk): harden profile runtime startup

Resolve packaged profile proxies with Node ESM import conditions from each package installation, and fail loud when an explicit runtime export or legacy main entry is missing. Serialize the shared profile fallback under the existing cross-process writer lock so concurrent dsh processes cannot observe partial proxies; either carrier now replaces the other carrier’s managed entry without manual cleanup.

Give Python initialize its own 10-second default bound and name the selected profile in timeout diagnostics, while leaving ordinary agent turns unbounded by default. Package the dynamically resolved web frontend and skill-badge assets so the runtime wheel’s normal dsh profiles do not depend on pkg static-discovery accidents.

Rewrite the root launch rule and every active stale SDK-runtime note to the shipped dsh profile architecture in both languages. Focused tests prove import-only and transitive package exports, lock contention, cross-carrier transitions, missing-entry failures, asset inventory, and bounded initialization.
This commit is contained in:
Tianyi Cui
2026-08-24 17:28:27 +08:00
parent f0f9b294dd
commit 9edf1b9f10
35 changed files with 321 additions and 127 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: edf5ced63f181bb17895d81cc060577916554305
README.zh.md: e03a19e5b8f0cef2e2f9d86f8d91b8808e268ff4
README.md: ef2ba1764aa93fc6499941dd372a1de94f33d454
README.zh.md: b5c36b6ff0fbce303649ae92efd7e1edac954e82
+1 -1
View File
@@ -26,7 +26,7 @@ with DeepSeekHarness(
print(result.final_response)
```
`DeepSeekHarness` starts lazily and reuses its runtime until `close()` or context-manager exit. `cwd` is the agent workspace; `runtime_cwd` independently selects the subprocess working directory. Both become absolute before launch. `provider`, `model`, and optional positive `max_tokens` are sent during JSON-RPC initialization. `base_url` and `api_key` explicitly override `DEEPSEEK_BASE_URL` and `DEEPSEEK_API_KEY` in the child environment.
`DeepSeekHarness` starts lazily and reuses its runtime until `close()` or context-manager exit. The initial profile handshake has an independent 10-second default bound through `initialize_timeout_seconds`; ordinary turns remain unbounded unless `request_timeout_seconds` is set. A timeout names the selected profile and includes retained runtime diagnostics. `cwd` is the agent workspace; `runtime_cwd` independently selects the subprocess working directory. Both become absolute before launch. `provider`, `model`, and optional positive `max_tokens` are sent during JSON-RPC initialization. `base_url` and `api_key` explicitly override `DEEPSEEK_BASE_URL` and `DEEPSEEK_API_KEY` in the child environment.
## Customize plugins
+1 -1
View File
@@ -26,7 +26,7 @@ with DeepSeekHarness(
print(result.final_response)
```
`DeepSeekHarness` 延迟启动运行时,并在调用 `close()` 或退出上下文管理器前复用该进程。`cwd` 是 agent workspace`runtime_cwd` 独立选择子进程工作目录。两者都会在启动前转成绝对路径。`provider``model` 和可选的正整数 `max_tokens` 通过 JSON-RPC 初始化发送。`base_url``api_key` 会显式覆盖子进程环境中的 `DEEPSEEK_BASE_URL``DEEPSEEK_API_KEY`
`DeepSeekHarness` 延迟启动运行时,并在调用 `close()` 或退出上下文管理器前复用该进程。首次 profile 握手通过 `initialize_timeout_seconds` 使用独立的 10 秒默认上限;普通轮次在未设置 `request_timeout_seconds` 时仍不设上限。超时诊断会指明所选 profile,并包含保留的运行时诊断。`cwd` 是 agent workspace`runtime_cwd` 独立选择子进程工作目录。两者都会在启动前转成绝对路径。`provider``model` 和可选的正整数 `max_tokens` 通过 JSON-RPC 初始化发送。`base_url``api_key` 会显式覆盖子进程环境中的 `DEEPSEEK_BASE_URL``DEEPSEEK_API_KEY`
## 自定义插件
+2
View File
@@ -29,6 +29,7 @@ class DeepSeekHarnessConfig:
patches: tuple[str, ...] = ()
dsh_home: str | None = None
env: dict[str, str] = field(default_factory=dict)
initialize_timeout_seconds: float = 10.0
request_timeout_seconds: float | None = None
shutdown_timeout_seconds: float | None = 1.0
base_url: str | None = None
@@ -79,6 +80,7 @@ class DeepSeekHarness:
dsh_home=self.config.dsh_home,
cwd=runtime_cwd,
env=env,
initialize_timeout_seconds=self.config.initialize_timeout_seconds,
request_timeout_seconds=self.config.request_timeout_seconds,
shutdown_timeout_seconds=self.config.shutdown_timeout_seconds,
),
+10 -1
View File
@@ -31,6 +31,7 @@ class HarnessConfig:
dsh_home: str | None = None
cwd: str | None = None
env: dict[str, str] | None = None
initialize_timeout_seconds: float = 10.0
request_timeout_seconds: float | None = None
shutdown_timeout_seconds: float | None = 1.0
_launch_args: tuple[str, ...] | None = None
@@ -137,7 +138,15 @@ class HarnessClient:
if max_tokens is not None:
payload["maxTokens"] = max_tokens
try:
return self.request("initialize", payload, response_model=InitializeResponse)
return self.request(
"initialize",
payload,
response_model=InitializeResponse,
timeout_seconds=self.config.initialize_timeout_seconds,
)
except TimeoutError as error:
self.close()
raise TimeoutError(f"{error}\nselected dsh profile {self.config.profile!r}") from error
except BaseException as error:
self.close()
diagnostics = self._runtime_diagnostics()
+5 -1
View File
@@ -731,7 +731,8 @@ time.sleep(60)
with HarnessClient(
HarnessConfig(
_launch_args=(sys.executable, str(script)),
request_timeout_seconds=0.1,
profile="web",
initialize_timeout_seconds=0.1,
)
) as client:
start = time.monotonic()
@@ -740,6 +741,7 @@ time.sleep(60)
except TimeoutError as exc:
assert time.monotonic() - start < 2
assert "bridge is still starting" in str(exc)
assert "profile 'web'" in str(exc)
else:
raise AssertionError("initialize should time out")
@@ -832,6 +834,8 @@ def test_public_signatures_omit_unsupported_wire_parameters() -> None:
assert {"dsh_bin", "profile", "patches", "dsh_home"} <= set(
HarnessConfig.__dataclass_fields__
)
assert "initialize_timeout_seconds" in DeepSeekHarnessConfig.__dataclass_fields__
assert "initialize_timeout_seconds" in 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__