Merge remote-tracking branch 'origin/master' into worktree/provider-routed-llm-adapters

# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	examples/sandbox-acp-agent/cordis.yml
#	examples/sandbox-acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/mode-switching/session.jsonl
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/src/index.ts
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/loop.ts
#	packages/core/agent-loop/tests/properties.spec.ts
#	packages/core/session/README.md
#	packages/core/session/src/types.ts
#	packages/core/session/tests/derived-cache.spec.ts
#	packages/llm/llm-deepseek/src/index.ts
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/src/adapter.ts
#	packages/llm/llm-pi-ai/src/convert.ts
#	packages/llm/llm-pi-ai/tests/adapter.spec.ts
#	packages/llm/llm/README.md
#	packages/llm/llm/src/call-config.ts
#	packages/llm/llm/src/index.ts
#	packages/ui/acp-agent/src/index.ts
#	packages/ui/acp/tests/harness.ts
#	packages/ui/jsonrpc/README.md
#	packages/ui/jsonrpc/src/server.ts
#	packages/ui/stdio-agent/README.md
#	packages/ui/stdio-agent/src/index.ts
#	python/sdk/README.i18n.yaml
This commit is contained in:
Yichen Jiang
2026-07-14 22:17:50 +08:00
672 changed files with 10295 additions and 14200 deletions

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
README.md: b1189a789963a5e4180cc4ee402c885b0ec82dca
README.zh.md: 721221418eb65f323f521a4fa16cbc355d283f0d
README.md: 80c9d1f50d26fc4f4670800fd7d7f5ea442ad891
README.zh.md: ffedb5eb30f17388fe589863dbc654b22716b40c

View File

@@ -38,6 +38,6 @@ with DeepSeekHarness(
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
event stream, including intermediate assistant messages and tool activity.
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` 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.
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.
`cwd` and `runtime_cwd` are resolved to absolute paths before subprocess launch, environment injection, and the wire handshake. The public API exposes only applied options: deployment persona and persistence belong in `cordis.yml`, while `session_root` remains the high-level convenience that sets `DSH_SESSION_ROOT`.

View File

@@ -28,10 +28,10 @@ with DeepSeekHarness(
result = harness.run("Make the requested code change.")
```
`provider` 用于选择当前 Cordis 组合已注册的 provider 路由;`model` 是该适配器解析的模型 ID。内置默认组合注册 `deepseek`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各 provider 的凭据与端点,再选择 pi-ai 已安装目录中的任意 provider/model
`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``launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
同样的行为也可以通过 `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)。
`cwd``runtime_cwd` 会在启动子进程、注入环境变量和协议握手前解析为绝对路径。公开 API 只暴露真正生效的选项:部署的角色设定与持久化配置归 `cordis.yml` 管理,而 `session_root` 继续作为设置 `DSH_SESSION_ROOT` 的高层便捷选项。

View File

@@ -24,8 +24,7 @@ testpaths = ["tests"]
[tool.hatch.build.targets.wheel]
packages = ["src/deepseek_harness"]
# Editable: the runtime package's executables are injected into its source
# tree AFTER install (by scripts/build-exe-for-python-sdk.ts or a manual copy); an
# editable install sees them immediately instead of freezing a wheel snapshot.
# Editable installs see runtime executables injected after installation instead
# of freezing a wheel snapshot.
[tool.uv.sources]
deepseek-harness-runtime-bin = { path = "../sdk-runtime", editable = true }

View File

