fix(python): make Windows release paths native

Run the GitHub Windows runtime leg under the runner’s native PowerShell instead of inheriting the POSIX Bash body. POSIX and Windows now own explicit output resolution, virtual-environment setup, environment scrubbing, and keyless/live black-box commands, while portable build commands continue to use each runner’s default shell.

Put the pinned uv installation on the GitLab Windows job PATH before either the smoke or release builder invokes it. Reject a runtime executable whose basename does not match the selected platform manifest, and reject Intel macOS at platform selection instead of reporting a misleading missing artifact.

Add a complete PowerShell path to the published Python tutorial and record the three-phase shutdown-time bound in the Windows runtime decision. Workflow, Python, and bilingual documentation tests pin the resulting behavior.
This commit is contained in:
Tianyi Cui
2026-08-24 19:09:40 +08:00
parent d4a63abe85
commit 8101a0d097
13 changed files with 270 additions and 67 deletions
@@ -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 .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.md # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-python-sdk-windows-x64-runtime.md
2026-08-23-python-sdk-windows-x64-runtime.md: 57c3ac66517d62528464521ba37e9c644899d1ca 2026-08-23-python-sdk-windows-x64-runtime.md: b4ba54d9bd8e7a2eaa9277116a7868a1942d0531
2026-08-23-python-sdk-windows-x64-runtime.zh.md: e50efac91bed33f6559386ebdbb3deaa52c9d3ca 2026-08-23-python-sdk-windows-x64-runtime.zh.md: 6f05817b647a152cec626f09866f415d164064ec
@@ -26,7 +26,7 @@ The required GitHub matrix builds `node24-win-x64` on `windows-2025` beside the
The Windows lane creates a clean Windows virtual environment, installs the exact SDK and `win_amd64` runtime wheels, changes to a directory outside the checkout, unsets `PYTHONPATH` and `DSH_RUNTIME_MODE`, and runs the same `--scenario all --installed-wheel` blackbox as every other target. Trusted pull requests also run the same two-turn `sdk-live` provider scenario. Fork and Dependabot heads receive no key. The Windows lane creates a clean Windows virtual environment, installs the exact SDK and `win_amd64` runtime wheels, changes to a directory outside the checkout, unsets `PYTHONPATH` and `DSH_RUNTIME_MODE`, and runs the same `--scenario all --installed-wheel` blackbox as every other target. Trusted pull requests also run the same two-turn `sdk-live` provider scenario. Fork and Dependabot heads receive no key.
After a successful shutdown response, the Python client closes stdin and waits within the configured shutdown timeout for the `dsh` context to exit and flush durable session state before terminating it. A failed shutdown retains immediate bounded termination. This distinction preserves the final accepted turn on Windows, where `terminate()` force-kills the process rather than delivering a catchable signal. After a successful shutdown response, the Python client closes stdin and waits within the configured shutdown timeout for the `dsh` context to exit and flush durable session state before terminating it. A failed shutdown retains immediate bounded termination. `shutdown_timeout_seconds` bounds each of the shutdown request, EOF grace, and termination-confirmation phases, so a pathological close can approach three times that value before the final kill. This distinction preserves the final accepted turn on Windows, where `terminate()` force-kills the process rather than delivering a catchable signal.
The minimal blackbox uses persistent `pwsh` plus `str_replace_editor` on Windows and owns `minimal/win-x64/model-visible.json`; Linux and macOS retain persistent Bash and the shared `minimal/model-visible.json`. The advanced process/subagent snapshot and restart/durable-log snapshot remain shared across all targets. The shipped [`sdk-minimal` bundle](../../../../packages/bundle/sdk-minimal/README.md) selects the same platform shell pair for the runnable Python tutorial. The minimal blackbox uses persistent `pwsh` plus `str_replace_editor` on Windows and owns `minimal/win-x64/model-visible.json`; Linux and macOS retain persistent Bash and the shared `minimal/model-visible.json`. The advanced process/subagent snapshot and restart/durable-log snapshot remain shared across all targets. The shipped [`sdk-minimal` bundle](../../../../packages/bundle/sdk-minimal/README.md) selects the same platform shell pair for the runnable Python tutorial.
@@ -42,7 +42,7 @@ This decision partially supersedes the Windows non-goal in the [single-file runt
**Give Windows a smaller smoke suite.** Rejected because a platform wheel cannot borrow protocol, persistence, worker, MCP, plugin, native-tool, or real-provider evidence from another executable. Platform-specific expected output is limited to the persistent shell surface; the remaining snapshots stay shared. **Give Windows a smaller smoke suite.** Rejected because a platform wheel cannot borrow protocol, persistence, worker, MCP, plugin, native-tool, or real-provider evidence from another executable. Platform-specific expected output is limited to the persistent shell surface; the remaining snapshots stay shared.
**Run Windows commands through PowerShell workflow steps only.** Rejected for the reusable build body because it would duplicate the Linux/macOS installation and blackbox sequence. Git Bash supplies the common workflow grammar; only virtual-environment executable selection and the product payload names differ. **Run the Windows leg through Git Bash.** Rejected because the repository requires native `pwsh` on Windows runners and MSYS path conversion would not prove native command behavior. Portable one-line steps use each runner's default shell; path, virtual-environment, and blackbox steps have explicit POSIX and PowerShell forms.
## Consequences ## Consequences
@@ -26,7 +26,7 @@ Python 进程仍按 [Python profile 运行时决策](2026-08-23-python-sdk-dsh-p
Windows lane 会创建干净的 Windows 虚拟环境,安装版本精确匹配的 SDK 与 `win_amd64` 运行时 wheel,切换到 checkout 外的目录,清除 `PYTHONPATH``DSH_RUNTIME_MODE`,再运行与其他目标相同的 `--scenario all --installed-wheel` 黑盒测试。可信拉取请求还会运行相同的双轮 `sdk-live` 真实提供方场景。Fork 与 Dependabot head 不会获得密钥。 Windows lane 会创建干净的 Windows 虚拟环境,安装版本精确匹配的 SDK 与 `win_amd64` 运行时 wheel,切换到 checkout 外的目录,清除 `PYTHONPATH``DSH_RUNTIME_MODE`,再运行与其他目标相同的 `--scenario all --installed-wheel` 黑盒测试。可信拉取请求还会运行相同的双轮 `sdk-live` 真实提供方场景。Fork 与 Dependabot head 不会获得密钥。
成功收到 shutdown 响应后,Python 客户端会关闭 stdin,并在已配置的 shutdown 超时内等待 `dsh` 上下文退出及刷写持久 session 状态,然后才回退到终止进程。Shutdown 失败时仍立即执行有界终止。该区别会保留 Windows 上最后一个已接受轮次;该平台的 `terminate()` 会强制结束进程,而不是发送可捕获信号。 成功收到 shutdown 响应后,Python 客户端会关闭 stdin,并在已配置的 shutdown 超时内等待 `dsh` 上下文退出及刷写持久 session 状态,然后才回退到终止进程。Shutdown 失败时仍立即执行有界终止。`shutdown_timeout_seconds` 会分别限制 shutdown 请求、EOF 宽限与终止确认阶段,因此异常关闭在最终 kill 前可能接近该值的三倍。该区别会保留 Windows 上最后一个已接受轮次;该平台的 `terminate()` 会强制结束进程,而不是发送可捕获信号。
极简黑盒测试在 Windows 上使用持久 `pwsh``str_replace_editor`,并由 `minimal/win-x64/model-visible.json` 固定预期;Linux 与 macOS 保留持久 Bash 和共享的 `minimal/model-visible.json`。高级进程/subagent 快照与重启/持久日志快照继续由所有目标共享。随附的 [`sdk-minimal` 组合包](../../../../packages/bundle/sdk-minimal/README.zh.md)为可运行 Python 教程选择同一组平台 shell。 极简黑盒测试在 Windows 上使用持久 `pwsh``str_replace_editor`,并由 `minimal/win-x64/model-visible.json` 固定预期;Linux 与 macOS 保留持久 Bash 和共享的 `minimal/model-visible.json`。高级进程/subagent 快照与重启/持久日志快照继续由所有目标共享。随附的 [`sdk-minimal` 组合包](../../../../packages/bundle/sdk-minimal/README.zh.md)为可运行 Python 教程选择同一组平台 shell。
@@ -42,7 +42,7 @@ Windows lane 会创建干净的 Windows 虚拟环境,安装版本精确匹配
**为 Windows 提供较小的冒烟测试套件。** 否决:一个平台 wheel 不能借用其他可执行文件的协议、持久化、worker、MCP、插件、原生工具或真实提供方证据。只有持久 shell surface 使用平台专属预期,其余快照继续共享。 **为 Windows 提供较小的冒烟测试套件。** 否决:一个平台 wheel 不能借用其他可执行文件的协议、持久化、worker、MCP、插件、原生工具或真实提供方证据。只有持久 shell surface 使用平台专属预期,其余快照继续共享。
**通过 PowerShell workflow 步骤运行 Windows 命令** 否决:这会在可复用构建主体中复制 Linux/macOS 的安装与黑盒测试序列。Git Bash 提供通用 workflow 语法;只有虚拟环境可执行程序选择与产品载荷名称因平台而异 **通过 Git Bash 运行 Windows lane** 否决:仓库要求 Windows runner 使用原生 `pwsh`,而 MSYS 路径转换无法证明原生命令行为。可移植的单行步骤使用各 runner 的默认 shell;路径、虚拟环境与黑盒步骤分别提供显式 POSIX 和 PowerShell 形式
## Consequences ## Consequences
+109 -36
View File
@@ -156,9 +156,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: ${{ fromJSON(needs.plan.outputs.matrix) }} include: ${{ fromJSON(needs.plan.outputs.matrix) }}
defaults:
run:
shell: bash
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -241,8 +238,9 @@ jobs:
DSH_BUILD_CLIENT_PROFILE: official DSH_BUILD_CLIENT_PROFILE: official
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 }}
- name: Resolve platform outputs - name: Resolve platform outputs (POSIX)
id: runtime id: runtime-posix
if: runner.os != 'Windows'
env: env:
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
VERSION: ${{ needs.plan.outputs.version }} VERSION: ${{ needs.plan.outputs.version }}
@@ -254,27 +252,36 @@ jobs:
linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;; linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;;
linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;; linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;;
macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl ;; macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl ;;
win-x64)
exe="$exe.exe"
wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-win_amd64.whl
;;
*) echo "::error::Unsupported runtime platform $platform"; exit 1 ;; *) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
esac esac
if [ "$RUNNER_OS" = Windows ]; then [ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
[ -f "$exe" ] || { echo "::error::$exe missing"; exit 1; }
else
[ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
fi
echo "platform=$platform" >> "$GITHUB_OUTPUT" echo "platform=$platform" >> "$GITHUB_OUTPUT"
echo "exe=$exe" >> "$GITHUB_OUTPUT" echo "exe=$exe" >> "$GITHUB_OUTPUT"
echo "wheel=$wheel" >> "$GITHUB_OUTPUT" echo "wheel=$wheel" >> "$GITHUB_OUTPUT"
- name: Resolve platform outputs (Windows)
id: runtime-windows
if: runner.os == 'Windows'
shell: pwsh
env:
TARGET: ${{ matrix.target }}
VERSION: ${{ needs.plan.outputs.version }}
run: |
if ($env:TARGET -ne 'node24-win-x64') { throw "Unsupported runtime target $env:TARGET" }
$platform = 'win-x64'
$exe = Join-Path $PWD 'dist-exe\deepseek-harness-sdk-runtime-win-x64.exe'
$wheel = "deepseek_harness_runtime_bin-$env:VERSION-py3-none-win_amd64.whl"
if (-not (Test-Path -LiteralPath $exe -PathType Leaf)) { throw "Runtime executable is missing at $exe" }
"platform=$platform" >> $env:GITHUB_OUTPUT
"exe=$exe" >> $env:GITHUB_OUTPUT
"wheel=$wheel" >> $env:GITHUB_OUTPUT
- name: Build release-shaped runtime wheel - name: Build release-shaped runtime wheel
run: >- run: >-
python scripts/build-python-release.py python scripts/build-python-release.py
--package runtime --package runtime
--platform "${{ steps.runtime.outputs.platform }}" --platform "${{ steps.runtime-posix.outputs.platform || steps.runtime-windows.outputs.platform }}"
--runtime-exe "${{ steps.runtime.outputs.exe }}" --runtime-exe "${{ steps.runtime-posix.outputs.exe || steps.runtime-windows.outputs.exe }}"
--output-dir dist-python --output-dir dist-python
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v8
@@ -282,40 +289,68 @@ jobs:
name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
path: dist-python path: dist-python
- name: Install local SDK and runtime wheels into a clean venv - name: Install local SDK and runtime wheels into a clean venv (POSIX)
id: smoke-venv id: smoke-venv-posix
if: runner.os != 'Windows'
env: env:
RUNTIME_WHEEL: ${{ steps.runtime.outputs.wheel }} RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
run: | run: |
set -euo pipefail set -euo pipefail
venv="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))')" venv="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))')"
python -m venv "$venv" python -m venv "$venv"
if [ "$RUNNER_OS" = Windows ]; then smoke_python="$venv/bin/python"
smoke_python="$(cygpath -u "$venv")/Scripts/python.exe"
else
smoke_python="$venv/bin/python"
fi
"$smoke_python" -m pip install \ "$smoke_python" -m pip install \
"dist-python/$SDK_WHEEL" \ "dist-python/$SDK_WHEEL" \
"dist-python/$RUNTIME_WHEEL" "dist-python/$RUNTIME_WHEEL"
echo "python=$smoke_python" >> "$GITHUB_OUTPUT" echo "python=$smoke_python" >> "$GITHUB_OUTPUT"
- name: Run installed-wheel keyless black-box tests - name: Install local SDK and runtime wheels into a clean venv (Windows)
id: smoke-venv-windows
if: runner.os == 'Windows'
shell: pwsh
env:
RUNTIME_WHEEL: ${{ steps.runtime-windows.outputs.wheel }}
SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
run: |
$venv = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-smoke-"))').Trim()
python -m venv $venv
$smokePython = Join-Path $venv 'Scripts\python.exe'
& $smokePython -m pip install "dist-python/$env:SDK_WHEEL" "dist-python/$env:RUNTIME_WHEEL"
if ($LASTEXITCODE -ne 0) { throw "Wheel installation failed with exit code $LASTEXITCODE" }
"python=$smokePython" >> $env:GITHUB_OUTPUT
- name: Run installed-wheel keyless black-box tests (POSIX)
if: runner.os != 'Windows'
run: | run: |
set -euo pipefail set -euo pipefail
blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))')" blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))')"
if [ "$RUNNER_OS" = Windows ]; then blackbox_root="$(cygpath -u "$blackbox_root")"; fi
cd "$blackbox_root" cd "$blackbox_root"
env -u PYTHONPATH -u DSH_RUNTIME_MODE \ env -u PYTHONPATH -u DSH_RUNTIME_MODE \
"${{ steps.smoke-venv.outputs.python }}" \ "${{ steps.smoke-venv-posix.outputs.python }}" \
"$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \ "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
--scenario all \ --scenario all \
--installed-wheel --installed-wheel
- name: Preflight installed-wheel real API test - name: Run installed-wheel keyless black-box tests (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-"))').Trim()
Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
Push-Location $blackboxRoot
try {
& "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario all --installed-wheel
if ($LASTEXITCODE -ne 0) { throw "Installed-wheel black-box failed with exit code $LASTEXITCODE" }
} finally {
Pop-Location
}
- name: Preflight installed-wheel real API test (POSIX)
if: >- if: >-
inputs.ci inputs.ci
&& runner.os != 'Windows'
&& (github.event_name != 'pull_request' && (github.event_name != 'pull_request'
|| !(github.event.pull_request.head.repo.fork || !(github.event.pull_request.head.repo.fork
|| github.event.pull_request.user.login == 'dependabot[bot]')) || github.event.pull_request.user.login == 'dependabot[bot]'))
@@ -328,9 +363,25 @@ jobs:
exit 1 exit 1
fi fi
- name: Run installed-wheel real API black-box test - name: Preflight installed-wheel real API test (Windows)
if: >- if: >-
inputs.ci inputs.ci
&& runner.os == 'Windows'
&& (github.event_name != 'pull_request'
|| !(github.event.pull_request.head.repo.fork
|| github.event.pull_request.user.login == 'dependabot[bot]'))
shell: pwsh
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
run: |
if ([string]::IsNullOrWhiteSpace($env:DEEPSEEK_API_KEY)) {
throw 'DEEPSEEK_API_KEY_EXTERNAL is empty; the installed-wheel real API test cannot self-skip.'
}
- name: Run installed-wheel real API black-box test (POSIX)
if: >-
inputs.ci
&& runner.os != 'Windows'
&& (github.event_name != 'pull_request' && (github.event_name != 'pull_request'
|| !(github.event.pull_request.head.repo.fork || !(github.event.pull_request.head.repo.fork
|| github.event.pull_request.user.login == 'dependabot[bot]')) || github.event.pull_request.user.login == 'dependabot[bot]'))
@@ -340,19 +391,41 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))')" blackbox_root="$(python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))')"
if [ "$RUNNER_OS" = Windows ]; then blackbox_root="$(cygpath -u "$blackbox_root")"; fi
cd "$blackbox_root" cd "$blackbox_root"
env -u PYTHONPATH -u DSH_RUNTIME_MODE \ env -u PYTHONPATH -u DSH_RUNTIME_MODE \
"${{ steps.smoke-venv.outputs.python }}" \ "${{ steps.smoke-venv-posix.outputs.python }}" \
"$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \ "$GITHUB_WORKSPACE/scripts/smoke-python-runtime.py" \
--scenario sdk-live \ --scenario sdk-live \
--installed-wheel --installed-wheel
- name: Run installed-wheel real API black-box test (Windows)
if: >-
inputs.ci
&& runner.os == 'Windows'
&& (github.event_name != 'pull_request'
|| !(github.event.pull_request.head.repo.fork
|| github.event.pull_request.user.login == 'dependabot[bot]'))
shell: pwsh
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
DEEPSEEK_BASE_URL: https://api.deepseek.com
run: |
$blackboxRoot = (& python -c 'import tempfile; print(tempfile.mkdtemp(prefix="dsh-sdk-blackbox-live-"))').Trim()
Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
Remove-Item Env:DSH_RUNTIME_MODE -ErrorAction SilentlyContinue
Push-Location $blackboxRoot
try {
& "${{ steps.smoke-venv-windows.outputs.python }}" "$env:GITHUB_WORKSPACE\scripts\smoke-python-runtime.py" --scenario sdk-live --installed-wheel
if ($LASTEXITCODE -ne 0) { throw "Installed-wheel live API smoke failed with exit code $LASTEXITCODE" }
} finally {
Pop-Location
}
- name: Check Linux GLIBC requirements - name: Check Linux GLIBC requirements
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
set -euo pipefail set -euo pipefail
readelf --version-info "${{ steps.runtime.outputs.exe }}" | tee glibc-versions.txt readelf --version-info "${{ steps.runtime-posix.outputs.exe }}" | tee glibc-versions.txt
maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' glibc-versions.txt | sort -V | tail -1)" maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' glibc-versions.txt | sort -V | tail -1)"
[ -n "$maximum" ] || { echo "::error::No GLIBC requirements found"; exit 1; } [ -n "$maximum" ] || { echo "::error::No GLIBC requirements found"; exit 1; }
dpkg --compare-versions "$maximum" le 2.28 || { dpkg --compare-versions "$maximum" le 2.28 || {
@@ -363,7 +436,7 @@ jobs:
- name: Check macOS deployment target - name: Check macOS deployment target
if: runner.os == 'macOS' if: runner.os == 'macOS'
env: env:
EXE: ${{ steps.runtime.outputs.exe }} EXE: ${{ steps.runtime-posix.outputs.exe }}
run: >- run: >-
python3 scripts/check-macos-deployment-target.py python3 scripts/check-macos-deployment-target.py
"$EXE" "$EXE-spawn-helper" "$EXE" "$EXE-spawn-helper"
@@ -372,7 +445,7 @@ jobs:
if: runner.os == 'Linux' if: runner.os == 'Linux'
env: env:
RUNNER_ARCH: ${{ runner.arch }} RUNNER_ARCH: ${{ runner.arch }}
RUNTIME_WHEEL: ${{ steps.runtime.outputs.wheel }} RUNTIME_WHEEL: ${{ steps.runtime-posix.outputs.wheel }}
SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
run: | run: |
set -euo pipefail set -euo pipefail
@@ -392,7 +465,7 @@ jobs:
- uses: actions/upload-artifact@v7 - uses: actions/upload-artifact@v7
with: with:
name: ${{ steps.runtime.outputs.wheel }} name: ${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
path: dist-python/${{ steps.runtime.outputs.wheel }} path: dist-python/${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }}
if-no-files-found: error if-no-files-found: error
retention-days: 7 retention-days: 7
+1
View File
@@ -112,6 +112,7 @@ runtime-windows-x64:
- $env:DSH_WHEEL_VERSION = (& .ci-python\Scripts\python.exe -c 'import runpy; release = runpy.run_path("scripts/build-python-release.py"); print(release["pep440_version"](release["repository_version"]()))') - $env:DSH_WHEEL_VERSION = (& .ci-python\Scripts\python.exe -c 'import runpy; release = runpy.run_path("scripts/build-python-release.py"); print(release["pep440_version"](release["repository_version"]()))')
- if ($env:CI_COMMIT_TAG -ne "python-v$env:DSH_VERSION") { throw "Tag $env:CI_COMMIT_TAG does not match package.json version $env:DSH_VERSION" } - if ($env:CI_COMMIT_TAG -ne "python-v$env:DSH_VERSION") { throw "Tag $env:CI_COMMIT_TAG does not match package.json version $env:DSH_VERSION" }
- .ci-python\Scripts\python.exe -m pip install uv==0.11.23 - .ci-python\Scripts\python.exe -m pip install uv==0.11.23
- $env:Path = (Join-Path $PWD ".ci-python\Scripts") + [IO.Path]::PathSeparator + $env:Path
script: script:
- corepack enable - corepack enable
- pnpm install --frozen-lockfile - pnpm install --frozen-lockfile
+2 -2
View File
@@ -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 docs/user/guide/python-sdk.md # pnpm run verify-translation-pairing --write docs/user/guide/python-sdk.md
python-sdk.md: 5fd8b35c08acdd0f0ff457547ca62b31e12994d5 python-sdk.md: 388b259f0adbba11b7d359fcf861980cf0a3bec7
python-sdk.zh.md: 354d6829dc07056556d19ddfca68a95ad3a5b47f python-sdk.zh.md: 2cc23e5cd1d7d7df5ad4b27441c54e6c3239c917
+43
View File
@@ -14,6 +14,8 @@ This tutorial installs the published Python SDK, runs the shipped standalone min
## Install the SDK ## Install the SDK
### Linux and macOS
```sh ```sh
git clone https://github.com/deepseek-ai/deepseek-harness.git git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness cd deepseek-harness
@@ -22,19 +24,40 @@ python -m venv .venv
python -m pip install deepseek-harness-sdk python -m pip install deepseek-harness-sdk
``` ```
### Windows PowerShell
```powershell
git clone https://github.com/deepseek-ai/deepseek-harness.git
Set-Location deepseek-harness
py -3.10 -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install deepseek-harness-sdk
```
The installation includes a matching native runtime wheel and the `dsh` command. Normal SDK execution needs no system Node.js. Repository contributors who build the artifacts should use the [Python contributor workflow](../../../python/development.md). The installation includes a matching native runtime wheel and the `dsh` command. Normal SDK execution needs no system Node.js. Repository contributors who build the artifacts should use the [Python contributor workflow](../../../python/development.md).
## Run the checked-in example ## Run the checked-in example
Export the credential and, when needed, a compatible proxy endpoint: Export the credential and, when needed, a compatible proxy endpoint:
### Linux and macOS
```sh ```sh
export DEEPSEEK_API_KEY=sk-your-key-here export DEEPSEEK_API_KEY=sk-your-key-here
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1 # export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
``` ```
### Windows PowerShell
```powershell
$env:DEEPSEEK_API_KEY = "sk-your-key-here"
# $env:DEEPSEEK_BASE_URL = "http://127.0.0.1:8000/v1"
```
Run one task with explicit workspace and home paths: Run one task with explicit workspace and home paths:
### Linux and macOS
```sh ```sh
python examples/python-sdk-agent/minimal.py \ python examples/python-sdk-agent/minimal.py \
--workspace /absolute/path/to/disposable-workspace \ --workspace /absolute/path/to/disposable-workspace \
@@ -43,6 +66,16 @@ python examples/python-sdk-agent/minimal.py \
"Inspect the repository and fix the failing tests." "Inspect the repository and fix the failing tests."
``` ```
### Windows PowerShell
```powershell
python examples/python-sdk-agent/minimal.py `
--workspace C:\work\disposable-workspace `
--dsh-home C:\work\example-dsh-home `
--session-id example-001 `
"Inspect the repository and fix the failing tests."
```
The script prints the final assistant response. The selected home receives the generated `sdk-minimal` profile, installed plugins, and uncompressed JSONL session logs under `sessions/`. The example and SDK never silently read `~/.dsh`. The script prints the final assistant response. The selected home receives the generated `sdk-minimal` profile, installed plugins, and uncompressed JSONL session logs under `sessions/`. The example and SDK never silently read `~/.dsh`.
## Use the SDK in your program ## Use the SDK in your program
@@ -76,12 +109,22 @@ The SDK starts the bundled `dsh --profile sdk-minimal` process lazily and reuses
Use `dsh plugin` for dependencies and bundle layers that should persist in this home: Use `dsh plugin` for dependencies and bundle layers that should persist in this home:
### Linux and macOS
```sh ```sh
export DSH_HOME=/absolute/path/to/example-dsh-home export DSH_HOME=/absolute/path/to/example-dsh-home
dsh --profile sdk-minimal --dump-default-config >/dev/null dsh --profile sdk-minimal --dump-default-config >/dev/null
dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle
``` ```
### Windows PowerShell
```powershell
$env:DSH_HOME = "C:\work\example-dsh-home"
dsh --profile sdk-minimal --dump-default-config | Out-Null
dsh plugin --profile sdk-minimal add file:C:/work/my-plugin-bundle
```
The first command initializes the shipped standalone profile. The second forwards package management to `pnpm`, then records any installed package that exports a `dsh.bundle` layer. Install `pnpm` only for this management command; launching the installed SDK does not need it. Edit `$DSH_HOME/profiles/sdk-minimal/cordis.patch.yml` for persistent row changes, or pass patch files from Python for per-launch changes. The first command initializes the shipped standalone profile. The second forwards package management to `pnpm`, then records any installed package that exports a `dsh.bundle` layer. Install `pnpm` only for this management command; launching the installed SDK does not need it. Edit `$DSH_HOME/profiles/sdk-minimal/cordis.patch.yml` for persistent row changes, or pass patch files from Python for per-launch changes.
Another `profile` is valid when it includes `@deepseek-ai/dsh-sdk-app` or another JSON-RPC server row. Missing server rows, unresolved plugins, and invalid patches fail during startup instead of falling back to another composition. Another `profile` is valid when it includes `@deepseek-ai/dsh-sdk-app` or another JSON-RPC server row. Missing server rows, unresolved plugins, and invalid patches fail during startup instead of falling back to another composition.
+43
View File
@@ -14,6 +14,8 @@
## 安装 SDK ## 安装 SDK
### Linux 与 macOS
```sh ```sh
git clone https://github.com/deepseek-ai/deepseek-harness.git git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness cd deepseek-harness
@@ -22,19 +24,40 @@ python -m venv .venv
python -m pip install deepseek-harness-sdk python -m pip install deepseek-harness-sdk
``` ```
### Windows PowerShell
```powershell
git clone https://github.com/deepseek-ai/deepseek-harness.git
Set-Location deepseek-harness
py -3.10 -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install deepseek-harness-sdk
```
安装内容包含匹配的原生运行时 wheel 与 `dsh` 命令。普通 SDK 运行不需要系统 Node.js。需要构建产物的仓库贡献者应使用 [Python 贡献者工作流](../../../python/development.zh.md)。 安装内容包含匹配的原生运行时 wheel 与 `dsh` 命令。普通 SDK 运行不需要系统 Node.js。需要构建产物的仓库贡献者应使用 [Python 贡献者工作流](../../../python/development.zh.md)。
## 运行检入示例 ## 运行检入示例
导出凭据;使用兼容代理时再设置 endpoint: 导出凭据;使用兼容代理时再设置 endpoint:
### Linux 与 macOS
```sh ```sh
export DEEPSEEK_API_KEY=sk-your-key-here export DEEPSEEK_API_KEY=sk-your-key-here
# export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1 # export DEEPSEEK_BASE_URL=http://127.0.0.1:8000/v1
``` ```
### Windows PowerShell
```powershell
$env:DEEPSEEK_API_KEY = "sk-your-key-here"
# $env:DEEPSEEK_BASE_URL = "http://127.0.0.1:8000/v1"
```
使用显式 workspace 与 home 路径运行一个任务: 使用显式 workspace 与 home 路径运行一个任务:
### Linux 与 macOS
```sh ```sh
python examples/python-sdk-agent/minimal.py \ python examples/python-sdk-agent/minimal.py \
--workspace /absolute/path/to/disposable-workspace \ --workspace /absolute/path/to/disposable-workspace \
@@ -43,6 +66,16 @@ python examples/python-sdk-agent/minimal.py \
"Inspect the repository and fix the failing tests." "Inspect the repository and fix the failing tests."
``` ```
### Windows PowerShell
```powershell
python examples/python-sdk-agent/minimal.py `
--workspace C:\work\disposable-workspace `
--dsh-home C:\work\example-dsh-home `
--session-id example-001 `
"Inspect the repository and fix the failing tests."
```
脚本会打印最终 assistant 响应。所选 home 会保存生成的 `sdk-minimal` profile、已安装插件,以及 `sessions/` 下的未压缩 JSONL 会话日志。示例与 SDK 绝不会静默读取 `~/.dsh` 脚本会打印最终 assistant 响应。所选 home 会保存生成的 `sdk-minimal` profile、已安装插件,以及 `sessions/` 下的未压缩 JSONL 会话日志。示例与 SDK 绝不会静默读取 `~/.dsh`
## 在程序中使用 SDK ## 在程序中使用 SDK
@@ -76,12 +109,22 @@ SDK 会延迟启动内置的 `dsh --profile sdk-minimal` 进程,并复用到
需要在该 home 中持久保存依赖与 bundle 层时,使用 `dsh plugin` 需要在该 home 中持久保存依赖与 bundle 层时,使用 `dsh plugin`
### Linux 与 macOS
```sh ```sh
export DSH_HOME=/absolute/path/to/example-dsh-home export DSH_HOME=/absolute/path/to/example-dsh-home
dsh --profile sdk-minimal --dump-default-config >/dev/null dsh --profile sdk-minimal --dump-default-config >/dev/null
dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle dsh plugin --profile sdk-minimal add file:/absolute/path/to/my-plugin-bundle
``` ```
### Windows PowerShell
```powershell
$env:DSH_HOME = "C:\work\example-dsh-home"
dsh --profile sdk-minimal --dump-default-config | Out-Null
dsh plugin --profile sdk-minimal add file:C:/work/my-plugin-bundle
```
第一个命令初始化随附的独立 profile。第二个命令把包管理转发给 `pnpm`,然后记录所有导出 `dsh.bundle` 层的已安装包。只有执行此管理命令时才需要安装 `pnpm`;启动已安装 SDK 不需要它。持久配置项变更应编辑 `$DSH_HOME/profiles/sdk-minimal/cordis.patch.yml`;单次启动变更则从 Python 传入 patch 文件。 第一个命令初始化随附的独立 profile。第二个命令把包管理转发给 `pnpm`,然后记录所有导出 `dsh.bundle` 层的已安装包。只有执行此管理命令时才需要安装 `pnpm`;启动已安装 SDK 不需要它。持久配置项变更应编辑 `$DSH_HOME/profiles/sdk-minimal/cordis.patch.yml`;单次启动变更则从 Python 传入 patch 文件。
另一个 `profile` 只有包含 `@deepseek-ai/dsh-sdk-app` 或另一个 JSON-RPC server 配置项时才有效。缺失 server 配置项、无法解析的插件和非法 patch 会在启动时失败,不会回退到其他组合。 另一个 `profile` 只有包含 `@deepseek-ai/dsh-sdk-app` 或另一个 JSON-RPC server 配置项时才有效。缺失 server 配置项、无法解析的插件和非法 patch 会在启动时失败,不会回退到其他组合。
@@ -116,7 +116,12 @@ def resolve_bundled_launch_args(mode: str | None = None) -> tuple[str, ...]:
def _current_platform_tag() -> str: def _current_platform_tag() -> str:
plat = _PLATFORM_TAGS.get(sys.platform) plat = _PLATFORM_TAGS.get(sys.platform)
arch = _ARCH_TAGS.get(platform.machine().lower()) arch = _ARCH_TAGS.get(platform.machine().lower())
if plat is None or arch is None or (plat == "win" and arch != "x64"): if (
plat is None
or arch is None
or (plat == "win" and arch != "x64")
or (plat == "macos" and arch != "arm64")
):
raise FileNotFoundError( raise FileNotFoundError(
"no bundled DeepSeek Harness SDK runtime exists for this platform " "no bundled DeepSeek Harness SDK runtime exists for this platform "
f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: " f"(sys.platform={sys.platform!r}, machine={platform.machine()!r}); supported: "
+13
View File
@@ -137,3 +137,16 @@ def test_stage_runtime_copies_platform_payload(
assert (destination / "THIRD_PARTY_NOTICES.md").read_bytes() == ( assert (destination / "THIRD_PARTY_NOTICES.md").read_bytes() == (
ROOT / "THIRD_PARTY_NOTICES.md" ROOT / "THIRD_PARTY_NOTICES.md"
).read_bytes() ).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",
)
@@ -79,6 +79,14 @@ def test_current_platform_supports_windows_x64_only(monkeypatch: pytest.MonkeyPa
runtime._current_platform_tag() 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( def test_runtime_requires_ripgrep_sidecar(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None: ) -> None:
+4
View File
@@ -208,6 +208,10 @@ def stage_sdk(destination: Path, version: str) -> None:
def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None: def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None:
if executable.name != executable_name:
raise ValueError(
f"runtime executable must be named {executable_name}, got {executable.name}"
)
copy_package(ROOT / "python" / "sdk-runtime", destination) copy_package(ROOT / "python" / "sdk-runtime", destination)
stage_license_files(destination, include_notices=True) stage_license_files(destination, include_notices=True)
rewrite_version(destination / "pyproject.toml", version) rewrite_version(destination / "pyproject.toml", version)
+35 -22
View File
@@ -390,12 +390,19 @@ describe('Python release workflows', () => {
const manylinuxAddon = buildSteps.find(step => isRecord(step) && step.name === 'Rebuild Linux node-pty against manylinux 2.28') const manylinuxAddon = buildSteps.find(step => isRecord(step) && step.name === 'Rebuild Linux node-pty against manylinux 2.28')
const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS deployment target') const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS deployment target')
const manylinuxSmoke = buildSteps.find(step => isRecord(step) && step.name === 'Run wheel in a manylinux 2.28 container') const manylinuxSmoke = buildSteps.find(step => isRecord(step) && step.name === 'Run wheel in a manylinux 2.28 container')
const cleanVenv = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv') const cleanVenvPosix = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (POSIX)')
const installedKeyless = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests') const cleanVenvWindows = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (Windows)')
const realApiPreflight = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test') const installedKeylessPosix = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests (POSIX)')
const installedRealApi = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test') const installedKeylessWindows = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests (Windows)')
if (!isRecord(cleanVenv) || !isRecord(installedKeyless) || !isRecord(realApiPreflight) || !isRecord(installedRealApi)) { const realApiPreflightPosix = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test (POSIX)')
throw new TypeError('Python wheel builder must define installed-wheel keyless and real API steps') const realApiPreflightWindows = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test (Windows)')
const installedRealApiPosix = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (POSIX)')
const installedRealApiWindows = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (Windows)')
if (!isRecord(cleanVenvPosix) || !isRecord(cleanVenvWindows)
|| !isRecord(installedKeylessPosix) || !isRecord(installedKeylessWindows)
|| !isRecord(realApiPreflightPosix) || !isRecord(realApiPreflightWindows)
|| !isRecord(installedRealApiPosix) || !isRecord(installedRealApiWindows)) {
throw new TypeError('Python wheel builder must define native POSIX and Windows installed-wheel steps')
} }
expect(call.inputs).toHaveProperty('targets') expect(call.inputs).toHaveProperty('targets')
expect(call.inputs).toMatchObject({ expect(call.inputs).toMatchObject({
@@ -408,7 +415,7 @@ describe('Python release workflows', () => {
expect(workflow.concurrency).toMatchObject({ expect(workflow.concurrency).toMatchObject({
group: 'build-single-exe-${{ github.workflow }}-${{ github.ref }}', group: 'build-single-exe-${{ github.workflow }}-${{ github.ref }}',
}) })
expect(build.defaults).toMatchObject({ run: { shell: 'bash' } }) expect(build.defaults).toBeUndefined()
expect(plan.if).toContain('inputs.ci') expect(plan.if).toContain('inputs.ci')
expect(plan.if).toContain('inputs.release') expect(plan.if).toContain('inputs.release')
expect(JSON.stringify(plan.steps)).toContain('pep440_version') expect(JSON.stringify(plan.steps)).toContain('pep440_version')
@@ -423,6 +430,7 @@ describe('Python release workflows', () => {
expect(workflowJson).toContain('/work/dist-python/$RUNTIME_WHEEL') expect(workflowJson).toContain('/work/dist-python/$RUNTIME_WHEEL')
expect(workflowJson).not.toContain('--find-links dist-python') expect(workflowJson).not.toContain('--find-links dist-python')
expect(workflowJson).not.toContain('--find-links /work/dist-python') expect(workflowJson).not.toContain('--find-links /work/dist-python')
expect(workflowJson).not.toContain('cygpath')
expect(manylinuxAddon).toMatchObject({ if: "runner.os == 'Linux'" }) expect(manylinuxAddon).toMatchObject({ if: "runner.os == 'Linux'" })
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_x86_64') expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_x86_64')
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_aarch64') expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_aarch64')
@@ -434,27 +442,29 @@ describe('Python release workflows', () => {
expect(macosCheck).toMatchObject({ if: "runner.os == 'macOS'" }) expect(macosCheck).toMatchObject({ if: "runner.os == 'macOS'" })
expect(JSON.stringify(macosCheck)).toContain('scripts/check-macos-deployment-target.py') expect(JSON.stringify(macosCheck)).toContain('scripts/check-macos-deployment-target.py')
expect(JSON.stringify(macosCheck)).toContain('$EXE-spawn-helper') expect(JSON.stringify(macosCheck)).toContain('$EXE-spawn-helper')
expect(JSON.stringify(installedKeyless)).toContain('--scenario all') expect(JSON.stringify(installedKeylessPosix)).toContain('--scenario all')
expect(JSON.stringify(installedKeyless)).toContain('--installed-wheel') expect(JSON.stringify(installedKeylessPosix)).toContain('env -u PYTHONPATH')
expect(JSON.stringify(installedKeyless)).toContain('env -u PYTHONPATH') expect(JSON.stringify(installedKeylessWindows)).toContain('--scenario all --installed-wheel')
expect(JSON.stringify(installedKeyless)).toContain('-u DSH_RUNTIME_MODE') expect(installedKeylessWindows).toMatchObject({ if: "runner.os == 'Windows'", shell: 'pwsh' })
expect(JSON.stringify(cleanVenv)).toContain('Scripts/python.exe') expect(cleanVenvWindows).toMatchObject({ if: "runner.os == 'Windows'", shell: 'pwsh' })
expect(realApiPreflight).toMatchObject({ expect(JSON.stringify(cleanVenvWindows)).toContain('Scripts\\\\python.exe')
expect(realApiPreflightPosix).toMatchObject({
env: { DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}' }, env: { DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}' },
}) })
expect(String(realApiPreflight.if)).toContain('inputs.ci') expect(String(realApiPreflightPosix.if)).toContain('inputs.ci')
expect(String(realApiPreflight.if)).toContain('head.repo.fork') expect(String(realApiPreflightPosix.if)).toContain('head.repo.fork')
expect(String(realApiPreflight.if)).toContain('dependabot[bot]') expect(String(realApiPreflightPosix.if)).toContain('dependabot[bot]')
expect(installedRealApi).toMatchObject({ expect(realApiPreflightWindows).toMatchObject({ shell: 'pwsh' })
expect(installedRealApiPosix).toMatchObject({
env: { env: {
DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}', DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}',
DEEPSEEK_BASE_URL: 'https://api.deepseek.com', DEEPSEEK_BASE_URL: 'https://api.deepseek.com',
}, },
}) })
expect(installedRealApi.if).toBe(realApiPreflight.if) expect(JSON.stringify(installedRealApiPosix)).toContain('--scenario sdk-live')
expect(JSON.stringify(installedRealApi)).toContain('--scenario sdk-live') expect(JSON.stringify(installedRealApiPosix)).toContain('-u DSH_RUNTIME_MODE')
expect(JSON.stringify(installedRealApi)).toContain('--installed-wheel') expect(installedRealApiWindows).toMatchObject({ shell: 'pwsh' })
expect(JSON.stringify(installedRealApi)).toContain('-u DSH_RUNTIME_MODE') expect(JSON.stringify(installedRealApiWindows)).toContain('--scenario sdk-live --installed-wheel')
expect(manylinuxSmoke).toMatchObject({ if: "runner.os == 'Linux'" }) expect(manylinuxSmoke).toMatchObject({ if: "runner.os == 'Linux'" })
expect(JSON.stringify(manylinuxSmoke)).toContain('-e DSH_TELEMETRY_DISABLED') expect(JSON.stringify(manylinuxSmoke)).toContain('-e DSH_TELEMETRY_DISABLED')
}) })
@@ -481,12 +491,15 @@ describe('Python release workflows', () => {
const workflow = loadWorkflow('.gitlab-ci.yml') const workflow = loadWorkflow('.gitlab-ci.yml')
const windows = workflow['runtime-windows-x64'] const windows = workflow['runtime-windows-x64']
const publish = workflow['publish-python'] const publish = workflow['publish-python']
if (!isRecord(windows) || !Array.isArray(windows.script) || !isRecord(publish) || !Array.isArray(publish.needs)) { if (!isRecord(windows) || !Array.isArray(windows.before_script) || !Array.isArray(windows.script)
|| !isRecord(publish) || !Array.isArray(publish.needs)) {
throw new TypeError('GitLab CI must define the Windows runtime and aggregate publication jobs') throw new TypeError('GitLab CI must define the Windows runtime and aggregate publication jobs')
} }
expect(windows.tags).toEqual(['windows-x64']) expect(windows.tags).toEqual(['windows-x64'])
expect(windows.variables).toMatchObject({ PKG_TARGET: 'node24-win-x64', PLATFORM: 'win-x64' }) expect(windows.variables).toMatchObject({ PKG_TARGET: 'node24-win-x64', PLATFORM: 'win-x64' })
expect(JSON.stringify(windows.before_script)).toContain('.ci-python\\\\Scripts')
expect(JSON.stringify(windows.before_script)).toContain('[IO.Path]::PathSeparator')
expect(JSON.stringify(windows.script)).toContain('win_amd64.whl') expect(JSON.stringify(windows.script)).toContain('win_amd64.whl')
expect(JSON.stringify(windows.script)).toContain('--scenario all --installed-wheel') expect(JSON.stringify(windows.script)).toContain('--scenario all --installed-wheel')
expect(publish.needs).toContainEqual({ job: 'runtime-windows-x64', artifacts: true }) expect(publish.needs).toContainEqual({ job: 'runtime-windows-x64', artifacts: true })