<div align="center">

# KoboldCpp for DeepSeek Harness

**`dsh-koboldcpp-hands`** — 给 DeepSeek Harness 的智能体一双本地的手。

[![version](https://img.shields.io/badge/version-0.1.0-blue)](https://github.com/MicroHEROX/dsh-koboldcpp-hands)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![node](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org)
[![harness](https://img.shields.io/badge/DeepSeek%20Harness-0.1.0--rc-4D6BFE)](https://github.com/deepseek-ai/deepseek-harness)

**[English](README.md) · [中文](README.zh-CN.md)**

</div>

一个为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 编写的**第三方工具插件**：让**在线大模型**（你的主对话模型）把重复、耗 token 的简单劳动交给**本机 KoboldCpp**（llama.cpp）服务器完成——包括纯文本工作**和**视觉工作（识图 / OCR / 图片对比）。

主模型保持在你部署的位置不变。当它认为某个任务更适合本地完成时，它会调用：

- **`koboldcpp_run`** — 在本地文本模型上运行一条提示词（批量改写、名字翻译、字符串处理、短文本摘要、结构化提取）。
- **`koboldcpp_vision`** — 把图片交给本地多模态模型（OCR、图像分析、多图对比），使用结构化报告模板。

插件负责本地服务器生命周期：用**你的** KoboldCpp 可执行文件和**你的** `.kcpps` 启动配置（后端、模型、`mmproj`、端口都在里面）按需拉起，等待模型加载完成，并按 `stopBehavior`（`exit` / `idle` / `never`）停止。你自己启动的 KoboldCpp 会被复用，**绝不会被杀死**。

---

## 做了哪些事（What it does）

- **两个模型可见工具**，按官方 `dsh-tools` 契约注册（`defineTool`、canonical JSON 返回值、纯 render/presenter、`exec.signal` 转发）。
- **按需的服务器生命周期**：首次工具调用才拉起 `exePath`（带你的 `kcppsPath` + `--port`），轮询 `/v1/models` 直到健康；服务器崩溃后自动自愈；按 `stopBehavior` 停止。Windows 上做**进程树终止**（`taskkill /T`），因为 KoboldCpp 会自我派生子进程。
- **文本 + 视觉 wire 支持**：非流式 OpenAI 兼容 chat-completions；图片按标准多模态 `content` 数组发送。
- **三种图片来源**（视觉工具）：本地文件路径、`data:`/`http(s):` URL、或当前会话中已附带的图片（经 harness attachment 服务读取）。**注意**：会话附件来源要求主模型声明支持图片输入，纯文本主模型下只有 `image_paths` / `image_urls` 可用（见「当前版本限制」一节）。
- **结构化视觉提示词**：结构化报告契约 —— `analyze`（8 段报告）、`ocr`（逐字提取）、`compare`（多图、5 段报告），并附 fidelity 规则（逐字转发、不得编造、保留不确定性）。
- **热配置**：harness 用户设置文档中的 `llm-koboldcpp:` 节可无需重启覆盖插件配置；`KOBOOLDCPP_EXE` / `KOBOOLDCPP_KCPPS` 环境变量兜底。
- **安全的归属关系**：外部 KoboldCpp 进程只复用、绝不触碰；只有插件自己拉起的服务器才会被停止。

## 没做哪些事（What it does NOT do）

- **不替换** harness 的模型提供方（provider）——在线模型始终是主模型，本地模型只能通过两个工具触达。
- **不替你决定** GPU 后端、模型路径或模板。KoboldCpp 的一切启动设置都在**你的 `.kcpps` 文件**里（`usecuda`/`usevulkan`/`usecpu`、`model_param`、`mmproj`、端口）。不探测、不自动加参数。
- **不修改**任何 DeepSeek Harness 文件——纯插件，即插即卸。
- **不捆绑/托管** GGUF 或 `mmproj` 模型文件——模型自备。
- **不用流式、不用 API key**（本地服务，无凭据参与）。
- **不在 harness 进程内常驻服务**——只在需要时派生独立 KoboldCpp 进程。

## 环境要求

| 项 | 要求 |
| --- | --- |
| Node.js | ≥ 20 |
| DeepSeek Harness | 已安装（`npx @deepseek-ai/dsh web` 或源码检出） |
| KoboldCpp 可执行文件 | `koboldcpp.exe`（NVIDIA/CUDA）或 `koboldcpp-nocuda.exe`（AMD/Vulkan），任一提供 `/v1/chat/completions` 的版本 |
| GGUF 模型 | 自备；视觉场景另需多模态 GGUF **及其 `mmproj`**（在 kcpps 的 `"mmproj"` 字段配置） |

## 安装方式

在 harness 项目目录（组合文件 `cordis.yml` / `cordis.patch.yml` 所在处）：

```sh
npm install dsh-koboldcpp-hands
```

源码检出方式，可把插件条目直接指向本仓库的克隆：

```yaml
- insert:
    - id: koboldcpp-tool
      name: '../dsh-koboldcpp-hands'
```

## 使用方法（配置）

启动设置归你所有。npm 安装后插件行已由 bundle 自动插入，**请在 profile 的 `cordis.patch.yml` 中用不带 `insert` 的 id 覆盖形式修改**（同 id 再 `insert` 会导致 loader 崩溃 `duplicate loader entry id`）：

```yaml
- id: koboldcpp-tool
  name: 'dsh-koboldcpp-hands'
  config:
    baseURL: 'http://127.0.0.1:5001'                     # 必须与 kcpps 中的端口一致
    exePath: 'C:\path\to\koboldcpp-nocuda.exe'           # 你的二进制（CUDA 或 Vulkan 版）
    kcppsPath: 'C:\path\to\your-model.kcpps'             # 你的启动配置：后端 + 模型 + mmproj + 端口
    autoStart: true
    stopBehavior: idle
    idleStopMinutes: 30
```

（git/本地路径的普通依赖安装方式则用 `- insert:` 加同一行即可。）

实际启动的命令就是：

```
koboldcpp-nocuda.exe "C:\path\to\your-model.kcpps" --port 5001
```

全部 17 个配置字段及默认值：见 [docs/api.md](docs/api.md) §1.2。

## 工具用法

### `koboldcpp_run` — 文本

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `prompt` | string | 是 | 发给本地模型的指令/文本（user 消息） |
| `system` | string | 否 | 可选系统指令 |
| `temperature` | number | 否 | 采样温度（0–2） |
| `max_tokens` | integer | 否 | 输出上限（默认 `maxTokens`） |
| `stop` | string[] | 否 | 停止序列 |

返回 `{ text, reasoning?, model, usage, elapsedMs }`。

### `koboldcpp_vision` — 图片 / OCR

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `mode` | `analyze`/`ocr`/`compare` | 否 | 内置提示词模板（默认 `analyze`） |
| `prompt` | string | 否 | 自定义指令（覆盖模板） |
| `image_paths` | string[] | 否 | 本地图片（png/jpg/jpeg/webp/gif/bmp，单张 ≤20 MB） |
| `image_urls` | string[] | 否 | `data:image/...` 或 `http(s)://` URL |
| `temperature` | number | 否 | 采样温度（OCR 建议 ~0.2） |
| `max_tokens` | integer | 否 | 输出上限 |
| `stop` | string[] | 否 | 停止序列 |

图片来源按序解析：显式 `image_paths` + `image_urls` → 会话中最近的图片 → 清晰报错。`compare` 一次请求发送 2–4 张图做联合推理。

返回 `{ text, reasoning?, model, images, usage, elapsedMs }`。

> 视觉需要多模态 GGUF **及其 `mmproj` 投影器**（配置在 kcpps 中）。没有 `mmproj` 时请求正常完成，但模型看不到图片。

## 当前版本限制：纯文本主模型只能通过「在线链接」和「本地路径」送图

**当前版本（插件 0.1.0，harness 0.1.0-rc.6）下，如果主模型是纯文本模型，`koboldcpp_vision` 只能通过两个显式渠道收到图片：`image_paths`（本地文件路径）和 `image_urls`（在线 / `data:` 链接）。** 会话附件渠道在这种组合下不可用——这是 harness 的硬限制，不是本插件的限制：

1. 在纯文本模型下粘贴/拖入图片，dsh 会在消息进入会话之前**直接拒绝整条消息**（`attachment-error / MODEL_DOES_NOT_SUPPORT_IMAGES`，界面提示"当前模型不支持图片，请切换支持图片的模型"）。检查点在 `dsh-host-apiproxy`：所选模型在 pi-ai 模型目录中声明的输入模态必须包含 `image`；目录里声明 `input: ["text"]` 的模型（如 `opencode-go` 路由下的 `deepseek-v4-flash` / `deepseek-v4-pro`）会被拒绝。
2. 即使图片部分能进入消息，`dsh-llm-pi-ai` 的流式适配器也会对同样的纯文本模型拒绝图片内容（`UNSUPPORTED_CONTENT`）；子代理续写会话则在浏览器端直接屏蔽图片。
3. 由于消息在持久化为附件之前就被拒绝，"读取会话最近附带图片"的来源无图可读——与 OpenCode / Pi 不同，dsh 目前**不会**把粘贴的图片变成临时文件路径交给纯文本模型。

**当前可用的绕行方式：**

- 让模型调用 `koboldcpp_vision` 时传 `image_paths: ["C:\\...\\photo.png"]`——任何 harness 进程可读的路径都行。
- 或传在线链接 `image_urls: ["https://example.com/photo.png"]`（也支持 `data:` URL）。
- 或把主模型换成目录里声明支持图片输入的模型（如 `opencode-go` 下的 `minimax-m3`、`qwen3.7-plus`、`kimi-k2.6`、`kimi-k3`、`grok-4.5`），会话附件渠道即可自动生效。

上游已跟踪：[deepseek-harness 讨论 #1378](https://github.com/deepseek-ai/deepseek-harness/discussions/1378)（建议：纯文本模型也允许图片附件，并以链接/路径形式交给工具处理）。harness 放宽限制后我们会更新本节。

## 路线（Roadmap）

**可以走的方向：**

- 更多视觉模式与提示词模板（文档版面、表格提取等）。
- 多模型 `autoswapmode` 支持（kcpps 层面；wire `model` 字段已可配置）。
- 发布到 npm registry 与 `dsh-plugin` topic。
- 批处理任务：一个 agent 回合驱动多次本地调用。

**不能/不会走的方向：**

- 自动探测 GPU / 注入后端参数 —— **你的 kcpps 说了算**（设计如此）。
- 变成 LLM provider 适配器 —— 插件保持工具定位，在线模型始终是主模型。
- 流式响应 —— 工具调用一次往返拿全量结果（更简单、够用）。
- 捆绑模型文件（gguf / mmproj）或修改 DeepSeek Harness 本体。

## 卸载方法

卸载和安装一样干净：

1. **删除插件条目**：从 profile 的 `cordis.patch.yml`（或 `cordis.yml`）删除这段：
   ```yaml
   # 删除整个块
   - insert:
       - id: koboldcpp-tool
         name: 'dsh-koboldcpp-hands'
   ```
2. **重启 harness**（或热更新配置）。`koboldcpp_run` 和 `koboldcpp_vision` 两个工具会自动注销——在线模型不再看到它们。
3. **服务器善后**（取决于 `stopBehavior`）：
   - `exit`：harness 正常退出时，插件会停止它自己拉起的 KoboldCpp。
   - `idle`：空闲超时后自动停止。
   - `never`：服务器保持运行，需要你自己停止（Windows 可用 `taskkill /PID <pid> /T /F`）。
   - 你自己启动的 KoboldCpp **永远不会**被触碰。
4. **无残留**：插件不向 harness 写入任何文件、正常退出时不留进程、不创建自己的配置文件。如果通过 npm 安装，用 `npm uninstall dsh-koboldcpp-hands` 移除。

### 删除插件本身

- **npm 安装**——一条命令从项目中移除包：
  ```sh
  npm uninstall dsh-koboldcpp-hands
  ```
- **git clone 安装**（profile 条目指向克隆目录）——删除 profile 条目后删除克隆目录：
  ```powershell
  Remove-Item -Recurse -Force C:\path\to\dsh-koboldcpp-hands
  ```
  ```sh
  rm -rf /path/to/dsh-koboldcpp-hands
  ```

## 版本与兼容性

| 组件 | 版本 |
| --- | --- |
| 本插件 | `0.1.0` |
| DeepSeek Harness | `0.1.0-rc` 系列（在 npm `@deepseek-ai/*` `0.1.0-rc.6` 上测试） |
| Node.js | ≥ 20 |
| KoboldCpp | 任一提供 `/v1/chat/completions` 的版本 |

运行时 peer 依赖：`@deepseek-ai/cordis ^4.0.1`、`@deepseek-ai/dsh-tools`/`dsh-llm`/`dsh-session`/`dsh-attachment`/`dsh-settings`/`dsh-launch-environment` `>=0.1.0-rc.2`、`@deepseek-ai/schemastery ^3.18.1`。

## 开发

```sh
npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest run（45 个测试：单元/工具/集成/Loader 组合）
npm run build       # tsc -> lib/
```

测试包含 REAL-composition 层（app boot → Cordis Loader → `cordis.yml`，符合 harness 测试规范），以及真实机器场景驱动（`tests/real-driver.mjs`）：自动拉起 / 复用外部 / 服务器不可达三种行为。

## 文档

| 文档 | 内容 |
| --- | --- |
| [docs/engineering.md](docs/engineering.md) | 工程结构、插件契约、命令、测试分层 |
| [docs/api.md](docs/api.md) | 权威 API 参考（配置、工具、类、错误码） |
| [docs/glossary.md](docs/glossary.md) | 标准术语表 |
| [docs/solutions.md](docs/solutions.md) | 坑、疑难问题、方法论 |

## 致谢

- **[DeepSeek AI](https://github.com/deepseek-ai/deepseek-harness)** —— 本项目所依托的 DeepSeek Harness 平台，以及作为模式参考的实现（`dsh-llm-deepseek`、`dsh-tool-todo`）。
- **[LostRuins / KoboldCpp](https://github.com/LostRuins/koboldcpp)** —— 优秀的本地 llama.cpp 服务器，其 OpenAI 兼容 API 让这一切成为可能。
- **[Cordis](https://github.com/cordiverse/cordis)** —— 支撑 harness 的插件运行时。
- 运行在你机器上的开源模型与量化生态（llama.cpp 生态、GGUF）。

## License

[MIT](LICENSE)。与 DeepSeek AI 和 LostRuins 无隶属关系；`dsh` 与 `koboldcpp` 为各自所有者的商标。