@@ -418,16 +418,10 @@ class HarnessClient:
return resolve_bundled_launch_args()
def _inject_bundled_default_config(self, env: dict[str, str]) -> None:
"""Restore the zero-config experience over the config-mandatory bundled runtime.
"""Inject the default config for a bundled launch with no non-empty config.
The bundled runtime (single-file exe or the dev-only node closure)
always demands an explicit config. When the launch resolves to the
bundled runtime (no ``runtime_bin`` / ``bridge_bin`` /
``launch_args_override``) and the merged subprocess environment has no
non-empty ``DSH_CORDIS_CONFIG`` — the runtime bin treats an empty
value as absent, so this does too — inject the runtime package's
checked-in default cordis.yml. With an explicit runtime or config
channel the client stays out of the way.
Both bundled carriers require an explicit config. Explicit runtime,
launch-argument, and config channels remain untouched.
"""
uses_bundled_runtime = (
self.config.launch_args_override is None
@@ -436,9 +430,7 @@ class HarnessClient:
)
if not uses_bundled_runtime or env.get("DSH_CORDIS_CONFIG"):
return
# Cannot fail: _default_launch_args() already imported the runtime
# package on this (bundled) path, raising the actionable install
# error when it is absent.
# _default_launch_args already imported the package or raised its install error.
from deepseek_harness_runtime import bundled_default_config_path
env["DSH_CORDIS_CONFIG"] = str(bundled_default_config_path())

View File

@@ -1,12 +1,7 @@
"""Manual keyless smoke: drive the repo-source jsonrpc-agent bin (node + tsx).
"""Drive the repo-source JSON-RPC bin through the SDK and a keyless mock SSE server.
Runs the SDK against `packages/ui/jsonrpc-agent/src/bin.ts` executed from the
repo checkout (requires `pnpm install`; no build, no API key — the model
endpoint is a local mock SSE server). The bin only boots the supplied
cordis.yml — the stdio JSON-RPC server itself comes from the config's
`@deepseek-ai/dsh-jsonrpc` entry — so the runtime package's default cordis.yml
is passed explicitly. Not collected by pytest; run it directly:
`python tests/manual_sdk_agent_smoke.py`.
Requires ``pnpm install`` but no build. This manual test is not collected by
pytest; run ``python tests/manual_sdk_agent_smoke.py``.
"""
from __future__ import annotations

View File

@@ -1,12 +1,7 @@
"""Smoke tests against the bundled dsh-jsonrpc-agent artifacts.
"""Keyless boot tests for the production exe and development node carrier.
These boot the runtime the way an installed SDK does, once per bundled
carrier: the platform single-file exe (production) and the dev-only node
closure under ``runtime/node`` driven by system ``node``. Each carrier skips
independently when its artifact is absent on this machine — build or fetch it
per the FileNotFoundError guidance quoted in the skip reason. Keyless: the
dummy DEEPSEEK_API_KEY only satisfies the adapter's load-time check;
initialize/shutdown never call a model.
Each carrier skips independently when absent. The dummy API key only satisfies
adapter loading; initialize and shutdown do not call a model.
"""
from __future__ import annotations
@@ -21,8 +16,7 @@ from deepseek_harness_runtime import resolve_bundled_launch_args
_MODES = ("exe", "node")
# The serving surface is itself a plugin: without the dsh-jsonrpc entry the
# runtime boots an agent nobody can talk to and exits 0 on stdin EOF.
# The config must include the JSON-RPC serving plugin.
_CORDIS_YML = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
@@ -57,9 +51,7 @@ def _client(tmp_path: Path, launch_args: tuple[str, ...]) -> HarnessClient:
"DSH_CORDIS_CONFIG": "./cordis.yml",
"DSH_SESSION_ROOT": str(tmp_path / "sessions"),
"DSH_CWD": str(tmp_path),
# initialize() lazily mounts the llm-deepseek adapter for the
# requested model; a dummy key keeps the keyless boot green
# (initialize/shutdown never call the model).
# The lazily mounted adapter requires a key even without a model call.
"DEEPSEEK_API_KEY": "sk-dummy-for-boot",
"DEEPSEEK_BASE_URL": "http://127.0.0.1:9",
},
@@ -119,7 +111,4 @@ def test_zero_config_run_injects_bundled_default_cordis_config(
request_timeout_seconds=120,
)
with harness:
# __enter__ boots the runtime, which exits with a usage error unless
# HarnessClient.start() injected the bundled default config over the
# unset/empty DSH_CORDIS_CONFIG; __exit__ shuts it down.
pass

View File

@@ -682,11 +682,9 @@ with open(os.environ["SEEN"], "w") as seen:
def _install_fake_bundled_runtime(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> Path:
"""Fake the deepseek-harness-runtime-bin package on sys.path.
"""Install a fake runtime package that records config and serves lifecycle calls.
A stub exe that dumps DSH_CORDIS_CONFIG to $ENV_DUMP before serving
initialize/shutdown, plus a module exposing the resolution surface the
client consumes. Returns the fake bundled default config path.
Returns the fake bundled default config path.
"""
runtime = tmp_path / "dsh-jsonrpc-agent"
runtime.write_text(

View File

@@ -1,9 +1,4 @@
"""Keyless tests for the deepseek_harness_runtime resolution API.
These never launch a runtime, so they run everywhere regardless of which
bundled artifacts are present; the launch-and-boot coverage lives in
``test_bundled_runtime.py``.
"""
"""Keyless runtime-resolution tests; launch coverage lives in test_bundled_runtime.py."""
from __future__ import annotations