Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts: # docs/config-catalog.i18n.yaml # docs/config-catalog.md # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md # packages/client/ui-chat/src/client/contract/slots.ts # packages/shell/tool-pwsh/package.json # packages/workflow/tool-workflow/src/index.ts # python/sdk-runtime/package.json
This commit is contained in:
commit
5cd435e917
2067 files changed
+32266
-13034
No files matched your search
@@ -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/development.md
|
||||
development.md: 61094a277d2b91063a0d368ec30f444eeb132128
|
||||
development.zh.md: a73de6e091050cebb0b26037a7cca3adc814d961
|
||||
development.md: f0d448cf4c4ce21895b3f8b0cf43db7ab052caea
|
||||
development.zh.md: 74e2a6a83ca5ff5ac5b820ed6fdc8d72c0a48798
|
||||
@@ -13,7 +13,7 @@ pnpm install
|
||||
pnpm exec tsx scripts/build-exe-for-python-sdk.ts
|
||||
```
|
||||
|
||||
Use `--skip-build` when the required `lib/` artifacts already exist, or `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64` to select platforms. Products land in `dist-exe/` and the script syncs the selected carriers into `python/sdk-runtime/`. macOS builds also sync the matching spawn helper required by `node-pty`.
|
||||
Use `--skip-build` when the required `lib/` artifacts already exist, or `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64` to select platforms. Build each target on its native architecture. Products land in `dist-exe/` and the script syncs the selected carriers into `python/sdk-runtime/`. Windows emits `.exe` and `-rg.exe`; macOS also syncs the matching spawn helper required by `node-pty`.
|
||||
|
||||
## Validate the SDK
|
||||
|
||||
@@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
|
||||
```
|
||||
|
||||
Three scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the shipped `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages, so a plugin that contributes an unintended system section or user message fails the job. `advanced/` pins one complex process's SDK result and parent/child session logs. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
|
||||
Three scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
|
||||
|
||||
Trusted pull requests also run `--scenario sdk-live --installed-wheel` on every native target. That scenario performs two tool-using turns against `https://api.deepseek.com`, verifies the created file externally, and fails when the repository secret is absent instead of self-skipping. Fork and Dependabot pull requests run the complete keyless installed-wheel path but receive no key.
|
||||
|
||||
@@ -79,11 +79,11 @@ pip install \
|
||||
"dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
|
||||
```
|
||||
|
||||
The runtime distribution is wheel-only. The release pipeline publishes three platform wheels with the pure SDK wheel: Linux x64, Linux arm64, and macOS 14 or newer on arm64. A `python-v<repository-version>` tag is accepted only when it matches the repository version; prerelease repository versions such as `0.0.1-rc.1` use their normalized PEP 440 spelling, such as `0.0.1rc1`, inside wheel filenames and metadata.
|
||||
The runtime distribution is wheel-only. The release pipeline publishes four platform wheels with the pure SDK wheel: Linux x64, Linux arm64, macOS 14 or newer on arm64, and Windows x64 (`win_amd64`). A `python-v<repository-version>` tag is accepted only when it matches the repository version; prerelease repository versions such as `0.0.1-rc.1` use their normalized PEP 440 spelling, such as `0.0.1rc1`, inside wheel filenames and metadata.
|
||||
|
||||
## Validate a release candidate
|
||||
|
||||
Manually run the GitHub `Release (Python)` workflow with `publish=false` to build all four wheels, install the Linux release set on Python 3.10 and 3.14, check exact filenames and metadata, enforce PyPI's default per-file size limit, and retain one aggregate artifact with SHA-256 hashes. The run has no registry credentials; a dry run cannot enter either publication job.
|
||||
Manually run the GitHub `Release (Python)` workflow with `publish=false` to build all five wheels, install the Linux release set on Python 3.10 and 3.14, check exact filenames and metadata, enforce PyPI's default per-file size limit, and retain one aggregate artifact with SHA-256 hashes. The run has no registry credentials; a dry run cannot enter either publication job.
|
||||
|
||||
Public publication runs from the private automation repository; package metadata points to the separate read-only public source mirror, which does not run release Actions. The private repository defines the repository variable `PYPI_PUBLISHER_REPOSITORY` as its own `owner/name` and keeps `PUBLIC_PYPI_RELEASE_ENABLED=false` except during an intentional release.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ pnpm install
|
||||
pnpm exec tsx scripts/build-exe-for-python-sdk.ts
|
||||
```
|
||||
|
||||
所需 `lib/` 产物已存在时使用 `--skip-build`;如需选择平台,请使用 `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64`。产物写入 `dist-exe/`,脚本会将所选载体同步到 `python/sdk-runtime/`。macOS 构建还会同步 `node-pty` 所需的配套 spawn 辅助程序。
|
||||
所需 `lib/` 产物已存在时使用 `--skip-build`;如需选择平台,请使用 `--targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64`。每个目标都应在其原生架构上构建。产物写入 `dist-exe/`,脚本会将所选载体同步到 `python/sdk-runtime/`。Windows 会生成 `.exe` 与 `-rg.exe`;macOS 构建还会同步 `node-pty` 所需的配套 spawn 辅助程序。
|
||||
|
||||
## 验证 SDK
|
||||
|
||||
@@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
|
||||
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
|
||||
```
|
||||
|
||||
其中三个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定随附 `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息,因此插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败。`advanced/` 固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并固定其彼此隔离的模型历史、高层结果与独立持久日志。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
|
||||
其中三个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
|
||||
|
||||
可信拉取请求还会在每个原生目标上运行 `--scenario sdk-live --installed-wheel`。该场景面向 `https://api.deepseek.com` 执行两个使用工具的轮次,从外部验证已创建文件,并在仓库密钥缺失时失败而不是自行 skip。Fork 与 Dependabot 拉取请求会运行完整的 keyless 安装后 wheel 路径,但不会获得密钥。
|
||||
|
||||
@@ -79,11 +79,11 @@ pip install \
|
||||
"dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
|
||||
```
|
||||
|
||||
运行时分发包仅提供 wheel 包。发布流水线会连同纯 SDK wheel 包一起发布三个平台 wheel 包:Linux x64、Linux arm64 和 macOS 14 或更高版本的 arm64。只有与仓库版本匹配时,才接受 `python-v<repository-version>` 标签;`0.0.1-rc.1` 之类的仓库预发布版本在 wheel 包文件名和元数据中使用规范化的 PEP 440 写法,例如 `0.0.1rc1`。
|
||||
运行时分发包仅提供 wheel 包。发布流水线会连同纯 SDK wheel 包一起发布四个平台 wheel 包:Linux x64、Linux arm64、macOS 14 或更高版本的 arm64,以及 Windows x64(`win_amd64`)。只有与仓库版本匹配时,才接受 `python-v<repository-version>` 标签;`0.0.1-rc.1` 之类的仓库预发布版本在 wheel 包文件名和元数据中使用规范化的 PEP 440 写法,例如 `0.0.1rc1`。
|
||||
|
||||
## 验证候选发行版
|
||||
|
||||
手动运行 GitHub 的 `Release (Python)` 工作流并设置 `publish=false`,即可构建全部四个 wheel 包,在 Python 3.10 和 3.14 上安装 Linux 发行集合,检查精确文件名和元数据,执行 PyPI 默认单文件大小限制,并保留一份带 SHA-256 哈希的汇总产物。该运行没有注册表凭据,dry-run 运行无法进入任何发布作业。
|
||||
手动运行 GitHub 的 `Release (Python)` 工作流并设置 `publish=false`,即可构建全部五个 wheel 包,在 Python 3.10 和 3.14 上安装 Linux 发行集合,检查精确文件名和元数据,执行 PyPI 默认单文件大小限制,并保留一份带 SHA-256 哈希的汇总产物。该运行没有注册表凭据,dry-run 运行无法进入任何发布作业。
|
||||
|
||||
公开发布从私有自动化仓库运行;包元数据指向独立的只读公开源码镜像,该镜像不运行发布 Actions。私有仓库把仓库变量 `PYPI_PUBLISHER_REPOSITORY` 定义为自身的 `owner/name`,并且只在有意发布期间把 `PUBLIC_PYPI_RELEASE_ENABLED` 从 `false` 改为 `true`。
|
||||
|
||||
|
||||
@@ -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-runtime/README.md
|
||||
README.md: 1552f2a120938ecab6d05dd244a745bec65bb00a
|
||||
README.zh.md: 9524617ee1a950080476a91db5ec6e14727518ce
|
||||
README.md: 28695259928a7edc6e6cf67e737f1012729df5a4
|
||||
README.zh.md: f23b253cfe47d9f1ae24568b51d9db810c7a4a9f
|
||||
@@ -8,7 +8,7 @@ Platform runtime wheel for the DeepSeek Harness Python SDK. It packages the norm
|
||||
|
||||
The wheel installs a `dsh` console command and the `deepseek_harness_runtime` Python module. `dsh` forwards its arguments to the bundled executable and requires a non-empty `DSH_HOME`; it never falls back to `~/.dsh`.
|
||||
|
||||
Production executables are named `deepseek-harness-sdk-runtime-<platform>-<arch>` under the module's `runtime/` directory. Linux and macOS wheels include a target-native `-rg` sidecar; macOS also includes `-spawn-helper` for `node-pty`. Published targets are Linux x64, Linux arm64, and macOS arm64. The wheel tag and payload must match exactly.
|
||||
Production executables are named `deepseek-harness-sdk-runtime-<platform>-<arch>` under the module's `runtime/` directory; Windows uses the `.exe` suffix. Linux and macOS wheels include a target-native `-rg` sidecar, Windows includes `-rg.exe`, and macOS also includes `-spawn-helper` for `node-pty`. Published targets are Linux x64, Linux arm64, macOS arm64, and Windows x64. The wheel tag and payload must match exactly; no Windows arm64 wheel is published.
|
||||
|
||||
Repository builds also materialize a dev-only `runtime/node/` carrier. It runs `node runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js` on system Node 22.19 or newer. It is never selected automatically and is excluded from wheels and sdists.
|
||||
|
||||
@@ -25,7 +25,7 @@ Unsupported platforms and missing executables or sidecars raise `FileNotFoundErr
|
||||
|
||||
## Packaged profile resolution
|
||||
|
||||
`dsh` initializes shipped profiles under the explicit home, composes their bundle patches, and loads bundled plugins from the executable's virtual filesystem. Because operating-system symlinks cannot enter that filesystem, packaged launches maintain small real ESM proxy packages under `$DSH_HOME/profiles/node_modules`. Each proxy mirrors explicit runtime exports, records the original package identity, and re-exports the virtual module URL. Built-in rows and external plugin peers therefore share one Cordis/module instance. Native shared libraries are packaged with native addons, while ripgrep and the macOS PTY helper remain executable sidecars.
|
||||
`dsh` initializes shipped profiles under the explicit home, composes their bundle patches, and loads bundled plugins from the executable's virtual filesystem. Because operating-system symlinks cannot enter that filesystem, packaged launches maintain small real ESM proxy packages under `$DSH_HOME/profiles/node_modules`. Each proxy mirrors explicit runtime exports, records the original package identity, and re-exports the virtual module URL. Built-in rows and external plugin peers therefore share one Cordis/module instance. Native shared libraries and Windows ConPTY addons are packaged with native addons, while ripgrep and the macOS PTY helper remain executable sidecars.
|
||||
|
||||
External profile management uses `dsh plugin --profile <name> ...`. That command requires `pnpm` on `PATH`; ordinary SDK/profile execution does not.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ DeepSeek Harness Python SDK 的平台运行时 wheel。它把普通 `dsh` CLI
|
||||
|
||||
Wheel 会安装 `dsh` 控制台命令和 `deepseek_harness_runtime` Python 模块。`dsh` 将参数转发给内置可执行程序,并要求非空 `DSH_HOME`;它不会回退到 `~/.dsh`。
|
||||
|
||||
生产可执行程序位于模块的 `runtime/` 目录,命名为 `deepseek-harness-sdk-runtime-<platform>-<arch>`。Linux 与 macOS wheel 包含目标平台原生的 `-rg` 伴随程序;macOS 还包含 `node-pty` 使用的 `-spawn-helper`。已发布目标是 Linux x64、Linux arm64 与 macOS arm64。Wheel tag 必须与载荷严格匹配。
|
||||
生产可执行程序位于模块的 `runtime/` 目录,命名为 `deepseek-harness-sdk-runtime-<platform>-<arch>`;Windows 使用 `.exe` 后缀。Linux 与 macOS wheel 包含目标平台原生的 `-rg` 伴随程序,Windows 包含 `-rg.exe`,macOS 还包含 `node-pty` 使用的 `-spawn-helper`。已发布目标是 Linux x64、Linux arm64、macOS arm64 与 Windows x64。Wheel tag 必须与载荷严格匹配;不发布 Windows arm64 wheel。
|
||||
|
||||
仓库构建还会物化仅限开发的 `runtime/node/` 载体。它在系统 Node 22.19 或更高版本上运行 `node runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`。系统不会自动选择它,而且 wheel 与 sdist 均不包含它。
|
||||
|
||||
@@ -25,7 +25,7 @@ Wheel 会安装 `dsh` 控制台命令和 `deepseek_harness_runtime` Python 模
|
||||
|
||||
## 打包后的 profile 解析
|
||||
|
||||
`dsh` 在显式 home 下初始化随附 profile、组合其 bundle patch,并从可执行程序的虚拟文件系统加载内置插件。操作系统符号链接无法进入该文件系统,因此打包运行会在 `$DSH_HOME/profiles/node_modules` 下维护小型真实 ESM 代理包。每个代理镜像显式运行时 exports、记录原包身份,并重新导出虚拟模块 URL。因此,内置配置项与外部插件 peer 会共享同一个 Cordis/模块实例。原生共享库与原生 addon 一同打包;ripgrep 与 macOS PTY helper 仍是可执行伴随程序。
|
||||
`dsh` 在显式 home 下初始化随附 profile、组合其 bundle patch,并从可执行程序的虚拟文件系统加载内置插件。操作系统符号链接无法进入该文件系统,因此打包运行会在 `$DSH_HOME/profiles/node_modules` 下维护小型真实 ESM 代理包。每个代理镜像显式运行时 exports、记录原包身份,并重新导出虚拟模块 URL。因此,内置配置项与外部插件 peer 会共享同一个 Cordis/模块实例。原生共享库与 Windows ConPTY addon 会同其他原生 addon 一起打包;ripgrep 与 macOS PTY helper 仍是可执行伴随程序。
|
||||
|
||||
外部 profile 管理使用 `dsh plugin --profile <name> ...`。该命令要求 `PATH` 中存在 `pnpm`;普通 SDK/profile 运行不需要它。
|
||||
|
||||
|
||||
@@ -39,7 +39,15 @@ def _host_platform_tag() -> str:
|
||||
machine = platform.machine().lower()
|
||||
arch = "arm64" if machine in {"arm64", "aarch64"} else "x64" if machine in {"x86_64", "amd64"} else machine
|
||||
system = platform.system().lower()
|
||||
key = f"macos-{arch}" if system == "darwin" else f"linux-{arch}" if system == "linux" else system
|
||||
key = (
|
||||
f"macos-{arch}"
|
||||
if system == "darwin"
|
||||
else f"linux-{arch}"
|
||||
if system == "linux"
|
||||
else f"win-{arch}"
|
||||
if system == "windows"
|
||||
else system
|
||||
)
|
||||
try:
|
||||
return _PLATFORMS[key][0]
|
||||
except KeyError as exc:
|
||||
@@ -69,16 +77,21 @@ class RuntimeBuildHook(BuildHookInterface):
|
||||
runtime_files = sorted(
|
||||
runtime_dir.glob("deepseek-harness-sdk-runtime-*") if runtime_dir.is_dir() else []
|
||||
)
|
||||
expected_files = [expected_executable, f"{expected_executable}-rg"]
|
||||
expected_files = (
|
||||
[expected_executable, f"{expected_executable.removesuffix('.exe')}-rg.exe"]
|
||||
if expected_executable.endswith(".exe")
|
||||
else [expected_executable, f"{expected_executable}-rg"]
|
||||
)
|
||||
if "-macos-" in expected_executable:
|
||||
expected_files.append(f"{expected_executable}-spawn-helper")
|
||||
expected_files.sort()
|
||||
found_files = [path.name for path in runtime_files]
|
||||
if found_files != expected_files:
|
||||
raise RuntimeError(
|
||||
f"runtime wheel {platform_tag} payload must be {expected_files}; found {found_files}"
|
||||
)
|
||||
for executable in runtime_files:
|
||||
if executable.stat().st_mode & stat.S_IXUSR == 0:
|
||||
if platform_tag != "win_amd64" and executable.stat().st_mode & stat.S_IXUSR == 0:
|
||||
raise RuntimeError(f"runtime executable is not executable: {executable}")
|
||||
build_data["pure_python"] = False
|
||||
build_data["infer_tag"] = False
|
||||
|
||||
@@ -108,6 +108,8 @@
|
||||
"@deepseek-ai/dsh-tool-fs-search": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-jobs": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-pwsh": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-ralph": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-skill": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^",
|
||||
|
||||
@@ -10,5 +10,9 @@
|
||||
"macos-arm64": {
|
||||
"tag": "macosx_14_0_arm64",
|
||||
"executable": "deepseek-harness-sdk-runtime-macos-arm64"
|
||||
},
|
||||
"win-x64": {
|
||||
"tag": "win_amd64",
|
||||
"executable": "deepseek-harness-sdk-runtime-win-x64.exe"
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ Two runtime carriers coexist under ``runtime/``, both injected by the repo's
|
||||
``scripts/build-exe-for-python-sdk.ts`` build (neither is checked into git):
|
||||
|
||||
- **exe (production)**: single-file Node executables named
|
||||
``deepseek-harness-sdk-runtime-<platform>-<arch>`` (platform in {linux, macos}, arch in
|
||||
{x64, arm64}) with a sibling ``-rg`` executable; macOS also uses a sibling
|
||||
``-spawn-helper``. The target machine needs no Node installation.
|
||||
``deepseek-harness-sdk-runtime-<platform>-<arch>`` for Linux/macOS and an
|
||||
``.exe`` counterpart for Windows. Each has a sibling ripgrep executable;
|
||||
macOS also uses a sibling ``-spawn-helper``. The target machine needs no
|
||||
Node installation.
|
||||
- **node (dev-only)**: the full deploy closure under ``runtime/node/``
|
||||
(``package.json`` + ``node_modules/``), executed as ``node
|
||||
runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`` on a
|
||||
@@ -30,7 +31,7 @@ PACKAGE_METADATA_FILENAME = "deepseek-harness-runtime.json"
|
||||
|
||||
RUNTIME_MODE_ENV_VAR = "DSH_RUNTIME_MODE"
|
||||
|
||||
_PLATFORM_TAGS = {"linux": "linux", "darwin": "macos"}
|
||||
_PLATFORM_TAGS = {"linux": "linux", "darwin": "macos", "win32": "win"}
|
||||
_ARCH_TAGS = {"x86_64": "x64", "amd64": "x64", "arm64": "arm64", "aarch64": "arm64"}
|
||||
|
||||
_EXE_ACQUISITION_HINT = (
|
||||
@@ -62,13 +63,18 @@ def bundled_runtime_path() -> Path:
|
||||
touching callers).
|
||||
"""
|
||||
tag = _current_platform_tag()
|
||||
path = bundled_package_dir() / "runtime" / f"deepseek-harness-sdk-runtime-{tag}"
|
||||
extension = ".exe" if tag.startswith("win-") else ""
|
||||
path = bundled_package_dir() / "runtime" / f"deepseek-harness-sdk-runtime-{tag}{extension}"
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(
|
||||
f"deepseek-harness-runtime-bin is missing the runtime executable at {path}. "
|
||||
+ _EXE_ACQUISITION_HINT
|
||||
)
|
||||
ripgrep = Path(f"{path}-rg")
|
||||
ripgrep = (
|
||||
path.with_name(f"{path.stem}-rg.exe")
|
||||
if tag.startswith("win-")
|
||||
else Path(f"{path}-rg")
|
||||
)
|
||||
if not ripgrep.is_file():
|
||||
raise FileNotFoundError(
|
||||
f"deepseek-harness-runtime-bin is missing the ripgrep sidecar at {ripgrep}. "
|
||||
@@ -110,11 +116,16 @@ def resolve_bundled_launch_args(mode: str | None = None) -> tuple[str, ...]:
|
||||
def _current_platform_tag() -> str:
|
||||
plat = _PLATFORM_TAGS.get(sys.platform)
|
||||
arch = _ARCH_TAGS.get(platform.machine().lower())
|
||||
if plat is None or arch is None:
|
||||
if (
|
||||
plat is None
|
||||
or arch is None
|
||||
or (plat == "win" and arch != "x64")
|
||||
or (plat == "macos" and arch != "arm64")
|
||||
):
|
||||
raise FileNotFoundError(
|
||||
"no bundled DeepSeek Harness SDK runtime exists for this platform "
|
||||
f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: "
|
||||
"linux/macos on x64/arm64. " + _EXE_ACQUISITION_HINT
|
||||
"Linux x64/arm64, macOS arm64, and Windows x64. " + _EXE_ACQUISITION_HINT
|
||||
)
|
||||
return f"{plat}-{arch}"
|
||||
|
||||
|
||||
@@ -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: cf9bb3e3ccac4908e9212d8f7247545b5a6b5d8e
|
||||
README.zh.md: 9acb8f26129144a77a834f94854cdd3f1a200086
|
||||
README.md: b6dee18cf10858d0eb24c6551e68bd45d156a01f
|
||||
README.zh.md: eff134e9461207c2e54464ee24dbf15e07f8e405
|
||||
@@ -26,7 +26,7 @@ with DeepSeekHarness(
|
||||
print(result.final_response)
|
||||
```
|
||||
|
||||
`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.
|
||||
`DeepSeekHarness` starts lazily and reuses its runtime until `close()` or context-manager exit. The initial profile handshake has an independent 30-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
|
||||
|
||||
@@ -63,4 +63,4 @@ The shipped `sdk-minimal` profile is a standalone explicit tree rather than an o
|
||||
|
||||
The selected home stores profiles, plugins, and every profile-owned durable resource. The full `sdk` profile uses its credentials, settings, and session stores; `sdk-minimal` uses only its JSONL session store. Use a fresh home when those resources must be isolated, and a fresh session id for independent work. Reusing both a harness and session id continues the durable conversation and session-owned resources.
|
||||
|
||||
See the [Python tutorial](../../docs/user/guide/python-sdk.md), [`python-sdk-agent` example](../../examples/python-sdk-agent/README.md), and [runtime wheel reference](../sdk-runtime/README.md).
|
||||
See the [Python tutorial](../../docs/user/guide/python-sdk.md), [runnable example](examples/README.md), and [runtime wheel reference](../sdk-runtime/README.md).
|
||||
@@ -26,7 +26,7 @@ with DeepSeekHarness(
|
||||
print(result.final_response)
|
||||
```
|
||||
|
||||
`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`。
|
||||
`DeepSeekHarness` 延迟启动运行时,并在调用 `close()` 或退出上下文管理器前复用该进程。首次 profile 握手通过 `initialize_timeout_seconds` 使用独立的 30 秒默认上限;普通轮次在未设置 `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`。
|
||||
|
||||
## 自定义插件
|
||||
|
||||
@@ -63,4 +63,4 @@ with DeepSeekHarness(
|
||||
|
||||
所选 home 保存 profile、插件与每个 profile 自有的持久资源。完整 `sdk` profile 使用其中的凭据、设置与会话存储;`sdk-minimal` 只使用自己的 JSONL 会话存储。需要隔离这些资源时应使用新的 home;独立工作应使用新的 session id。同时复用 harness 与 session id 会延续持久对话和会话资源。
|
||||
|
||||
另见 [Python 教程](../../docs/user/guide/python-sdk.zh.md)、[`python-sdk-agent` 示例](../../examples/python-sdk-agent/README.zh.md)和[运行时 wheel 参考](../sdk-runtime/README.zh.md)。
|
||||
另见 [Python 教程](../../docs/user/guide/python-sdk.zh.md)、[可运行示例](examples/README.zh.md)和[运行时 wheel 参考](../sdk-runtime/README.zh.md)。
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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/examples/README.md
|
||||
README.md: e79cb309318c1aa31d41bb50e52f5517b07ea485
|
||||
README.zh.md: 3208c5c1462a15a50a6990f5c989ef6b81da6ab4
|
||||
@@ -0,0 +1,44 @@
|
||||
# Python SDK example
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Runnable Python SDK example over the sole application launcher, `dsh --profile sdk-minimal`. The Python client owns JSON-RPC stdio; the profile owns the agent composition, persistence, execution policy, and plugins.
|
||||
|
||||
## Run the minimal agent
|
||||
|
||||
Install `deepseek-harness-sdk`, export a model credential, then supply an isolated Harness home and workspace:
|
||||
|
||||
```sh
|
||||
export DEEPSEEK_API_KEY=sk-your-key-here
|
||||
python python/sdk/examples/minimal.py \
|
||||
--dsh-home /absolute/path/to/example-dsh-home \
|
||||
--workspace /absolute/path/to/disposable-workspace \
|
||||
--session-id example-001 \
|
||||
"Inspect the repository and fix the failing tests."
|
||||
```
|
||||
|
||||
Set `DEEPSEEK_BASE_URL` for a compatible proxy, `DSH_MODEL` for the script's default model, or `DSH_SYSTEM_PROMPT` for the deployment persona. `--model` is the single runtime model selection; no matching environment variable is required. `--profile` can select another SDK-serving profile. The selected home stores the generated `sdk-minimal` profile and uncompressed JSONL session logs under `sessions/`; the script never reads `~/.dsh` implicitly.
|
||||
|
||||
The shipped [`@deepseek-ai/dsh-sdk-minimal` bundle](../../../packages/bundle/sdk-minimal/README.md) is the complete explicit Cordis tree for this mode. It exposes exactly:
|
||||
|
||||
- owner-scoped persistent `bash` on Linux/macOS or `pwsh` on Windows
|
||||
- `str_replace_editor` with `view`, `create`, `str_replace`, and `insert`
|
||||
|
||||
The bundle does not include `dsh-base`, so every additional row is an explicit profile change. Runtime context, local instruction discovery, compaction, settings, managed credentials, telemetry, Web tools, subagents, and the full default tool roster are absent. The tree retains SDK startup and JSON-RPC serving, one environment-configured DeepSeek adapter, local execution, and JSONL persistence.
|
||||
|
||||
The persistent PTY and editor can modify any path available to the runtime process, so use a disposable checkout or container.
|
||||
|
||||
## Add plugins
|
||||
|
||||
Use the runtime wheel's `dsh` command against the same explicit home for persistent profile changes:
|
||||
|
||||
```sh
|
||||
export DSH_HOME=/absolute/path/to/example-dsh-home
|
||||
dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle
|
||||
```
|
||||
|
||||
Use `sdk-minimal` in that command to extend this example, or `sdk` to extend the full base-backed SDK profile. The Python call can also pass additional absolute patch paths in `patches=(...)`; later files win. A selected profile must retain `@deepseek-ai/dsh-sdk-app` or another JSON-RPC server row. The example accepts no complete Cordis file or arbitrary process argv.
|
||||
|
||||
The same runtime wheel packages the `web` profile and its frontend assets for direct CLI use: `dsh web` starts that separate application. A Python SDK client cannot select `web` because it has no JSON-RPC server row.
|
||||
|
||||
See the [Python SDK tutorial](../../../docs/user/guide/python-sdk.md) and [SDK reference](../README.md).
|
||||
@@ -0,0 +1,44 @@
|
||||
# Python SDK 示例
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
基于唯一应用启动器 `dsh --profile sdk-minimal` 的可运行 Python SDK 示例。Python 客户端负责 JSON-RPC stdio;profile 负责 agent 组合、持久化、执行策略与插件。
|
||||
|
||||
## 运行极简 agent
|
||||
|
||||
安装 `deepseek-harness-sdk`、导出模型凭据,然后提供隔离的 Harness home 与 workspace:
|
||||
|
||||
```sh
|
||||
export DEEPSEEK_API_KEY=sk-your-key-here
|
||||
python python/sdk/examples/minimal.py \
|
||||
--dsh-home /absolute/path/to/example-dsh-home \
|
||||
--workspace /absolute/path/to/disposable-workspace \
|
||||
--session-id example-001 \
|
||||
"Inspect the repository and fix the failing tests."
|
||||
```
|
||||
|
||||
兼容代理使用 `DEEPSEEK_BASE_URL`,脚本默认模型使用 `DSH_MODEL`,deployment persona 使用 `DSH_SYSTEM_PROMPT`。`--model` 是唯一运行时模型选择,不要求匹配的环境变量;`--profile` 可以选择另一个提供 SDK 服务的 profile。所选 home 保存生成的 `sdk-minimal` profile,并在 `sessions/` 下保存未压缩 JSONL 会话日志;脚本绝不会隐式读取 `~/.dsh`。
|
||||
|
||||
随附的 [`@deepseek-ai/dsh-sdk-minimal` 组合包](../../../packages/bundle/sdk-minimal/README.zh.md)是该模式完整且显式的 Cordis 配置树。它只暴露:
|
||||
|
||||
- Linux/macOS 上 agent 所有的持久 `bash`,或 Windows 上的 `pwsh`
|
||||
- 支持 `view`、`create`、`str_replace` 与 `insert` 的 `str_replace_editor`
|
||||
|
||||
该组合包不包含 `dsh-base`,因此每一个新增配置项都是显式 profile 变更。运行时上下文、本地指令发现、compaction、settings、托管凭据、遥测、Web 工具、subagent 与完整默认工具清单均不存在。配置树保留 SDK 启动与 JSON-RPC 服务、一个由环境配置的 DeepSeek 适配器、本地执行和 JSONL 持久化。
|
||||
|
||||
持久 PTY 与 editor 可以修改运行时进程可访问的任何路径,因此只应在一次性 checkout 或容器中使用。
|
||||
|
||||
## 添加插件
|
||||
|
||||
对同一个显式 home 使用运行时 wheel 提供的 `dsh` 命令,以进行持久 profile 变更:
|
||||
|
||||
```sh
|
||||
export DSH_HOME=/absolute/path/to/example-dsh-home
|
||||
dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle
|
||||
```
|
||||
|
||||
在该命令中使用 `sdk-minimal` 可扩展本示例,使用 `sdk` 则扩展基于完整 base 的 SDK profile。Python 调用也可以在 `patches=(...)` 中传入更多绝对 patch 路径;后面的文件优先。所选 profile 必须保留 `@deepseek-ai/dsh-sdk-app` 或另一个 JSON-RPC server 配置项。该示例不接受完整 Cordis 文件或任意进程 argv。
|
||||
|
||||
同一个运行时 wheel 还为直接 CLI 使用打包 `web` profile 及其前端产物:`dsh web` 会启动这个独立应用。Python SDK client 不能选择 `web`,因为其中没有 JSON-RPC server 配置项。
|
||||
|
||||
另见 [Python SDK 教程](../../../docs/user/guide/python-sdk.zh.md)与 [SDK 参考](../README.zh.md)。
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run one minimal-agent turn through the bundled Python SDK runtime."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Parse one task and print the agent's final response."""
|
||||
parser = argparse.ArgumentParser()
|
||||
configured_home = os.environ.get("DSH_HOME", "")
|
||||
parser.add_argument("prompt", help="Task for the minimal agent")
|
||||
parser.add_argument("--workspace", type=Path, default=Path.cwd())
|
||||
parser.add_argument(
|
||||
"--dsh-home",
|
||||
type=Path,
|
||||
default=Path(configured_home) if configured_home.strip() else None,
|
||||
)
|
||||
parser.add_argument("--profile", default="sdk-minimal")
|
||||
parser.add_argument("--session-id")
|
||||
parser.add_argument("--provider", default="deepseek-official")
|
||||
parser.add_argument("--model", default=os.environ.get("DSH_MODEL", "deepseek-v4-flash"))
|
||||
parser.add_argument("--max-tokens", type=int)
|
||||
args = parser.parse_args()
|
||||
if args.dsh_home is None:
|
||||
parser.error("--dsh-home or a non-empty DSH_HOME is required")
|
||||
|
||||
workspace = args.workspace.resolve()
|
||||
dsh_home = args.dsh_home.resolve()
|
||||
with DeepSeekHarness(
|
||||
provider=args.provider,
|
||||
model=args.model,
|
||||
max_tokens=args.max_tokens,
|
||||
cwd=str(workspace),
|
||||
dsh_home=str(dsh_home),
|
||||
profile=args.profile,
|
||||
) as harness:
|
||||
result = harness.run(args.prompt, session_id=args.session_id)
|
||||
print(result.final_response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -29,7 +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
|
||||
initialize_timeout_seconds: float = 30.0
|
||||
request_timeout_seconds: float | None = None
|
||||
shutdown_timeout_seconds: float | None = 1.0
|
||||
base_url: str | None = None
|
||||
|
||||
@@ -31,10 +31,9 @@ class HarnessConfig:
|
||||
dsh_home: str | None = None
|
||||
cwd: str | None = None
|
||||
env: dict[str, str] | None = None
|
||||
initialize_timeout_seconds: float = 10.0
|
||||
initialize_timeout_seconds: float = 30.0
|
||||
request_timeout_seconds: float | None = None
|
||||
shutdown_timeout_seconds: float | None = 1.0
|
||||
_launch_args: tuple[str, ...] | None = None
|
||||
|
||||
|
||||
class HarnessClient:
|
||||
@@ -47,7 +46,7 @@ class HarnessClient:
|
||||
_launch_args: tuple[str, ...] | None = None,
|
||||
) -> None:
|
||||
self.config = config or HarnessConfig()
|
||||
self._launch_args = _launch_args or self.config._launch_args
|
||||
self._launch_args = _launch_args
|
||||
self._proc: subprocess.Popen[str] | None = None
|
||||
self._lock = threading.Lock()
|
||||
self._write_lock = threading.Lock()
|
||||
@@ -93,11 +92,14 @@ class HarnessClient:
|
||||
self._start_stderr_thread()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the runtime after a bounded opportunity to flush durable state."""
|
||||
proc = self._proc
|
||||
if proc is None:
|
||||
return
|
||||
shutdown_completed = False
|
||||
try:
|
||||
self.request("shutdown", None, response_model=_ShutdownResponse, timeout_seconds=self.config.shutdown_timeout_seconds)
|
||||
shutdown_completed = True
|
||||
except Exception as exc:
|
||||
self._stderr_lines.append(f"shutdown request failed: {exc}")
|
||||
if proc.stdin:
|
||||
@@ -105,16 +107,22 @@ class HarnessClient:
|
||||
proc.stdin.close()
|
||||
except Exception as exc:
|
||||
self._stderr_lines.append(f"stdin close failed: {exc}")
|
||||
if shutdown_completed:
|
||||
try:
|
||||
proc.wait(timeout=self.config.shutdown_timeout_seconds)
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.terminate()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
try:
|
||||
proc.wait(timeout=self.config.shutdown_timeout_seconds)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.wait(timeout=self.config.shutdown_timeout_seconds)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
self._proc = None
|
||||
self._fail_waiters(self._runtime_closed_error("DeepSeek Harness runtime closed"))
|
||||
if self._reader_thread and self._reader_thread.is_alive():
|
||||
|
||||
@@ -471,9 +471,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
|
||||
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
|
||||
@@ -611,7 +609,7 @@ for line in sys.stdin:
|
||||
def broken_filter(_notification: object) -> bool:
|
||||
raise RuntimeError("bad notification filter")
|
||||
|
||||
with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
|
||||
with HarnessClient(_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,
|
||||
@@ -648,7 +646,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script)))) as client:
|
||||
with HarnessClient(_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"}])
|
||||
@@ -675,9 +673,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
|
||||
request = client.next_request()
|
||||
@@ -709,9 +705,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(_launch_args=(sys.executable, str(script)))
|
||||
) as client:
|
||||
with HarnessClient(_launch_args=(sys.executable, str(script))) as client:
|
||||
init = client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
assert init.serverInfo.name == "fake-dsh"
|
||||
|
||||
@@ -730,10 +724,10 @@ time.sleep(60)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
profile="web",
|
||||
initialize_timeout_seconds=0.1,
|
||||
)
|
||||
),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
) as client:
|
||||
start = time.monotonic()
|
||||
try:
|
||||
@@ -768,9 +762,9 @@ for line in sys.stdin:
|
||||
|
||||
client = HarnessClient(
|
||||
HarnessConfig(
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
shutdown_timeout_seconds=0.1,
|
||||
)
|
||||
),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
)
|
||||
client.start()
|
||||
proc = client._proc
|
||||
@@ -783,6 +777,43 @@ for line in sys.stdin:
|
||||
assert client._proc is None
|
||||
|
||||
|
||||
def test_client_close_allows_eof_quiescence_after_shutdown_response(tmp_path: Path) -> None:
|
||||
script = tmp_path / "fake_runtime.py"
|
||||
marker = tmp_path / "quiesced.txt"
|
||||
script.write_text(
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
for line in sys.stdin:
|
||||
msg = json.loads(line)
|
||||
if msg.get("method") == "initialize":
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "fake-dsh"}}}), flush=True)
|
||||
elif msg.get("method") == "shutdown":
|
||||
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
|
||||
|
||||
time.sleep(0.05)
|
||||
Path(os.environ["QUIESCED_MARKER"]).write_text("quiesced")
|
||||
""".strip()
|
||||
)
|
||||
|
||||
client = HarnessClient(
|
||||
HarnessConfig(
|
||||
env={"QUIESCED_MARKER": str(marker)},
|
||||
shutdown_timeout_seconds=1,
|
||||
),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
)
|
||||
client.start()
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
client.close()
|
||||
|
||||
assert marker.read_text() == "quiesced"
|
||||
|
||||
|
||||
def test_initialize_failure_reaps_started_runtime(tmp_path: Path) -> None:
|
||||
script = tmp_path / "rejecting_runtime.py"
|
||||
script.write_text(
|
||||
@@ -801,7 +832,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
|
||||
client = HarnessClient(_launch_args=(sys.executable, str(script)))
|
||||
client.start()
|
||||
proc = client._proc
|
||||
assert proc is not None
|
||||
@@ -836,9 +867,12 @@ def test_public_signatures_omit_unsupported_wire_parameters() -> None:
|
||||
)
|
||||
assert "initialize_timeout_seconds" in DeepSeekHarnessConfig.__dataclass_fields__
|
||||
assert "initialize_timeout_seconds" in HarnessConfig.__dataclass_fields__
|
||||
assert DeepSeekHarnessConfig().initialize_timeout_seconds == 30.0
|
||||
assert HarnessConfig().initialize_timeout_seconds == 30.0
|
||||
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 "_launch_args" not in HarnessConfig.__dataclass_fields__
|
||||
assert "session_root" not in RunResult.__dataclass_fields__
|
||||
|
||||
|
||||
@@ -861,7 +895,7 @@ for line in sys.stdin:
|
||||
""".strip()
|
||||
)
|
||||
|
||||
client = HarnessClient(HarnessConfig(_launch_args=(sys.executable, str(script))))
|
||||
client = HarnessClient(_launch_args=(sys.executable, str(script)))
|
||||
client.start()
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
client.close()
|
||||
@@ -881,9 +915,9 @@ sys.exit(42)
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
request_timeout_seconds=2,
|
||||
)
|
||||
),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
) as client:
|
||||
with pytest.raises(Exception, match="fatal bridge exploded"):
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
@@ -913,9 +947,9 @@ with open(os.environ["SEEN"], "w") as seen:
|
||||
|
||||
with HarnessClient(
|
||||
HarnessConfig(
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
env={"SEEN": str(output)},
|
||||
)
|
||||
),
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
) as client:
|
||||
client.initialize(provider="deepseek-official", cwd="/workspace", model="dsagent")
|
||||
threads = [
|
||||
|
||||
@@ -62,6 +62,14 @@ def test_macos_wheel_tag_does_not_claim_unsupported_node_platforms() -> None:
|
||||
assert build_python_release.PLATFORMS["macos-arm64"][1] == "deepseek-harness-sdk-runtime-macos-arm64"
|
||||
|
||||
|
||||
def test_windows_wheel_tag_and_payload_are_x64_only() -> None:
|
||||
assert build_python_release.PLATFORMS["win-x64"] == (
|
||||
"win_amd64",
|
||||
"deepseek-harness-sdk-runtime-win-x64.exe",
|
||||
)
|
||||
assert not any(name.startswith("win-") and name != "win-x64" for name in build_python_release.PLATFORMS)
|
||||
|
||||
|
||||
def test_platform_manifest_rejects_incomplete_entries(tmp_path: Path) -> None:
|
||||
manifest = tmp_path / "platforms.json"
|
||||
manifest.write_text('{"macos-arm64":{"tag":"macosx_14_0_arm64"}}\n')
|
||||
@@ -85,7 +93,10 @@ def test_stage_sdk_keeps_distribution_module_and_runtime_pin_distinct(tmp_path:
|
||||
assert (destination / "src" / "deepseek_harness" / "__init__.py").is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("target", "with_helper"), [("linux-x64", False), ("macos-arm64", True)])
|
||||
@pytest.mark.parametrize(
|
||||
("target", "with_helper"),
|
||||
[("linux-x64", False), ("macos-arm64", True), ("win-x64.exe", False)],
|
||||
)
|
||||
def test_stage_runtime_copies_platform_payload(
|
||||
tmp_path: Path, target: str, with_helper: bool
|
||||
) -> None:
|
||||
@@ -93,7 +104,11 @@ def test_stage_runtime_copies_platform_payload(
|
||||
executable.write_bytes(b"runtime")
|
||||
executable.chmod(0o755)
|
||||
expected = {executable.name: b"runtime"}
|
||||
ripgrep = Path(f"{executable}-rg")
|
||||
ripgrep = (
|
||||
executable.with_name(f"{executable.stem}-rg.exe")
|
||||
if executable.suffix == ".exe"
|
||||
else Path(f"{executable}-rg")
|
||||
)
|
||||
ripgrep.write_bytes(b"ripgrep")
|
||||
ripgrep.chmod(0o755)
|
||||
expected[ripgrep.name] = b"ripgrep"
|
||||
@@ -122,3 +137,16 @@ def test_stage_runtime_copies_platform_payload(
|
||||
assert (destination / "THIRD_PARTY_NOTICES.md").read_bytes() == (
|
||||
ROOT / "THIRD_PARTY_NOTICES.md"
|
||||
).read_bytes()
|
||||
|
||||
|
||||
def test_stage_runtime_rejects_a_noncanonical_executable_name(tmp_path: Path) -> None:
|
||||
executable = tmp_path / "renamed.exe"
|
||||
executable.write_bytes(b"runtime")
|
||||
|
||||
with pytest.raises(ValueError, match="must be named deepseek-harness-sdk-runtime-win-x64.exe"):
|
||||
build_python_release.stage_runtime(
|
||||
tmp_path / "staging",
|
||||
"1.2.3",
|
||||
executable,
|
||||
"deepseek-harness-sdk-runtime-win-x64.exe",
|
||||
)
|
||||
@@ -55,6 +55,38 @@ def test_runtime_requires_spawn_helper_only_on_macos(
|
||||
assert runtime.bundled_runtime_path() == linux
|
||||
|
||||
|
||||
def test_windows_runtime_uses_exe_payload_and_exe_sidecar(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
runtime_dir = tmp_path / "runtime"
|
||||
runtime_dir.mkdir()
|
||||
executable = runtime_dir / "deepseek-harness-sdk-runtime-win-x64.exe"
|
||||
executable.touch()
|
||||
(runtime_dir / "deepseek-harness-sdk-runtime-win-x64-rg.exe").touch()
|
||||
monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "win-x64")
|
||||
|
||||
assert runtime.bundled_runtime_path() == executable
|
||||
|
||||
|
||||
def test_current_platform_supports_windows_x64_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(runtime.sys, "platform", "win32")
|
||||
monkeypatch.setattr(runtime.platform, "machine", lambda: "AMD64")
|
||||
assert runtime._current_platform_tag() == "win-x64"
|
||||
|
||||
monkeypatch.setattr(runtime.platform, "machine", lambda: "ARM64")
|
||||
with pytest.raises(FileNotFoundError, match="Windows x64"):
|
||||
runtime._current_platform_tag()
|
||||
|
||||
|
||||
def test_current_platform_rejects_macos_x64(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(runtime.sys, "platform", "darwin")
|
||||
monkeypatch.setattr(runtime.platform, "machine", lambda: "x86_64")
|
||||
|
||||
with pytest.raises(FileNotFoundError, match="macOS arm64"):
|
||||
runtime._current_platform_tag()
|
||||
|
||||
|
||||
def test_runtime_requires_ripgrep_sidecar(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
|
||||
Reference in new issue
Block a user