Skip to content
dsh.fish
Bundle

@deepseek-ai/dsh-llm-acp

ACP LLM adapter + ACP Servers settings UI: drives external Agent Client Protocol servers as model providers on the DeepSeek Harness LLM seam, with a web settings page for browsing the ACP registry and managing configured servers

Source
shenkonghui
stars
2 stars
License
MIT
Updated
Updated 11 hours ago

Readme

# @deepseek-ai/dsh-llm-acp

中文 | [English](README.en.md)

[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 ACP 客户端 LLM 适配器 + ACP 服务设置界面。通过外部 [Agent Client Protocol](https://agentclientprotocol.com) 服务器作为模型提供方接入 harness 的 LLM 层,并提供一个 Web 设置页面用于浏览 ACP 注册表和管理已配置的服务器。

本包是一个**双面 dsh 插件**:宿主端(`lib/index.js`)是传输适配器,在 `ctx.llm` 上注册 provider 路由;客户端(`lib/client.js`)是浏览器设置页面,让用户从 Web UI 浏览 ACP 注册表并添加/删除 ACP agent 服务器。

## 界面
### ACP注册表添加acp-server
支持acp registry上的所有acp-server[https://agentclientprotocol.com/get-started/registry]

如claude,codex,opencode等
![alt text](image.png)

### ACP服务管理
![alt text](image-1.png)

### 使用acp-server进行交互
![alt text](image-2.png)

## 安装

```sh
dsh plugin --profile my-acp add github:shenkonghui/dsh-llm-acp
```

或从本地目录安装:

```sh
dsh plugin --profile my-acp add ./dsh-llm-acp
```

构建产物(`lib/`)已提交到仓库,安装时无需运行任何构建脚本。

## 卸载

```sh
dsh plugin --profile my-acp remove @deepseek-ai/dsh-llm-acp
```

这会从 profile 中移除依赖和 bundle 层。

## 配置

安装后,在 Web UI 中打开 **设置 → ACP 服务**。浏览 ACP 注册表,在任意 agent(如 Devin、Codex、Claude Agent)上点击 **添加**,即可将其配置为 ACP 服务器。每个已配置的服务器会创建一个独立的 provider 路由 `acp-<server-id>`。

在 **我的服务** 标签页中,点击任意已配置服务器上的 **编辑** 按钮,可以:
- 设置**环境变量**用于认证(如 `DEEPSEEK_API_KEY`、`OPENAI_API_KEY`)。每个服务器的环境变量会与插件级 `env` 合并,服务级优先。
- 选择要**启用的模型**。从服务器发现的模型目录中多选要暴露的模型,不选则启用全部已发现的模型。

ACP server 不单独保存权限策略。它复用会话输入框中的权限列表:`read-only` 和 `workspace-write` 将敏感操作转发到 harness 审批界面,`danger-full-access` 自动允许。

也可以直接在 `settings.yaml` 中配置:

```yaml
llm-acp:
  servers:
    devin:
      command: devin
      args:
        - acp
      name: Devin
      env:
        DEEPSEEK_API_KEY: sk-xxx
      models:
        - deepseek-chat
        - deepseek-reasoner
```

## 工作原理

### 整体流程

**1. 插件加载阶段(`apply()`,`src/index.ts`)**

```
dsh harness 启动
  └─ apply(ctx, config)
       ├─ 读取 llm-acp 设置命名空间 + 内联 config.servers,合并成服务器列表
       ├─ 对每个服务器 createServer():
       │    ├─ resolveNpxShortcut():npx -y <pkg> 若 bin 已在 PATH 则直接用 bin
       │    ├─ new AcpConnection():spawn 长生命周期子进程(stdin/stdout JSON-RPC)
       │    │    └─ initialize() 握手 → 按需 authenticate()(用配置的 API key)
       │    ├─ new AcpAdapter():构造时 discoverModels() 探测模型目录
       │    └─ ctx.llm.registerAdapter(['acp-<server-id>'], adapter)
       └─ reconcileDirectory():向设置页注册可配置 provider 目录
```

**2. 模型调用阶段(每次 `stream()`,`src/adapter.ts`)**

```
harness 请求模型
  └─ AcpAdapter.stream(options)
       ├─ await connection.ready(等 ACP initialize 完成)
       ├─ Session 决策:
       │    ├─ agent 支持 loadSession 且有 dsh sessionId
       │    │    → session/load 复用,只发增量用户消息(renderPromptDelta)
       │    │      失败/历史变短(compaction)→ 降级新建
       │    └─ 否则 session/new 新建,全量历史渲染成一条文本块(renderPrompt)
       ├─ setSessionModel():best-effort 设置所选模型
       ├─ session/prompt 流式循环:
       │    ├─ agent_message_chunk  → text-delta chunk
       │    ├─ agent_thought_chunk  → reasoning-delta(emitReasoning 开启时)
       │    ├─ 扩展进度通知          → reasoning-delta
       │    └─ stopReason 终态      → finish chunk(end_turn→stop 等)
       └─ 收尾:复用 session 记入 sessionMap 供下轮复用;一次性 session 关闭
```

权限请求(`session/request_permission`)按当前会话的权限预设路由:`danger-full-access` 自动 allow,否则弹 harness 的 approval UI。

**3. 设置界面阶段(浏览器端,`src/client/`)**

```
Web UI「设置 → ACP 服务」
  ├─ 浏览内置 ACP 注册表(registry.json)→ 点「添加」写入 llm-acp.servers
  ├─ 宿主端监听 settings 变更 → reconcileServers() 增删/重建连接(指纹比对)
  ├─ 模型发现:registerModelDiscovery 路由 acp-<id> → 临时 session/new 读 configOptions
  ├─ acp-info-<id>:只读 initialize 身份(agent 名/版本),不建 session
  └─ acp-resolve-<bin>:探测 PATH,把 npx 形式改存本地 bin 路径
```

核心设计:**每个 ACP 服务器 = 一个常驻子进程 = 一个 provider 路由 `acp-<id>`**;工具由 ACP 服务器内部自己执行,适配器只透传文本/推理流,不接 harness 工具生态。

### 宿主端 — LLM 适配器

`apply(ctx, config)` 从 `llm-acp` 设置命名空间读取已配置的服务器列表。对每个服务器,启动一个长生命周期的子进程,通过 stdin/stdout 建立 ACP `ClientSideConnection`,并在 `ctx.llm` 上注册路由为 `acp-<server-id>` 的 `AcpAdapter`。每次模型调用会创建新的 ACP session,将完整对话作为一条用户消息发送,并将流式 `agent_message_chunk` 更新转换为 harness 的 `StreamChunk`。

### 客户端 — 设置界面

浏览器端注册一个 `settings.section` slot,渲染 ACP 注册表浏览器和"我的服务"列表。添加服务器时会将其持久化到 `llm-acp` 设置命名空间;宿主端监听变更并同步更新 provider 目录。

### 注册表命令推导

ACP 注册表指定了不同的分发类型:

| 类型 | 命令 |
|---|---|
| `npx` | `npx -y <package> ...args` |
| `uvx` | `uvx <package> ...args` |
| `binary` | 取注册表 `cmd` 的 basename(如 `./bin/devin` → `devin`) |

binary 类型使用可执行文件的 basename,这样已安装到 PATH 的二进制文件可以直接找到,避免 `spawn ./bin/devin ENOENT` 错误。

## 配置项

| 配置 | 默认值 | 说明 |
|---|---|---|
| `emitReasoning` | `true` | 是否将 `agent_thought_chunk` 和扩展进度通知转换为 `reasoning-delta` chunk。 |
| `defaultModelId` | `glm-5-2` | ACP 发现未返回模型时的回退模型 ID。 |
| `defaultModelName` | `GLM-5.2 High` | 回退模型显示名称。 |
| `disposeEofGraceMs` | `6000` | stdin EOF 后等待平台终止的宽限时间(毫秒)。 |
| `disposeGraceMs` | `3000` | SIGTERM 后等待 SIGKILL 的 POSIX 宽限时间(毫秒)。 |

## 协议契约

每次 `stream()` 调用:

1. 创建新的 ACP `session/new`,使用配置的 `cwd`。
2. 将 harness 的 `messages` 和 `system` prompt 渲染为一条 ACP 文本块。
3. 发送 `session/prompt`,将流式 `agent_message_chunk` 更新作为 `text-delta` chunk 传输。
4. 当 `emitReasoning` 开启时,`agent_thought_chunk` 更新转换为 `reasoning-delta` chunk。
5. `session/prompt` 响应的终态 `stopReason` 转换为 `finish` chunk。

工具调用增量不会被输出。ACP 服务器内部执行自己的工具。`session/request_permission` 复用当前会话的权限预设:`danger-full-access` 自动允许,其他预设通过 harness 一次性审批请求处理;审批不可用、失败或 ACP 未提供 `allow_once` 时拒绝执行。

### 停止原因映射

| ACP | Harness finish |
|---|---|
| `end_turn` | `stop` |
| `max_tokens` | `max-tokens` |
| `refusal` | `error`(code `REFUSAL`) |
| `cancelled` | `aborted` |
| `max_turn_requests` / 未知 | `error` |

## 构建

```sh
pnpm install
pnpm build    # tsc -b && tsdown
```

构建产物已提交到仓库,用户安装时只需 `pnpm install` 即可。

## 已知限制与待办事项

- **不支持 harness 工具生态** — ACP 服务器执行自己的工具;harness 的 `GenerateOptions.tools` 被忽略。
- **无 session 复用** — 每次 `stream()` 调用创建新的 ACP session 并重新发送完整对话。
- **无 token 用量** — ACP v1 不提供 token 计数;适配器不输出 `usage` chunk。
- **系统提示在消息体内** — ACP `session/new` 没有 system 槽位,harness 的 system prompt 被拼接到用户消息文本前。
- **全量历史重发** — 适配器将整个 `messages` 数组渲染为一条用户消息。
- **ACP v1(SDK 0.25.1)** — 适配器使用 `@agentclientprotocol/sdk` 0.25.1,其 `session/prompt` 响应携带终态 `stopReason`(v1 契约)。
- **扩展协议处理** — Devin 的 `_cognition.ai/*` 通知被静默消费(进度文本在 `emitReasoning` 开启时作为 reasoning 输出);其他非标准 ACP 扩展被吞掉以避免 SDK 错误日志。

## 许可证

MIT

Install

dsh plugin --profile web add github:shenkonghui/dsh-llm-acp

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source