review: client-owned default-config injection; tar the bare exe artifact
Address the three ds-review-bot warnings on #253: - An empty DSH_CORDIS_CONFIG now counts as absent when deciding whether to inject the bundled default config, matching the runtime bin's config-discovery semantics. - The injection moves from DeepSeekHarness into HarnessClient.start(), so the low-level client's default bundled launch also boots without callers duplicating the env setup. - The bare single-file exe artifact ships inside a tar.gz like the Python bundle: upload-artifact's zip transport drops the executable bit.
This commit is contained in:
34
.github/workflows/build-exe-for-python-sdk.yml
vendored
34
.github/workflows/build-exe-for-python-sdk.yml
vendored
@@ -8,14 +8,16 @@ name: Build single-exe
|
|||||||
# the dsh-jsonrpc-agent-pkg closure manifest with @yao-pkg/pkg into
|
# the dsh-jsonrpc-agent-pkg closure manifest with @yao-pkg/pkg into
|
||||||
# dist-exe/.
|
# dist-exe/.
|
||||||
#
|
#
|
||||||
# Each build leg uploads two artifacts:
|
# Each build leg uploads two artifacts (both tar.gz — tar keeps file modes,
|
||||||
|
# so the exe's executable bit survives; upload-artifact's zip transport
|
||||||
|
# does not preserve them):
|
||||||
# - dsh-jsonrpc-agent-pkg-<target> — the bare single-file exe, for
|
# - dsh-jsonrpc-agent-pkg-<target> — the bare single-file exe, for
|
||||||
# consumers that want just the binary.
|
# consumers that want just the binary.
|
||||||
# - deepseek-harness-python-<target> — the whole python/ directory as a
|
# - deepseek-harness-python-<target> — the whole python/ directory with
|
||||||
# tar.gz with that exe already embedded (the build script syncs it into
|
# that exe already embedded (the build script syncs it into the Python
|
||||||
# the Python runtime package): unpack and both packages pip install
|
# runtime package): unpack and both packages pip install as-is, the
|
||||||
# as-is, the checked-in default runtime/cordis.yml is editable in
|
# checked-in default runtime/cordis.yml is editable in place, and the
|
||||||
# place, and the embedded exe also runs directly.
|
# embedded exe also runs directly.
|
||||||
#
|
#
|
||||||
# Two explicit triggers, deliberately no per-commit CI: the exe is a
|
# Two explicit triggers, deliberately no per-commit CI: the exe is a
|
||||||
# release-style deliverable, and the build (full pnpm build + pnpm deploy +
|
# release-style deliverable, and the build (full pnpm build + pnpm deploy +
|
||||||
@@ -154,10 +156,24 @@ jobs:
|
|||||||
- name: Build single-exe
|
- name: Build single-exe
|
||||||
run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
|
run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
|
||||||
|
|
||||||
|
# The bare exe ships inside a tar.gz (the mode-preservation note in
|
||||||
|
# the header): uploading dist-exe/ bare would hand consumers a 0644
|
||||||
|
# file that subprocess.Popen refuses to run.
|
||||||
|
- name: Pack bare exe
|
||||||
|
id: pack-exe
|
||||||
|
env:
|
||||||
|
TARGET: ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
exe_tar="dsh-jsonrpc-agent-pkg-${TARGET}.tar.gz"
|
||||||
|
tar -czf "$exe_tar" -C dist-exe .
|
||||||
|
ls -lh "$exe_tar"
|
||||||
|
echo "tar=$exe_tar" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v6
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: dsh-jsonrpc-agent-pkg-${{ matrix.target }}
|
name: dsh-jsonrpc-agent-pkg-${{ matrix.target }}
|
||||||
path: dist-exe/
|
path: ${{ steps.pack-exe.outputs.tar }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# After the build step, python/ is already in its complete
|
# After the build step, python/ is already in its complete
|
||||||
@@ -165,9 +181,7 @@ jobs:
|
|||||||
# python/sdk-runtime/src/deepseek_harness_runtime/runtime/ next to the
|
# python/sdk-runtime/src/deepseek_harness_runtime/runtime/ next to the
|
||||||
# checked-in default cordis.yml — so packing is all that is left.
|
# checked-in default cordis.yml — so packing is all that is left.
|
||||||
# Everything goes under one top-level deepseek-harness-python/
|
# Everything goes under one top-level deepseek-harness-python/
|
||||||
# directory so unpacking never scatters files. Shipping a tar (rather
|
# directory so unpacking never scatters files.
|
||||||
# than uploading the tree bare) preserves the exe's executable bit —
|
|
||||||
# tar keeps file modes; upload-artifact's zip does not.
|
|
||||||
#
|
#
|
||||||
# Excluded: runtime/node/ (dev-only node-mode carrier, ~140MB) plus
|
# Excluded: runtime/node/ (dev-only node-mode carrier, ~140MB) plus
|
||||||
# __pycache__ / .pytest_cache / .venv / node_modules anywhere (install
|
# __pycache__ / .pytest_cache / .venv / node_modules anywhere (install
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write
|
# pnpm run verify-translation-pairing --write
|
||||||
README.md: 5543b0ba00816c230dbe4e5bba11b7bf4b38096d
|
README.md: 4cf48c55f40ec1c8632582546dcbfb990bbaa660
|
||||||
README.zh.md: 163b6d8e8373f0f770a5d95be688785907108b8a
|
README.zh.md: 0c82569be45b5cefa2ca025728945be1bdefc541
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Two carriers coexist under `src/deepseek_harness_runtime/runtime/`, both injecte
|
|||||||
|
|
||||||
Both carriers hold the same content, defined once: the [package.json](package.json) at this package's root is the deploy root of the single-exe pipeline — a pure dependency manifest (no code of its own) whose dependency closure IS both the plugin set compiled into the exe and the tree materialized into `runtime/node/`. Adding a plugin to the distribution means adding one dependency line there and rebuilding.
|
Both carriers hold the same content, defined once: the [package.json](package.json) at this package's root is the deploy root of the single-exe pipeline — a pure dependency manifest (no code of its own) whose dependency closure IS both the plugin set compiled into the exe and the tree materialized into `runtime/node/`. Adding a plugin to the distribution means adding one dependency line there and rebuilding.
|
||||||
|
|
||||||
Missing carriers raise `FileNotFoundError` naming the acquisition routes: build via `scripts/build-exe-for-python-sdk.ts` in a deepseek-harness checkout, or download the platform artifact of the `build-exe-for-python-sdk` CI workflow and place it in the runtime directory. Acquisition strategy is deliberately separate from the lookup interface, so an on-demand download can replace it later without touching callers.
|
Missing carriers raise `FileNotFoundError` naming the acquisition routes: build via `scripts/build-exe-for-python-sdk.ts` in a deepseek-harness checkout, or download the platform artifact of the `build-exe-for-python-sdk` CI workflow (a tar.gz — tar preserves the executable bit) and unpack it into the runtime directory. Acquisition strategy is deliberately separate from the lookup interface, so an on-demand download can replace it later without touching callers.
|
||||||
|
|
||||||
## Resolution API
|
## Resolution API
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Python SDK 的运行时载体包(dist 名 `deepseek-harness-runtime-bin`,模
|
|||||||
|
|
||||||
两种载体承载相同的内容,且只定义一次:本包根目录的 [package.json](package.json) 是 single-exe 流水线的 deploy root——一份零代码的纯依赖 manifest,其依赖闭包既是编译进 exe 的插件集,也是物化到 `runtime/node/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
|
两种载体承载相同的内容,且只定义一次:本包根目录的 [package.json](package.json) 是 single-exe 流水线的 deploy root——一份零代码的纯依赖 manifest,其依赖闭包既是编译进 exe 的插件集,也是物化到 `runtime/node/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
|
||||||
|
|
||||||
载体缺失时抛出 `FileNotFoundError` 并写明获取途径:在 deepseek-harness 检出中经 `scripts/build-exe-for-python-sdk.ts` 构建,或下载 `build-exe-for-python-sdk` CI 工作流的对应平台产物并放入 runtime 目录。获取策略与查找接口刻意分离,之后可以换成按需下载而不动任何调用方。
|
载体缺失时抛出 `FileNotFoundError` 并写明获取途径:在 deepseek-harness 检出中经 `scripts/build-exe-for-python-sdk.ts` 构建,或下载 `build-exe-for-python-sdk` CI 工作流的对应平台产物(tar.gz——tar 保留可执行位)并解包到 runtime 目录。获取策略与查找接口刻意分离,之后可以换成按需下载而不动任何调用方。
|
||||||
|
|
||||||
## 解析 API
|
## 解析 API
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ _ARCH_TAGS = {"x86_64": "x64", "amd64": "x64", "arm64": "arm64", "aarch64": "arm
|
|||||||
_EXE_ACQUISITION_HINT = (
|
_EXE_ACQUISITION_HINT = (
|
||||||
"Two ways to get the executable: run `scripts/build-exe-for-python-sdk.ts` (via tsx) in a "
|
"Two ways to get the executable: run `scripts/build-exe-for-python-sdk.ts` (via tsx) in a "
|
||||||
"deepseek-harness checkout, or download the platform artifact of the `build-exe-for-python-sdk` "
|
"deepseek-harness checkout, or download the platform artifact of the `build-exe-for-python-sdk` "
|
||||||
"CI workflow; place the file in this package's runtime/ directory. For local development "
|
"CI workflow (a tar.gz — tar preserves the executable bit) and unpack it into this package's "
|
||||||
|
"runtime/ directory. For local development "
|
||||||
"against a repo source build, explicitly select the dev-only node carrier with "
|
"against a repo source build, explicitly select the dev-only node carrier with "
|
||||||
f"{RUNTIME_MODE_ENV_VAR}=node (or resolve_bundled_launch_args('node'))."
|
f"{RUNTIME_MODE_ENV_VAR}=node (or resolve_bundled_launch_args('node'))."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write
|
# pnpm run verify-translation-pairing --write
|
||||||
README.md: 67dbd04520ac01318c1f21393c12b7bf4965b4cb
|
README.md: ef9f8703f3bdb7b6d2dca28639f3b794083f7905
|
||||||
README.zh.md: 36ea4df4ff73e12ce3fda5bbee4108584dd5ad92
|
README.zh.md: 31b6e87d7b75cc10cb799862fe6eb0f1f26dccd6
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ with DeepSeekHarness(
|
|||||||
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
|
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
|
||||||
event stream, including intermediate assistant messages and tool activity.
|
event stream, including intermediate assistant messages and tool activity.
|
||||||
|
|
||||||
The same behavior can be selected for the runtime subprocess with `DSH_CORDIS_CONFIG`. If neither `cordis` nor `DSH_CORDIS_CONFIG` is set and the launch resolves to the bundled runtime, 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` 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.
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ with DeepSeekHarness(
|
|||||||
|
|
||||||
`TurnResult.final_response` 是本轮次最后一个 `assistant/message` 事件的文本内容。完整的事件流(包括中间的助手消息与工具活动)用 `TurnResult.events` 获取。
|
`TurnResult.final_response` 是本轮次最后一个 `assistant/message` 事件的文本内容。完整的事件流(包括中间的助手消息与工具活动)用 `TurnResult.events` 获取。
|
||||||
|
|
||||||
同样的行为也可以用 `DSH_CORDIS_CONFIG` 为运行时子进程选定。若 `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` 或 `launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 node 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -60,8 +59,6 @@ class DeepSeekHarness:
|
|||||||
env["DSH_SESSION_ROOT"] = self.config.session_root
|
env["DSH_SESSION_ROOT"] = self.config.session_root
|
||||||
if self.config.cordis is not None:
|
if self.config.cordis is not None:
|
||||||
env["DSH_CORDIS_CONFIG"] = self.config.cordis
|
env["DSH_CORDIS_CONFIG"] = self.config.cordis
|
||||||
else:
|
|
||||||
self._inject_bundled_default_config(env)
|
|
||||||
env["DSH_CWD"] = cwd
|
env["DSH_CWD"] = cwd
|
||||||
if self.config.base_url is not None:
|
if self.config.base_url is not None:
|
||||||
env["DEEPSEEK_BASE_URL"] = self.config.base_url
|
env["DEEPSEEK_BASE_URL"] = self.config.base_url
|
||||||
@@ -109,27 +106,6 @@ class DeepSeekHarness:
|
|||||||
self._client.close()
|
self._client.close()
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
|
|
||||||
def _inject_bundled_default_config(self, env: dict[str, str]) -> None:
|
|
||||||
"""Restore the zero-config experience over the config-mandatory bundled runtime.
|
|
||||||
|
|
||||||
The bundled runtime (single-file exe or the dev-only node closure)
|
|
||||||
always demands an explicit config. When the caller neither provided
|
|
||||||
``cordis`` nor selected a runtime explicitly (``runtime_bin`` /
|
|
||||||
``launch_args_override``), and no ambient ``DSH_CORDIS_CONFIG`` exists,
|
|
||||||
inject the runtime package's checked-in default cordis.yml. With an
|
|
||||||
explicit runtime or config channel the SDK stays out of the way.
|
|
||||||
"""
|
|
||||||
uses_bundled_runtime = self.config.runtime_bin is None and self.config.launch_args_override is None
|
|
||||||
if not uses_bundled_runtime or "DSH_CORDIS_CONFIG" in env or "DSH_CORDIS_CONFIG" in os.environ:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
from deepseek_harness_runtime import bundled_default_config_path
|
|
||||||
except ImportError:
|
|
||||||
# Only the runtime package's absence reaches here; swallow it so
|
|
||||||
# HarnessClient.start() reports the actionable install error.
|
|
||||||
return
|
|
||||||
env["DSH_CORDIS_CONFIG"] = str(bundled_default_config_path())
|
|
||||||
|
|
||||||
def start_session(self, session_id: str | None = None) -> "Session":
|
def start_session(self, session_id: str | None = None) -> "Session":
|
||||||
self.start()
|
self.start()
|
||||||
return Session(self, session_id or f"session-{uuid.uuid4().hex}")
|
return Session(self, session_id or f"session-{uuid.uuid4().hex}")
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class HarnessClient:
|
|||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if self.config.env:
|
if self.config.env:
|
||||||
env.update(self.config.env)
|
env.update(self.config.env)
|
||||||
|
self._inject_bundled_default_config(env)
|
||||||
self._proc = subprocess.Popen(
|
self._proc = subprocess.Popen(
|
||||||
args,
|
args,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
@@ -411,6 +412,32 @@ class HarnessClient:
|
|||||||
) from exc
|
) from exc
|
||||||
return resolve_bundled_launch_args()
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
uses_bundled_runtime = (
|
||||||
|
self.config.launch_args_override is None
|
||||||
|
and self.config.runtime_bin is None
|
||||||
|
and self.config.bridge_bin is None
|
||||||
|
)
|
||||||
|
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.
|
||||||
|
from deepseek_harness_runtime import bundled_default_config_path
|
||||||
|
|
||||||
|
env["DSH_CORDIS_CONFIG"] = str(bundled_default_config_path())
|
||||||
|
|
||||||
def _unsubscribe_notifications(self, subscription_id: str) -> None:
|
def _unsubscribe_notifications(self, subscription_id: str) -> None:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._notification_subscribers.pop(subscription_id, None)
|
self._notification_subscribers.pop(subscription_id, None)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import pytest
|
|||||||
|
|
||||||
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
|
from deepseek_harness import DeepSeekHarness, HarnessClient, HarnessConfig
|
||||||
from deepseek_harness.errors import TransportClosedError
|
from deepseek_harness.errors import TransportClosedError
|
||||||
from deepseek_harness_runtime import bundled_default_config_path, resolve_bundled_launch_args
|
from deepseek_harness_runtime import resolve_bundled_launch_args
|
||||||
|
|
||||||
_MODES = ("exe", "node")
|
_MODES = ("exe", "node")
|
||||||
|
|
||||||
@@ -99,12 +99,16 @@ def test_bundled_runtime_surfaces_unbundled_plugin_failure(tmp_path: Path, mode:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", _MODES)
|
@pytest.mark.parametrize("mode", _MODES)
|
||||||
|
@pytest.mark.parametrize("ambient_config", [None, ""], ids=["unset", "empty-counts-as-absent"])
|
||||||
def test_zero_config_run_injects_bundled_default_cordis_config(
|
def test_zero_config_run_injects_bundled_default_cordis_config(
|
||||||
tmp_path: Path, mode: str, monkeypatch: pytest.MonkeyPatch
|
tmp_path: Path, mode: str, ambient_config: str | None, monkeypatch: pytest.MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
_launch_args(mode) # skip early when this carrier is unavailable
|
_launch_args(mode) # skip early when this carrier is unavailable
|
||||||
monkeypatch.setenv("DSH_RUNTIME_MODE", mode)
|
monkeypatch.setenv("DSH_RUNTIME_MODE", mode)
|
||||||
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
|
if ambient_config is None:
|
||||||
|
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
|
||||||
|
else:
|
||||||
|
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
|
||||||
|
|
||||||
harness = DeepSeekHarness(
|
harness = DeepSeekHarness(
|
||||||
model="deepseek-v4-pro",
|
model="deepseek-v4-pro",
|
||||||
@@ -114,7 +118,8 @@ def test_zero_config_run_injects_bundled_default_cordis_config(
|
|||||||
base_url="http://127.0.0.1:9",
|
base_url="http://127.0.0.1:9",
|
||||||
request_timeout_seconds=120,
|
request_timeout_seconds=120,
|
||||||
)
|
)
|
||||||
assert harness.client.config.env is not None
|
|
||||||
assert harness.client.config.env["DSH_CORDIS_CONFIG"] == str(bundled_default_config_path())
|
|
||||||
with harness:
|
with harness:
|
||||||
pass # __enter__ boots the runtime on the injected default config; __exit__ shuts it down
|
# __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
|
||||||
|
|||||||
@@ -548,13 +548,23 @@ with open(os.environ["SEEN"], "w") as seen:
|
|||||||
json.loads(line)
|
json.loads(line)
|
||||||
|
|
||||||
|
|
||||||
def test_client_uses_bundled_runtime_package_by_default(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
def _install_fake_bundled_runtime(
|
||||||
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> Path:
|
||||||
|
"""Fake the deepseek-harness-runtime-bin package on sys.path.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
runtime = tmp_path / "dsh-jsonrpc-agent"
|
runtime = tmp_path / "dsh-jsonrpc-agent"
|
||||||
runtime.write_text(
|
runtime.write_text(
|
||||||
"""#!/usr/bin/env python3
|
"""#!/usr/bin/env python3
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
json.dump({"DSH_CORDIS_CONFIG": os.environ.get("DSH_CORDIS_CONFIG")}, open(os.environ["ENV_DUMP"], "w"))
|
||||||
for line in sys.stdin:
|
for line in sys.stdin:
|
||||||
msg = json.loads(line)
|
msg = json.loads(line)
|
||||||
if msg.get("method") == "initialize":
|
if msg.get("method") == "initialize":
|
||||||
@@ -566,22 +576,56 @@ for line in sys.stdin:
|
|||||||
)
|
)
|
||||||
runtime.chmod(0o755)
|
runtime.chmod(0o755)
|
||||||
|
|
||||||
|
default_config = tmp_path / "default-cordis.yml"
|
||||||
module_dir = tmp_path / "deepseek_harness_runtime"
|
module_dir = tmp_path / "deepseek_harness_runtime"
|
||||||
module_dir.mkdir()
|
module_dir.mkdir()
|
||||||
(module_dir / "__init__.py").write_text(
|
(module_dir / "__init__.py").write_text(
|
||||||
f"""
|
f"""
|
||||||
def resolve_bundled_launch_args(mode=None):
|
def resolve_bundled_launch_args(mode=None):
|
||||||
return ({str(runtime)!r},)
|
return ({str(runtime)!r},)
|
||||||
|
|
||||||
|
|
||||||
|
def bundled_default_config_path():
|
||||||
|
return {str(default_config)!r}
|
||||||
""".strip()
|
""".strip()
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.syspath_prepend(str(tmp_path))
|
monkeypatch.syspath_prepend(str(tmp_path))
|
||||||
monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
|
monkeypatch.delitem(sys.modules, "deepseek_harness_runtime", raising=False)
|
||||||
|
return default_config
|
||||||
|
|
||||||
with HarnessClient() as client:
|
|
||||||
|
@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(cwd="/workspace", model="deepseek-v4-pro")
|
init = client.initialize(cwd="/workspace", model="deepseek-v4-pro")
|
||||||
|
|
||||||
assert init.serverInfo.name == "bundled-runtime"
|
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(
|
||||||
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
env_dump = tmp_path / "env.json"
|
||||||
|
_install_fake_bundled_runtime(tmp_path, monkeypatch)
|
||||||
|
monkeypatch.delenv("DSH_CORDIS_CONFIG", raising=False)
|
||||||
|
|
||||||
|
with HarnessClient(
|
||||||
|
HarnessConfig(env={"ENV_DUMP": str(env_dump), "DSH_CORDIS_CONFIG": "./explicit.yml"})
|
||||||
|
) as client:
|
||||||
|
client.initialize(cwd="/workspace", model="deepseek-v4-pro")
|
||||||
|
|
||||||
|
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"
|
||||||
|
|
||||||
|
|
||||||
def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_client_reports_missing_bundled_runtime_dependency(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user