From 7dd0de83c2436326e6d9a2099189b99d74271faa Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Tue, 18 Aug 2026 21:16:42 +0800 Subject: [PATCH 1/2] feat: add DeepSeek Harness model plugin --- README.md | 1 + packages/bub-dsh/README.md | 85 +++++++ packages/bub-dsh/pyproject.toml | 20 ++ packages/bub-dsh/src/bub_dsh/__init__.py | 1 + packages/bub-dsh/src/bub_dsh/plugin.py | 184 ++++++++++++++ packages/bub-dsh/src/bub_dsh/py.typed | 1 + packages/bub-dsh/tests/test_plugin.py | 308 +++++++++++++++++++++++ pyproject.toml | 2 + uv.lock | 40 +++ 9 files changed, 642 insertions(+) create mode 100644 packages/bub-dsh/README.md create mode 100644 packages/bub-dsh/pyproject.toml create mode 100644 packages/bub-dsh/src/bub_dsh/__init__.py create mode 100644 packages/bub-dsh/src/bub_dsh/plugin.py create mode 100644 packages/bub-dsh/src/bub_dsh/py.typed create mode 100644 packages/bub-dsh/tests/test_plugin.py diff --git a/README.md b/README.md index 86b264e..111fd6d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Below is the list of packages currently included in this repository. | [`bub-tapestore-sqlite`](./packages/bub-tapestore-sqlite/README.md) | | Provides a SQLite-backed tape store for Bub conversation history. | | [`bub-discord`](./packages/bub-discord/README.md) | [![PyPI version](https://img.shields.io/pypi/v/bub-discord)](https://pypi.org/project/bub-discord/) | Provides a Discord channel adapter for Bub message IO. | | [`bub-dingtalk`](./packages/bub-dingtalk/README.md) | | Provides a DingTalk Stream Mode channel adapter for Bub message IO. | +| [`bub-dsh`](./packages/bub-dsh/README.md) | | Provides a `run_model` hook backed by the DeepSeek Harness Python SDK, with runtime-local session reuse. | | [`bub-extism`](./packages/bub-extism/README.md) | | Bridges selected Bub hooks to Extism WebAssembly plugins so extensions can be written in any Extism PDK language. | | [`bub-github-copilot`](./packages/bub-github-copilot/README.md) | | Provides a `run_model` hook backed by the GitHub Copilot SDK, plus `bub login github` device-flow login commands. | | [`bub-kimi`](./packages/bub-kimi/README.md) | | Provides a `run_model` hook backed by the Kimi CLI, including persisted session resume support and temporary Bub skill wiring. | diff --git a/packages/bub-dsh/README.md b/packages/bub-dsh/README.md new file mode 100644 index 0000000..627a801 --- /dev/null +++ b/packages/bub-dsh/README.md @@ -0,0 +1,85 @@ +# bub-dsh + +DeepSeek Harness Python SDK-backed `run_model` plugin for `bub`. + +## What It Provides + +- Bub plugin entry point: `dsh` +- A `run_model` hook backed by the official `deepseek-harness-sdk` +- Runtime-local DeepSeek Harness session mapping for each Bub `session_id` + +## Installation + +Install using Bub's plugin manager: + +```bash +bub install bub-dsh@main +``` + +Install directly from GitHub: + +```bash +uv pip install "git+https://github.com/bubbuild/bub-contrib.git#subdirectory=packages/bub-dsh" +``` + +## Authentication + +The plugin uses Bub's root model configuration. For example: + +```bash +export BUB_MODEL=dsh:deepseek-v4-flash +export BUB_MAX_TOKENS=49152 +export BUB_DSH_API_KEY=sk-your-key +export BUB_DSH_API_BASE=https://api.deepseek.com +``` + +`BUB_API_KEY` and `BUB_API_BASE` are also supported by Bub for non-provider-specific +values. + +The root provider `dsh` maps to the DeepSeek Harness provider route +`deepseek-official`. An unqualified root model defaults to `dsh`; any other explicit +provider is passed to DeepSeek Harness unchanged. + +## Configuration + +Environment variables use the `BUB_DSH_` prefix: + +- `BUB_DSH_REQUEST_TIMEOUT_SECONDS`: optional positive JSON-RPC request timeout +- `BUB_DSH_SHUTDOWN_TIMEOUT_SECONDS`: runtime shutdown timeout, default `1` +- `BUB_DSH_CORDIS`: optional custom Cordis configuration path +- `BUB_DSH_SESSION_ROOT`: session storage directory, default `/dsh` + +## Runtime Behavior + +The DeepSeek Harness SDK is synchronous, so the plugin runs it in a worker thread and +does not block Bub's async event loop. Runtime processes are reused until Bub exits. +Calls for the same Bub session reuse one runtime-local session id, preserving the +Harness conversation and persistent shell state without colliding with logs left by a +previous Bub process. Session storage defaults to `bub.home / "dsh"`. + +String prompts and structured lists of JSON content blocks are passed to +`DeepSeekHarness.run()` unchanged. + +When a run finishes with `finish_reason="error"`, the plugin raises +`DshRunError` instead of returning an empty response. The Harness error payload is +available unchanged through the exception's `error` attribute. + +The bundled runtime includes local shell tools and can modify files visible from the +Bub workspace. Run it only with filesystem permissions appropriate for the task. + +## Current Limitations + +- DeepSeek Harness is in developer preview and may introduce breaking SDK changes. +- Restarting Bub creates new Harness session ids. The current SDK cannot restore a + persisted session into a new runtime process, although existing logs remain on disk. +- Cancelling the Bub coroutine cannot forcibly stop work already running in Python's + worker thread. The current SDK has no whole-turn timeout while it waits for the + runtime to become idle. + +## Validation + +```bash +uv run pytest packages/bub-dsh/tests +uv run ruff check packages/bub-dsh +uv sync +``` diff --git a/packages/bub-dsh/pyproject.toml b/packages/bub-dsh/pyproject.toml new file mode 100644 index 0000000..a1575d8 --- /dev/null +++ b/packages/bub-dsh/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "bub-dsh" +version = "0.1.0" +description = "DeepSeek Harness SDK-backed run_model plugin for Bub" +readme = "README.md" +authors = [ + { name = "Frost Ming", email = "me@frostming.com" } +] +requires-python = ">=3.12" +dependencies = [ + "deepseek-harness-sdk>=0.1.0rc7,<0.2.0", + "pydantic-settings>=2.13.1", +] + +[project.entry-points.bub] +dsh = "bub_dsh.plugin" + +[build-system] +requires = ["uv_build>=0.10.4,<0.11.0"] +build-backend = "uv_build" diff --git a/packages/bub-dsh/src/bub_dsh/__init__.py b/packages/bub-dsh/src/bub_dsh/__init__.py new file mode 100644 index 0000000..687f75c --- /dev/null +++ b/packages/bub-dsh/src/bub_dsh/__init__.py @@ -0,0 +1 @@ +"""DeepSeek Harness integration for Bub.""" diff --git a/packages/bub-dsh/src/bub_dsh/plugin.py b/packages/bub-dsh/src/bub_dsh/plugin.py new file mode 100644 index 0000000..924f115 --- /dev/null +++ b/packages/bub-dsh/src/bub_dsh/plugin.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +import atexit +import asyncio +import threading +import uuid +from pathlib import Path + +import bub +from bub import hookimpl +from bub.builtin.settings import AgentSettings +from bub.turn import TurnState +from deepseek_harness import DeepSeekHarness, RunResult +from pydantic import Field +from pydantic_settings import SettingsConfigDict + +ROOT_PROVIDER = "dsh" +DEFAULT_HARNESS_PROVIDER = "deepseek-official" + + +class _HarnessRuntime: + def __init__(self, kwargs: dict[str, object]) -> None: + self.harness = DeepSeekHarness(**kwargs) + self.lock = threading.Lock() + self.session_ids: dict[str, str] = {} + + def session_id(self, bub_session_id: str) -> str: + return self.session_ids.setdefault( + bub_session_id, + f"bub-{uuid.uuid4().hex}", + ) + + +_runtimes: dict[tuple[tuple[str, object], ...], _HarnessRuntime] = {} +_runtimes_lock = threading.Lock() + + +class DshRunError(RuntimeError): + """Error reported by a completed DeepSeek Harness run.""" + + def __init__(self, error: dict[str, object] | None = None) -> None: + self.error = error + message = "DeepSeek Harness run failed" + if error: + detail = error.get("message") + code = error.get("code") + if isinstance(detail, str) and detail: + message = f"{message}: {detail}" + if isinstance(code, str) and code: + message = f"{message} [{code}]" + super().__init__(message) + + +@bub.config(name="dsh") +class DshSettings(bub.Settings): + """Configuration for the DeepSeek Harness Bub plugin.""" + + model_config = SettingsConfigDict(env_prefix="BUB_DSH_", extra="ignore") + + request_timeout_seconds: float | None = Field(default=None, gt=0) + shutdown_timeout_seconds: float = Field(default=1.0, gt=0) + cordis: str | None = None + session_root: Path = Field(default_factory=lambda: bub.home / "dsh") + + +def workspace_from_state(state: TurnState) -> Path: + raw = state.get("_runtime_workspace") + if isinstance(raw, str) and raw.strip(): + return Path(raw).expanduser().resolve() + return Path.cwd().resolve() + + +def _split_root_model(model: str) -> tuple[str, str]: + provider, separator, model_id = model.partition(":") + if not separator: + provider, model_id = ROOT_PROVIDER, provider + provider = provider.strip() + model_id = model_id.strip() + if not provider or not model_id: + raise RuntimeError(f"Invalid Bub model identifier: {model!r}") + return provider, model_id + + +def _provider_value( + value: str | dict[str, str] | None, + provider: str, +) -> str | None: + if isinstance(value, dict): + return value.get(provider) + return value + + +def _harness_kwargs(workspace: Path) -> dict[str, object]: + settings = bub.ensure_config(DshSettings) + root_settings = bub.ensure_config(AgentSettings) + root_provider, model_id = _split_root_model(root_settings.model) + kwargs: dict[str, object] = { + "provider": ( + DEFAULT_HARNESS_PROVIDER + if root_provider == ROOT_PROVIDER + else root_provider + ), + "model": model_id, + "max_tokens": root_settings.max_tokens, + "cwd": str(workspace), + "session_root": str(settings.session_root.expanduser().resolve()), + "request_timeout_seconds": settings.request_timeout_seconds, + "shutdown_timeout_seconds": settings.shutdown_timeout_seconds, + } + if settings.cordis: + kwargs["cordis"] = settings.cordis + if api_base := _provider_value(root_settings.api_base, root_provider): + kwargs["base_url"] = api_base + if api_key := _provider_value(root_settings.api_key, root_provider): + kwargs["api_key"] = api_key + return kwargs + + +def _runtime_for(workspace: Path) -> _HarnessRuntime: + kwargs = _harness_kwargs(workspace) + key = tuple(sorted(kwargs.items())) + with _runtimes_lock: + runtime = _runtimes.get(key) + if runtime is None: + runtime = _HarnessRuntime(kwargs) + _runtimes[key] = runtime + return runtime + + +def _close_runtimes() -> None: + with _runtimes_lock: + runtimes = list(_runtimes.values()) + _runtimes.clear() + for runtime in runtimes: + runtime.harness.close() + + +atexit.register(_close_runtimes) + + +def _run_with_dsh( + prompt: str | list[dict], + *, + session_id: str, + workspace: Path, +) -> str: + runtime = _runtime_for(workspace) + with runtime.lock: + result: RunResult = runtime.harness.run( + prompt, + session_id=runtime.session_id(session_id), + ) + if result.finish_reason == "error": + raise DshRunError(_run_result_error(result)) + return result.final_response + + +def _run_result_error(result: RunResult) -> dict[str, object] | None: + for event in reversed(result.events): + if event.get("type") != "turn/end": + continue + data = event.get("data") + if not isinstance(data, dict): + continue + reason = data.get("reason") + if not isinstance(reason, dict) or reason.get("kind") != "error": + continue + error = reason.get("error") + if isinstance(error, dict): + return error + return None + + +@hookimpl +async def run_model(prompt: str | list[dict], session_id: str, state: TurnState) -> str: + return await asyncio.to_thread( + _run_with_dsh, + prompt, + session_id=session_id, + workspace=workspace_from_state(state), + ) + + +__all__ = ["DshRunError", "DshSettings", "run_model"] diff --git a/packages/bub-dsh/src/bub_dsh/py.typed b/packages/bub-dsh/src/bub_dsh/py.typed new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/packages/bub-dsh/src/bub_dsh/py.typed @@ -0,0 +1 @@ + diff --git a/packages/bub-dsh/tests/test_plugin.py b/packages/bub-dsh/tests/test_plugin.py new file mode 100644 index 0000000..7373f01 --- /dev/null +++ b/packages/bub-dsh/tests/test_plugin.py @@ -0,0 +1,308 @@ +from __future__ import annotations + +import asyncio +from pathlib import Path + +import pytest + +from bub_dsh import plugin + + +@pytest.fixture(autouse=True) +def close_dsh_runtimes(): + plugin._close_runtimes() + yield + plugin._close_runtimes() + + +class FakeRootSettings: + def __init__( + self, + *, + model: str = "dsh:deepseek-v4-flash", + api_key: str | dict[str, str] | None = None, + api_base: str | dict[str, str] | None = None, + max_tokens: int = 16_384, + ) -> None: + self.model = model + self.api_key = api_key + self.api_base = api_base + self.max_tokens = max_tokens + + +def test_run_model_uses_dsh_sdk( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + calls: dict[str, object] = {} + + class FakeHarness: + def __init__(self, **kwargs: object) -> None: + calls["kwargs"] = kwargs + + def close(self) -> None: + calls["closed"] = True + + def run(self, prompt: str | list[dict], *, session_id: str): + calls["run"] = {"prompt": prompt, "session_id": session_id} + return type( + "Result", + (), + { + "final_response": "assistant-output", + "finish_reason": "completed", + "events": [], + }, + )() + + monkeypatch.setattr(plugin, "DeepSeekHarness", FakeHarness) + dsh_settings = plugin.DshSettings( + request_timeout_seconds=30, + shutdown_timeout_seconds=2, + session_root=tmp_path / "sessions", + ) + root_settings = FakeRootSettings( + api_key={"dsh": "secret", "openai": "other-secret"}, + api_base={ + "dsh": "https://deepseek.example/v1", + "openai": "https://openai.example/v1", + }, + max_tokens=8192, + ) + monkeypatch.setattr( + plugin.bub, + "ensure_config", + lambda settings_type: ( + dsh_settings if settings_type is plugin.DshSettings else root_settings + ), + ) + + result = asyncio.run( + plugin.run_model( + [{"type": "text", "text": "hello"}, {"type": "text", "text": "world"}], + "telegram:42", + {"_runtime_workspace": str(tmp_path)}, + ) + ) + + assert result == "assistant-output" + run_call = calls["run"] + assert isinstance(run_call, dict) + assert run_call["prompt"] == [ + {"type": "text", "text": "hello"}, + {"type": "text", "text": "world"}, + ] + assert isinstance(run_call["session_id"], str) + assert run_call["session_id"].startswith("bub-") + assert calls["kwargs"] == { + "provider": "deepseek-official", + "model": "deepseek-v4-flash", + "cwd": str(tmp_path), + "session_root": str(tmp_path / "sessions"), + "request_timeout_seconds": 30.0, + "shutdown_timeout_seconds": 2.0, + "max_tokens": 8192, + "base_url": "https://deepseek.example/v1", + "api_key": "secret", + } + + +def test_run_model_forwards_structured_prompt_unchanged( + monkeypatch: pytest.MonkeyPatch, +) -> None: + prompt = [ + {"type": "text", "text": "describe this"}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,AQ=="}}, + ] + captured: dict[str, object] = {} + + def fake_run_with_dsh( + value: str | list[dict], *, session_id: str, workspace: Path + ) -> str: + captured.update( + prompt=value, + session_id=session_id, + workspace=workspace, + ) + return "assistant-output" + + monkeypatch.setattr(plugin, "_run_with_dsh", fake_run_with_dsh) + + result = asyncio.run(plugin.run_model(prompt, "session-1", {})) + + assert result == "assistant-output" + assert captured["prompt"] is prompt + assert captured["session_id"] == "session-1" + + +def test_run_model_exposes_harness_error( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + harness_error = { + "message": "model failed", + "code": "UNKNOWN", + "retryable": False, + } + + class FakeHarness: + instances = 0 + + def __init__(self, **kwargs: object) -> None: + self.session_ids: list[str] = [] + FakeHarness.instances += 1 + + def close(self) -> None: + pass + + def run(self, prompt: str | list[dict], *, session_id: str): + self.session_ids.append(session_id) + return type( + "Result", + (), + { + "final_response": "", + "finish_reason": "error", + "events": [ + { + "type": "turn/end", + "data": { + "turn": 1, + "reason": { + "kind": "error", + "error": harness_error, + }, + }, + } + ], + }, + )() + + monkeypatch.setattr(plugin, "DeepSeekHarness", FakeHarness) + dsh_settings = plugin.DshSettings(session_root=tmp_path / "sessions") + root_settings = FakeRootSettings() + monkeypatch.setattr( + plugin.bub, + "ensure_config", + lambda settings_type: ( + dsh_settings if settings_type is plugin.DshSettings else root_settings + ), + ) + + with pytest.raises(plugin.DshRunError) as exc_info: + asyncio.run(plugin.run_model("hello", "session-error", {})) + + assert exc_info.value.error == harness_error + assert str(exc_info.value) == "DeepSeek Harness run failed: model failed [UNKNOWN]" + + +def test_run_model_reuses_runtime_and_internal_session_id( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + calls: list[str] = [] + instances = 0 + + class FakeHarness: + def __init__(self, **kwargs: object) -> None: + nonlocal instances + instances += 1 + + def close(self) -> None: + pass + + def run(self, prompt: str | list[dict], *, session_id: str): + calls.append(session_id) + return type( + "Result", + (), + { + "final_response": "assistant-output", + "finish_reason": "completed", + "events": [], + }, + )() + + monkeypatch.setattr(plugin, "DeepSeekHarness", FakeHarness) + dsh_settings = plugin.DshSettings(session_root=tmp_path / "sessions") + root_settings = FakeRootSettings() + monkeypatch.setattr( + plugin.bub, + "ensure_config", + lambda settings_type: ( + dsh_settings if settings_type is plugin.DshSettings else root_settings + ), + ) + + asyncio.run(plugin.run_model("first", "cli_session", {})) + asyncio.run(plugin.run_model("second", "cli_session", {})) + + assert instances == 1 + assert len(calls) == 2 + assert calls[0] == calls[1] + assert calls[0].startswith("bub-") + + +def test_run_model_forwards_internal_command_like_prompt( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def fake_run_with_dsh( + value: str | list[dict], *, session_id: str, workspace: Path + ) -> str: + captured["prompt"] = value + return "assistant-output" + + monkeypatch.setattr(plugin, "_run_with_dsh", fake_run_with_dsh) + + result = asyncio.run(plugin.run_model(",help", "session-1", {})) + + assert result == "assistant-output" + assert captured["prompt"] == ",help" + + +def test_harness_kwargs_omits_optional_settings( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + bub_home = tmp_path / "bub-home" + monkeypatch.setattr(plugin.bub, "home", bub_home) + dsh_settings = plugin.DshSettings() + root_settings = FakeRootSettings(model="deepseek-v4-flash") + monkeypatch.setattr( + plugin.bub, + "ensure_config", + lambda settings_type: ( + dsh_settings if settings_type is plugin.DshSettings else root_settings + ), + ) + + kwargs = plugin._harness_kwargs(tmp_path / "workspace") + + assert kwargs["provider"] == "deepseek-official" + assert kwargs["model"] == "deepseek-v4-flash" + assert kwargs["session_root"] == str(bub_home / "dsh") + assert kwargs["max_tokens"] == 16_384 + assert "api_key" not in kwargs + assert "base_url" not in kwargs + + +def test_harness_kwargs_forwards_other_root_provider( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + dsh_settings = plugin.DshSettings(session_root=tmp_path / "sessions") + root_settings = FakeRootSettings(model="openai:gpt-5") + monkeypatch.setattr( + plugin.bub, + "ensure_config", + lambda settings_type: ( + dsh_settings if settings_type is plugin.DshSettings else root_settings + ), + ) + + kwargs = plugin._harness_kwargs(tmp_path) + + assert kwargs["provider"] == "openai" + assert kwargs["model"] == "gpt-5" diff --git a/pyproject.toml b/pyproject.toml index 79fe6e4..cc3c39c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "bub-cursor", "bub-discord", "bub-dingtalk", + "bub-dsh", "bub-extism", "bub-github-copilot", "bub-kimi", @@ -43,6 +44,7 @@ bub-context-budget = { workspace = true } bub-cursor = { workspace = true } bub-discord = { workspace = true } bub-dingtalk = { workspace = true } +bub-dsh = { workspace = true } bub-extism = { workspace = true } bub-github-copilot = { workspace = true } bub-kimi = { workspace = true } diff --git a/uv.lock b/uv.lock index 09868ec..64e3b9b 100644 --- a/uv.lock +++ b/uv.lock @@ -16,6 +16,7 @@ members = [ "bub-cursor", "bub-dingtalk", "bub-discord", + "bub-dsh", "bub-extism", "bub-feishu", "bub-github-copilot", @@ -431,6 +432,7 @@ dependencies = [ { name = "bub-cursor" }, { name = "bub-dingtalk" }, { name = "bub-discord" }, + { name = "bub-dsh" }, { name = "bub-extism" }, { name = "bub-feishu" }, { name = "bub-github-copilot" }, @@ -470,6 +472,7 @@ requires-dist = [ { name = "bub-cursor", editable = "packages/bub-cursor" }, { name = "bub-dingtalk", editable = "packages/bub-dingtalk" }, { name = "bub-discord", editable = "packages/bub-discord" }, + { name = "bub-dsh", editable = "packages/bub-dsh" }, { name = "bub-extism", editable = "packages/bub-extism" }, { name = "bub-feishu", editable = "packages/bub-feishu" }, { name = "bub-github-copilot", editable = "packages/bub-github-copilot" }, @@ -535,6 +538,21 @@ dependencies = [ [package.metadata] requires-dist = [{ name = "discord-py", specifier = ">=2.7.1" }] +[[package]] +name = "bub-dsh" +version = "0.1.0" +source = { editable = "packages/bub-dsh" } +dependencies = [ + { name = "deepseek-harness-sdk" }, + { name = "pydantic-settings" }, +] + +[package.metadata] +requires-dist = [ + { name = "deepseek-harness-sdk", specifier = ">=0.1.0rc7,<0.2.0" }, + { name = "pydantic-settings", specifier = ">=2.13.1" }, +] + [[package]] name = "bub-extism" version = "0.1.0" @@ -1100,6 +1118,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/1d/f6c80f82a42c69ff5a86c2580e789133de387d067210c7c46677618a2ca7/cyclopts-4.14.1-py3-none-any.whl", hash = "sha256:b705fdaa3277554a5da03e9bb5ff96a44d204a1afe3099f1b56e1586488bbfd4", size = 214925, upload-time = "2026-05-19T19:49:23.559Z" }, ] +[[package]] +name = "deepseek-harness-runtime-bin" +version = "0.1.0rc7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6e/c7d6992c148312a9abee04115802a44eb474d9dd5464cc6856b14518bc8a/deepseek_harness_runtime_bin-0.1.0rc7-py3-none-macosx_14_0_arm64.whl", hash = "sha256:39ae51dec905c496ede69250d51b02a424a6c96829feb2ad7e01b3cec97a3255", size = 52067269, upload-time = "2026-08-18T02:04:29.222Z" }, + { url = "https://files.pythonhosted.org/packages/10/64/86796e0394b577741850c6818e8dad8e13afc38c9b4692c7b4738045916f/deepseek_harness_runtime_bin-0.1.0rc7-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:de4e2571d3cd1c521ee22c754460dd251b45bd91a033876f554d0eab9b2843a1", size = 56534076, upload-time = "2026-08-18T02:04:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/91501f0f08949fbc592f790a1a1a327ff781fe35138765eac38afb77c433/deepseek_harness_runtime_bin-0.1.0rc7-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:bf26f76a8cab7bedcce3a060b81d19ec1cabd8bd261c40b47381d630742797a7", size = 56796555, upload-time = "2026-08-18T02:04:35.677Z" }, +] + +[[package]] +name = "deepseek-harness-sdk" +version = "0.1.0rc7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deepseek-harness-runtime-bin" }, + { name = "pydantic" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/5e/86d118a15ad0f5b42933fb7162420b60b7cc53a4739da272a5204501e009/deepseek_harness_sdk-0.1.0rc7-py3-none-any.whl", hash = "sha256:5327d60659d8802442d2c589c89c3528cf18eb07b5d698059c833eb6b853b7d4", size = 12881, upload-time = "2026-08-18T02:05:09.933Z" }, +] + [[package]] name = "dingtalk-stream" version = "0.24.3" From f9e0b3c43a1e5ac6f4f6536bef1caad1b6310b3c Mon Sep 17 00:00:00 2001 From: frostming Date: Tue, 18 Aug 2026 21:24:57 +0800 Subject: [PATCH 2/2] test: avoid leaking Bub home override --- packages/bub-dsh/tests/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bub-dsh/tests/test_plugin.py b/packages/bub-dsh/tests/test_plugin.py index 7373f01..c8de750 100644 --- a/packages/bub-dsh/tests/test_plugin.py +++ b/packages/bub-dsh/tests/test_plugin.py @@ -267,7 +267,7 @@ def test_harness_kwargs_omits_optional_settings( tmp_path: Path, ) -> None: bub_home = tmp_path / "bub-home" - monkeypatch.setattr(plugin.bub, "home", bub_home) + monkeypatch.setenv("BUB_HOME", str(bub_home)) dsh_settings = plugin.DshSettings() root_settings = FakeRootSettings(model="deepseek-v4-flash") monkeypatch.setattr(