Merge origin/master into worktree/agent-execution-context-rfc
This commit is contained in:
@@ -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
|
||||
README.md: 5525e8a7b88df3f686bb1fa3a08c3556acc2e655
|
||||
README.zh.md: 30723d6d78b84e16261898a88ed22c3c58fc83bf
|
||||
README.md: cdf38d4474a0e0148a4804e14c12971c76b38e27
|
||||
README.zh.md: 99d57c6f900371a46b94c5ea80b2a1665fd5e8d1
|
||||
@@ -26,4 +26,4 @@ Each wheel contains exactly one executable. The fixed tags are `py3-none-manylin
|
||||
|
||||
## Zero-config design
|
||||
|
||||
The runtime binary always demands an explicit config (`$DSH_CORDIS_CONFIG`, or a config path as an argv positional argument) and exits loudly without one — that hard semantic is part of the runtime's design and this package does not soften it. The bin (`dsh-jsonrpc-agent`) boots only the plugins the config lists; the serving surface (the stdio JSON-RPC server) is itself one of its entries (`@deepseek-ai/dsh-jsonrpc`), and without it the booted agent has no channel to the outside. This package checks in `runtime/cordis.yml` with the JSON-RPC serving entry, agent core, a preloaded DeepSeek adapter, JSONL persistence, and local bash. The adapter reads `DEEPSEEK_API_KEY` and `DEEPSEEK_BASE_URL`, while persistence and bash use `DSH_SESSION_ROOT` and `DSH_CWD` with manual-run fallbacks. When the caller uses no explicit config channel, the `deepseek_harness` client injects that file's path via `DSH_CORDIS_CONFIG` (injection conditions: [sdk README](../sdk/README.md)). Zero-config is thus an explicit, visible parameter pass in the wrapper, not a hidden fallback in the runtime.
|
||||
The runtime binary always demands an explicit config (`$DSH_CORDIS_CONFIG`, or a config path as an argv positional argument) and exits loudly without one — that hard semantic is part of the runtime's design and this package does not soften it. The bin (`dsh-jsonrpc-agent`) boots only the plugins the config lists; the serving surface (the stdio JSON-RPC server) is itself one of its entries (`@deepseek-ai/dsh-jsonrpc`), and without it the booted agent has no channel to the outside. This package checks in `runtime/cordis.yml` with the JSON-RPC serving entry, agent core, a preloaded DeepSeek adapter, JSONL persistence, local bash, and a local filesystem provider for bounded workspace-instruction loading. The adapter reads `DEEPSEEK_API_KEY` and `DEEPSEEK_BASE_URL`, while persistence, bash, and the filesystem provider use `DSH_SESSION_ROOT` and `DSH_CWD` with manual-run fallbacks. When the caller uses no explicit config channel, the `deepseek_harness` client injects that file's path via `DSH_CORDIS_CONFIG` (injection conditions: [sdk README](../sdk/README.md)). Zero-config is thus an explicit, visible parameter pass in the wrapper, not a hidden fallback in the runtime.
|
||||
@@ -26,4 +26,4 @@ exe 缺失时抛出 `FileNotFoundError`,并写明两种获取途径:在 deep
|
||||
|
||||
## 零配置设计
|
||||
|
||||
运行时二进制始终要求显式配置(`$DSH_CORDIS_CONFIG`,或作为 argv 位置参数的配置路径),缺了就报错退出——这一硬语义是运行时设计的一部分,本包不软化它。`bin`(`dsh-jsonrpc-agent`)只启动配置里列出的插件;对外服务接口(stdio JSON-RPC 服务器)也是其中一个条目(`@deepseek-ai/dsh-jsonrpc`),缺了它,启动出的 agent 就没有对外通道。本包检入的 `runtime/cordis.yml` 包含 JSON-RPC 服务条目、`agent-core`、预载的 DeepSeek 适配器、JSONL 持久化与本地 bash。DeepSeek 适配器读取 `DEEPSEEK_API_KEY` 与 `DEEPSEEK_BASE_URL`,持久化与 bash 则使用 `DSH_SESSION_ROOT` 和 `DSH_CWD`,并为手动运行提供回退值。调用方未使用任何显式配置通道时,`deepseek_harness` 客户端把该文件路径注入 `DSH_CORDIS_CONFIG`(注入条件见 [sdk README](../sdk/README.md))。因此,零配置是包装层中一次显式、可见的参数传递,而不是运行时中的隐藏回退。
|
||||
运行时二进制始终要求显式配置(`$DSH_CORDIS_CONFIG`,或作为 argv 位置参数的配置路径),缺了就报错退出——这一硬语义是运行时设计的一部分,本包不软化它。`bin`(`dsh-jsonrpc-agent`)只启动配置里列出的插件;对外服务接口(stdio JSON-RPC 服务器)也是其中一个条目(`@deepseek-ai/dsh-jsonrpc`),缺了它,启动出的 agent 就没有对外通道。本包检入的 `runtime/cordis.yml` 包含 JSON-RPC 服务条目、`agent-core`、预载的 DeepSeek 适配器、JSONL 持久化、本地 bash,以及用于有界加载工作区指令的本地文件系统 provider。DeepSeek 适配器读取 `DEEPSEEK_API_KEY` 与 `DEEPSEEK_BASE_URL`,持久化、bash 和文件系统 provider 则使用 `DSH_SESSION_ROOT` 和 `DSH_CWD`,并为手动运行提供回退值。调用方未使用任何显式配置通道时,`deepseek_harness` 客户端把该文件路径注入 `DSH_CORDIS_CONFIG`(注入条件见 [sdk README](../sdk/README.md))。因此,零配置是包装层中一次显式、可见的参数传递,而不是运行时中的隐藏回退。
|
||||
@@ -24,6 +24,7 @@
|
||||
"@deepseek-ai/dsh-fs": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-home": "workspace:^",
|
||||
"@deepseek-ai/dsh-hook-protocol": "workspace:^",
|
||||
"@deepseek-ai/dsh-hooks-claude": "workspace:^",
|
||||
"@deepseek-ai/dsh-hooks-codex": "workspace:^",
|
||||
@@ -31,9 +32,11 @@
|
||||
"@deepseek-ai/dsh-jsonrpc": "workspace:^",
|
||||
"@deepseek-ai/dsh-jsonrpc-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
|
||||
"@deepseek-ai/dsh-permission": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:^",
|
||||
"@deepseek-ai/dsh-sandbox": "workspace:^",
|
||||
"@deepseek-ai/dsh-scope": "workspace:^",
|
||||
@@ -71,6 +74,7 @@
|
||||
"@deepseek-ai/dsh-web-search-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-web-search-exa": "workspace:^",
|
||||
"@deepseek-ai/dsh-web-search-perplexity": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
"@deepseek-ai/dsh-workflow": "workspace:^",
|
||||
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^",
|
||||
"cordis": "workspace:^"
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
# Agent spine; the SDK server creates agents per sessionId.
|
||||
- id: agent-core
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
|
||||
# Stock DeepSeek adapters. Loading requires an API key; initialize and shutdown
|
||||
# may use a dummy key because they do not call the model.
|
||||
@@ -17,9 +20,6 @@
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
baseURL: !!js process.env.DEEPSEEK_BASE_URL
|
||||
models:
|
||||
- deepseek-v4-flash
|
||||
- deepseek-v4-pro
|
||||
|
||||
# JSONL persistence; $DSH_SESSION_ROOT wins over ./.sessions in the process cwd.
|
||||
- id: sessions
|
||||
@@ -32,3 +32,10 @@
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
# Local filesystem provider for workspace instruction loading. This does not
|
||||
# expose model-facing file tools by itself.
|
||||
- id: fs-local
|
||||
name: '@deepseek-ai/dsh-fs-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
@@ -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
|
||||
README.md: 441b335b9e850c221fbd6a657c7de539ceca070d
|
||||
README.zh.md: 65134c0e856b933c793510c2ed528e97f940f47d
|
||||
README.md: 80c9d1f50d26fc4f4670800fd7d7f5ea442ad891
|
||||
README.zh.md: ffedb5eb30f17388fe589863dbc654b22716b40c
|
||||
@@ -25,12 +25,15 @@ By default, the SDK launches the bundled single-file `dsh-jsonrpc-agent` executa
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
with DeepSeekHarness(
|
||||
provider="deepseek",
|
||||
model="deepseek-v4-flash",
|
||||
cordis="examples/dsbench-coding-agent/cordis.yml",
|
||||
) as harness:
|
||||
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. 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.
|
||||
|
||||
`TurnResult.final_response` is the text content from the last
|
||||
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
|
||||
event stream, including intermediate assistant messages and tool activity.
|
||||
|
||||
@@ -21,12 +21,15 @@ with DeepSeekHarness() as harness:
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
with DeepSeekHarness(
|
||||
provider="deepseek",
|
||||
model="deepseek-v4-flash",
|
||||
cordis="examples/dsbench-coding-agent/cordis.yml",
|
||||
) as harness:
|
||||
result = harness.run("Make the requested code change.")
|
||||
```
|
||||
|
||||
`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。内置默认组合注册 `deepseek`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。
|
||||
|
||||
`TurnResult.final_response` 是本轮次最后一个 `assistant/message` 事件的文本内容。完整的事件流(包括中间的助手消息与工具活动)用 `TurnResult.events` 获取。
|
||||
|
||||
同样的行为也可以通过 `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)。
|
||||
|
||||
@@ -18,6 +18,7 @@ class DeepSeekHarnessConfig:
|
||||
intentionally override or inject variables for a subprocess.
|
||||
"""
|
||||
|
||||
provider: str = "deepseek"
|
||||
model: str = "deepseek-v4-flash"
|
||||
cwd: str | None = None
|
||||
runtime_cwd: str | None = None
|
||||
@@ -97,6 +98,7 @@ class DeepSeekHarness:
|
||||
self._client.start()
|
||||
self._client.initialize(
|
||||
cwd=self._cwd,
|
||||
provider=self.config.provider,
|
||||
model=self.config.model,
|
||||
)
|
||||
self._initialized = True
|
||||
|
||||
@@ -115,10 +115,12 @@ class HarnessClient:
|
||||
self,
|
||||
*,
|
||||
cwd: str,
|
||||
provider: str,
|
||||
model: str,
|
||||
) -> InitializeResponse:
|
||||
payload: JsonObject = {
|
||||
"cwd": str(Path(cwd).resolve()),
|
||||
"provider": provider,
|
||||
"model": model,
|
||||
}
|
||||
try:
|
||||
|
||||
@@ -22,6 +22,8 @@ _CORDIS_YML = """\
|
||||
name: '@deepseek-ai/dsh-jsonrpc'
|
||||
- id: agent-core
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
workspaceContext: false
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
@@ -66,7 +68,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(cwd=str(tmp_path), model="deepseek-v4-pro")
|
||||
init = client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
|
||||
|
||||
assert init.serverInfo is not None
|
||||
assert init.serverInfo.name == "deepseek-harness-sdk-runtime"
|
||||
@@ -83,7 +85,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(cwd=str(tmp_path), model="deepseek-v4-pro")
|
||||
client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ for line in sys.stdin:
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
) as client:
|
||||
init = client.initialize(cwd="/workspace", model="dsagent")
|
||||
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
|
||||
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
|
||||
@@ -382,7 +382,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(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
with (
|
||||
client.subscribe_notifications(broken_filter) as broken,
|
||||
client.subscribe_notifications(lambda notification: notification.method == "tick") as healthy,
|
||||
@@ -419,7 +419,7 @@ for line in sys.stdin:
|
||||
)
|
||||
|
||||
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
with pytest.raises(ValueError):
|
||||
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
|
||||
|
||||
@@ -448,7 +448,7 @@ for line in sys.stdin:
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
) as client:
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
|
||||
request = client.next_request()
|
||||
assert request.id == "bridge-req-1"
|
||||
@@ -482,7 +482,7 @@ for line in sys.stdin:
|
||||
with HarnessClient(
|
||||
HarnessConfig(launch_args_override=(sys.executable, str(script)))
|
||||
) as client:
|
||||
init = client.initialize(cwd="/workspace", model="dsagent")
|
||||
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ time.sleep(60)
|
||||
) as client:
|
||||
start = time.monotonic()
|
||||
try:
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
except TimeoutError:
|
||||
assert time.monotonic() - start < 2
|
||||
else:
|
||||
@@ -540,7 +540,7 @@ for line in sys.stdin:
|
||||
client.start()
|
||||
proc = client._proc
|
||||
assert proc is not None
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
start = time.monotonic()
|
||||
client.close()
|
||||
assert time.monotonic() - start < 2
|
||||
@@ -571,7 +571,7 @@ for line in sys.stdin:
|
||||
assert proc is not None
|
||||
|
||||
with pytest.raises(Exception, match="bad initialize"):
|
||||
client.initialize(cwd=".", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd=".", model="dsagent")
|
||||
|
||||
assert proc.wait(timeout=1) is not None
|
||||
assert client._proc is None
|
||||
@@ -611,7 +611,7 @@ for line in sys.stdin:
|
||||
|
||||
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
|
||||
client.start()
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
client.close()
|
||||
client.close()
|
||||
|
||||
@@ -634,7 +634,7 @@ sys.exit(42)
|
||||
)
|
||||
) as client:
|
||||
with pytest.raises(Exception, match="fatal bridge exploded"):
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
|
||||
|
||||
def test_client_serializes_concurrent_writes(tmp_path: Path) -> None:
|
||||
@@ -665,7 +665,7 @@ with open(os.environ["SEEN"], "w") as seen:
|
||||
env={"SEEN": str(output)},
|
||||
)
|
||||
) as client:
|
||||
client.initialize(cwd="/workspace", model="dsagent")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
|
||||
threads = [
|
||||
threading.Thread(target=client.notify, args=(f"notice-{index}", {"index": index}))
|
||||
for index in range(50)
|
||||
@@ -736,7 +736,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(cwd="/workspace", model="deepseek-v4-pro")
|
||||
init = client.initialize(provider="deepseek", 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)
|
||||
@@ -752,7 +752,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(cwd="/workspace", model="deepseek-v4-pro")
|
||||
client.initialize(provider="deepseek", cwd="/workspace", model="deepseek-v4-pro")
|
||||
|
||||
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user