Merge latest master into Office progressive skills
This commit is contained in:
commit
b6fc04f403
1458 files changed
+29668
-5360
No files matched your search
@@ -9,8 +9,8 @@
|
||||
en: ddf4dabea526cef1
|
||||
zh: 6e31cab10d2f3ed8
|
||||
/deepseek-harness-python-sdk/start-a-runtime:
|
||||
en: 9a069c1a7cbb0779
|
||||
zh: 7f625a802cd1e9ea
|
||||
en: 5fceaffcc35039c6
|
||||
zh: 1d87e3defe7e875b
|
||||
/deepseek-harness-python-sdk/customize-plugins:
|
||||
en: d32ee602f121e6af
|
||||
zh: fdea4487688e5774
|
||||
|
||||
@@ -50,6 +50,14 @@ print(result.final_response)
|
||||
|
||||
`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`, optional `reasoning_effort`, 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.
|
||||
|
||||
`harness.resume_session(saved_session_id)` restores persisted history and returns a `Session` ready for `run()`. Save the original session id and reuse its `dsh_home` and workspace. Restoration preserves the stored directory, repairs an interrupted final turn, and sends no new input. Missing, unreadable, incompatible, or occupied sessions raise without creating a replacement. `start_session(id)` only opens a lazy handle; it does not request persisted restoration. This API does not restore shell, browser, or other external processes.
|
||||
|
||||
```py
|
||||
with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
|
||||
session = harness.resume_session(saved_session_id)
|
||||
result = session.run("Continue the task.")
|
||||
```
|
||||
|
||||
## Customize plugins
|
||||
|
||||
Persistent customization belongs to a `dsh` profile. Initialize the shipped SDK profile and install an external bundle with the runtime wheel's `dsh` command:
|
||||
|
||||
@@ -50,6 +50,14 @@ print(result.final_response)
|
||||
|
||||
`DeepSeekHarness` 延迟启动运行时,并在调用 `close()` 或退出上下文管理器前复用该进程。首次 profile 握手通过 `initialize_timeout_seconds` 使用独立的 30 秒默认上限;普通轮次在未设置 `request_timeout_seconds` 时仍不设上限。超时诊断会指明所选 profile,并包含保留的运行时诊断。`cwd` 是 agent workspace;`runtime_cwd` 独立选择子进程工作目录。两者都会在启动前转成绝对路径。`provider`、`model`、可选的 `reasoning_effort` 和可选的正整数 `max_tokens` 通过 JSON-RPC 初始化发送。`base_url` 与 `api_key` 会显式覆盖子进程环境中的 `DEEPSEEK_BASE_URL` 与 `DEEPSEEK_API_KEY`。
|
||||
|
||||
`harness.resume_session(saved_session_id)` 恢复持久化历史,并返回可调用 `run()` 的 `Session`。请保存原会话 id,并沿用其 `dsh_home` 和工作区。恢复会保留存储的目录、修复最后一个被中断的轮次,且不发送新输入。会话缺失、不可读、不兼容或被占用时会抛出异常,不创建替代会话。`start_session(id)` 只打开惰性句柄,不会请求持久化恢复。此 API 不恢复 shell、浏览器或其他外部进程。
|
||||
|
||||
```py
|
||||
with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
|
||||
session = harness.resume_session(saved_session_id)
|
||||
result = session.run("Continue the task.")
|
||||
```
|
||||
|
||||
## 自定义插件
|
||||
|
||||
持久自定义属于 `dsh` profile。使用运行时 wheel 包提供的 `dsh` 命令初始化随附的 SDK profile,并安装外部 bundle:
|
||||
|
||||
@@ -121,6 +121,15 @@ class DeepSeekHarness:
|
||||
self.start()
|
||||
return Session(self, session_id or f"session-{uuid.uuid4().hex}")
|
||||
|
||||
def resume_session(self, session_id: str) -> "Session":
|
||||
"""Restore persisted history from the configured Harness home without sending input.
|
||||
|
||||
Missing, unreadable, incompatible, or occupied sessions raise instead of creating a new session.
|
||||
"""
|
||||
self.start()
|
||||
self._client.resume_session(session_id)
|
||||
return Session(self, session_id)
|
||||
|
||||
def run(
|
||||
self,
|
||||
input: str | list[JsonObject],
|
||||
|
||||
@@ -14,7 +14,7 @@ from typing import Callable, TypeAlias, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .errors import JsonRpcError, TransportClosedError
|
||||
from .errors import JsonRpcError, SdkProtocolError, TransportClosedError
|
||||
from .models import IncomingRequest, InitializeResponse, JsonObject, JsonValue, Notification
|
||||
|
||||
ModelT = TypeVar("ModelT", bound=BaseModel)
|
||||
@@ -98,7 +98,7 @@ class HarnessClient:
|
||||
return
|
||||
shutdown_completed = False
|
||||
try:
|
||||
self.request("shutdown", None, response_model=_ShutdownResponse, timeout_seconds=self.config.shutdown_timeout_seconds)
|
||||
self.request("shutdown", None, response_model=_EmptyResponse, timeout_seconds=self.config.shutdown_timeout_seconds)
|
||||
shutdown_completed = True
|
||||
except Exception as exc:
|
||||
self._stderr_lines.append(f"shutdown request failed: {exc}")
|
||||
@@ -188,6 +188,14 @@ class HarnessClient:
|
||||
)
|
||||
return response.messageId
|
||||
|
||||
def resume_session(self, session_id: str) -> None:
|
||||
"""Restore a persisted session without sending a prompt; propagate restoration failures."""
|
||||
self.request(
|
||||
"session/resume",
|
||||
{"sessionId": session_id},
|
||||
response_model=_EmptyResponse,
|
||||
)
|
||||
|
||||
def get_working_directory(self, session_id: str) -> str:
|
||||
"""Read the effective directory, recovering a missing directory to the Session origin."""
|
||||
response = self.request(
|
||||
@@ -603,7 +611,7 @@ class _WorkingDirectoryResponse(BaseModel):
|
||||
cwd: str
|
||||
|
||||
|
||||
class _ShutdownResponse(BaseModel):
|
||||
class _EmptyResponse(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
from deepseek_harness.errors import JsonRpcError
|
||||
|
||||
|
||||
@pytest.mark.parametrize("outcome", ["restored", "missing"])
|
||||
def test_resume_preserves_identity_and_propagates_failure(tmp_path: Path, outcome: str) -> None:
|
||||
script = tmp_path / "runtime.py"
|
||||
requests = tmp_path / "requests.jsonl"
|
||||
script.write_text(
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
for line in sys.stdin:
|
||||
frame = json.loads(line)
|
||||
with open(os.environ["REQUESTS"], "a") as log:
|
||||
log.write(line)
|
||||
method = frame["method"]
|
||||
response = {"jsonrpc": "2.0", "id": frame["id"]}
|
||||
if method == "initialize":
|
||||
response["result"] = {"serverInfo": {"name": "fake-runtime"}}
|
||||
elif method == "session/resume":
|
||||
outcome = os.environ["OUTCOME"]
|
||||
if outcome == "missing":
|
||||
response["error"] = {"code": -32603, "message": "session not found"}
|
||||
else:
|
||||
response["result"] = {}
|
||||
else:
|
||||
response["result"] = {}
|
||||
print(json.dumps(response), flush=True)
|
||||
if method == "shutdown":
|
||||
break
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
with DeepSeekHarness(
|
||||
_launch_args=(sys.executable, str(script)),
|
||||
env={"OUTCOME": outcome, "REQUESTS": str(requests)},
|
||||
) as harness:
|
||||
if outcome == "restored":
|
||||
session = harness.resume_session("saved")
|
||||
assert session.id == "saved"
|
||||
assert session.harness is harness
|
||||
else:
|
||||
with pytest.raises(JsonRpcError, match="session not found"):
|
||||
harness.resume_session("saved")
|
||||
frames = [json.loads(line) for line in requests.read_text(encoding="utf-8").splitlines()]
|
||||
assert [frame["method"] for frame in frames] == ["initialize", "session/resume", "shutdown"]
|
||||
assert frames[1]["params"] == {"sessionId": "saved"}
|
||||
Reference in new issue
Block a user