mirror of
https://github.com/countbot-ai/CountBot.git
synced 2026-09-14 20:46:47 +08:00
feat(external-agents): add dsh profile for DeepSeek Harness headless
This commit is contained in:
@@ -437,5 +437,20 @@ class ExternalAgentRegistry:
|
||||
"history_message_count": 10,
|
||||
"timeout": 900,
|
||||
},
|
||||
{
|
||||
"name": "dsh",
|
||||
"aliases": ["deepseek-harness", "dsh-headless"],
|
||||
"type": "cli",
|
||||
"icon_svg": "",
|
||||
"enabled": False,
|
||||
"description": "DeepSeek Harness(headless 一次性任务,打印最终答案后退出)",
|
||||
"command": "dsh",
|
||||
"args": ["--profile", "headless", "{prompt}"],
|
||||
"working_dir": "",
|
||||
"inherit_env": ["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL", "DSH_HOME"],
|
||||
"session_mode": "history",
|
||||
"history_message_count": 10,
|
||||
"timeout": 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
"""外部编码工具 dsh profile 回归测试。
|
||||
|
||||
覆盖:新工作区默认配置自动带出 dsh(默认禁用);dsh 走通用 CLI 适配器
|
||||
(history 模式、不注入 --session-id);别名解析;禁用态保护。
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from backend.modules.external_agents.adapters.cli import CliExternalAgentAdapter
|
||||
from backend.modules.external_agents.base import ExternalAgentRequest
|
||||
from backend.modules.external_agents.conversation import (
|
||||
profile_supports_native_session,
|
||||
resolve_effective_session_mode,
|
||||
)
|
||||
from backend.modules.external_agents.registry import ExternalAgentRegistry
|
||||
|
||||
|
||||
def _load_dsh_profile(tmp_path):
|
||||
"""在新工作区创建 registry(会写出默认配置),返回解析后的 dsh profile。"""
|
||||
registry = ExternalAgentRegistry(workspace=tmp_path)
|
||||
return registry, registry._load_profiles()["dsh"]
|
||||
|
||||
|
||||
def test_default_config_contains_dsh_profile(tmp_path):
|
||||
"""新工作区首次生成的 external_coding_tools.json 自动带出 dsh(默认禁用)。"""
|
||||
registry = ExternalAgentRegistry(workspace=tmp_path)
|
||||
config = json.loads(
|
||||
(tmp_path / ExternalAgentRegistry.CONFIG_FILENAME).read_text(encoding="utf-8")
|
||||
)
|
||||
profiles = {item["name"]: item for item in config["profiles"]}
|
||||
dsh = profiles.get("dsh")
|
||||
assert dsh is not None
|
||||
assert dsh["enabled"] is False
|
||||
assert dsh["type"] == "cli"
|
||||
assert dsh["command"] == "dsh"
|
||||
assert dsh["args"] == ["--profile", "headless", "{prompt}"]
|
||||
assert dsh["session_mode"] == "history"
|
||||
assert "stdin_template" not in dsh # dsh 从 argv 读任务,不走 stdin
|
||||
assert dsh["timeout"] == 900
|
||||
assert dsh["inherit_env"] == ["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL", "DSH_HOME"]
|
||||
|
||||
|
||||
def test_dsh_profile_uses_history_session_mode(tmp_path):
|
||||
"""dsh 解析为 history 模式,且不会启用 native 会话。"""
|
||||
_, profile = _load_dsh_profile(tmp_path)
|
||||
assert profile.type == "cli"
|
||||
assert resolve_effective_session_mode(profile) == "history"
|
||||
assert profile_supports_native_session(profile) is False
|
||||
|
||||
|
||||
def test_dsh_aliases_resolve_to_canonical_name(tmp_path):
|
||||
"""dsh 及其别名均可解析到规范名。"""
|
||||
registry, _ = _load_dsh_profile(tmp_path)
|
||||
assert registry.resolve_profile_name("dsh") == "dsh"
|
||||
assert registry.resolve_profile_name("deepseek-harness") == "dsh"
|
||||
assert registry.resolve_profile_name("dsh-headless") == "dsh"
|
||||
|
||||
|
||||
def test_dsh_argv_does_not_inject_session_id(tmp_path):
|
||||
"""dsh 的 argv 形如 dsh --profile headless <prompt>,不注入 --session-id。"""
|
||||
registry, profile = _load_dsh_profile(tmp_path)
|
||||
adapter = CliExternalAgentAdapter()
|
||||
request = ExternalAgentRequest(
|
||||
task="hello",
|
||||
prompt="hello",
|
||||
workspace=tmp_path,
|
||||
working_dir=tmp_path,
|
||||
session_id="session-1", # 即使携带 session_id,history 模式也不注入
|
||||
)
|
||||
argv = adapter._build_argv(
|
||||
"dsh", profile, request, adapter._build_template_variables(request)
|
||||
)
|
||||
assert argv == ["dsh", "--profile", "headless", "hello"]
|
||||
assert "--session-id" not in argv
|
||||
|
||||
|
||||
def test_disabled_dsh_profile_is_rejected(tmp_path):
|
||||
"""dsh 默认禁用:未启用时 resolve_profile 应明确拒绝。"""
|
||||
registry, _ = _load_dsh_profile(tmp_path)
|
||||
with pytest.raises(ValueError, match="disabled"):
|
||||
registry.resolve_profile("dsh")
|
||||
@@ -447,6 +447,18 @@ POST /api/settings/external-coding-tools/check
|
||||
}
|
||||
```
|
||||
|
||||
### DeepSeek Harness(dsh)配置说明
|
||||
|
||||
dsh(DeepSeek Harness)是 DeepSeek 官方开源的 Agent 框架,其 headless 模式适合作为一次性编码工具接入。新工作区首次生成 `external_coding_tools.json` 时会自动带出 dsh profile(默认 `enabled: false`);**老工作区不会自动出现,需在设置页手动新增,或手改 json 添加**。
|
||||
|
||||
- 调用契约:`dsh --profile headless "<task>"`;`--profile headless` 必须写在最前,任务从命令行参数读取,不从 stdin 读取,因此**不要配置 `stdin_template`**。
|
||||
- 行为:每次运行创建一个全新的一次性会话,任务完成后把最后一条非空 assistant 文本写到 stdout 并退出;正常完成退出码 0,出错(含启动失败、初始化失败、最终原因为 error)退出码非 0。
|
||||
- 工作目录:dsh 把当前工作目录当作默认 workspace 根,直接沿用 CountBot 传入的 `working_dir`(子进程 cwd),无需 `{working_dir}` 占位符。
|
||||
- 会话:headless 不支持续会话,`session_mode` 固定用 `history`(或 `stateless`),**不要用 `native`**。
|
||||
- 凭证:dsh 默认从 `~/.dsh/.credentials.yaml` 读取凭证(由 `dsh-credentials-local` 提供),不依赖环境变量;`inherit_env` 里的 `DEEPSEEK_API_KEY` / `DEEPSEEK_BASE_URL` / `DSH_HOME` 只是可选的 env 替代方式,未设置不代表不可用。
|
||||
- 首次运行:headless 首次使用会向 `$DSH_HOME/profiles/headless` 写初始化文件,要求子进程有可写的 `HOME` / `DSH_HOME`(默认 `~/.dsh`);请确保 CountBot 与 dsh 以同一用户运行,否则会以 `EPERM` 失败。
|
||||
- 前置条件:机器上 `dsh` 在 PATH 中(Node CLI,一般 `npm i -g @deepseek-ai/dsh`),且已有可用凭证。
|
||||
|
||||
## 10. 导入导出
|
||||
|
||||
图形化路径:
|
||||
|
||||
@@ -87,6 +87,37 @@
|
||||
"success_exit_codes": [
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dsh",
|
||||
"aliases": [
|
||||
"deepseek-harness",
|
||||
"dsh-headless"
|
||||
],
|
||||
"type": "cli",
|
||||
"icon_svg": "",
|
||||
"enabled": false,
|
||||
"description": "DeepSeek Harness(headless 一次性任务,打印最终答案后退出)",
|
||||
"command": "dsh",
|
||||
"args": [
|
||||
"--profile",
|
||||
"headless",
|
||||
"{prompt}"
|
||||
],
|
||||
"working_dir": "",
|
||||
"stdin_template": null,
|
||||
"env": {},
|
||||
"inherit_env": [
|
||||
"DEEPSEEK_API_KEY",
|
||||
"DEEPSEEK_BASE_URL",
|
||||
"DSH_HOME"
|
||||
],
|
||||
"session_mode": "history",
|
||||
"history_message_count": 10,
|
||||
"timeout": 900,
|
||||
"success_exit_codes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user