dsh-llm-qodersdk
中文 | English
将 DeepSeek Harness 的 LLM 接缝(ctx.llm)路由到本机 Qoder CLI 的适配器插件(@deepseek-ai/dsh-llm-qoder),基于 @qoder-ai/qoder-agent-sdk。
它注册 qoder / qoder-byok 两个 provider 路由,让 harness 的模型请求复用本机 qodercli 的登录态——无需任何凭据或设置项。模型与账号自定义模型都从 qodercli 实时拉取。
特性
- 无配置接入:完全复用本机
qodercli登录态,不需要 API key 或 settings 段。 - 长驻会话:每个宿主 session id 对应一个 warm 内层
query()子进程,对话延续、工具轮次都发生在会话内部。 - 工具桥接:宿主工具通过进程内 MCP server(
dsh-host)暴露给内层模型;qodercli 一次执行一个调用、宿主一次回传整轮结果,二者通过 callId 配对。 - 模型目录:实时从 CLI 拉取可用模型,TTL 缓存 + 并发共享,失败回退静态目录;另提供
deepseek-v4-flash/deepseek-v4-pro别名。 - 双 provider 分组:
qoder出 qodercli 内置模型,qoder-byok出账号自定义模型(source === 'user'),各自独立路由。 - 思考强度:
resolveModel上报 CLI 的 reasoning efforts 与默认档位,产品模型选择器自动显示思考强度选项;所选档位通过 model-policy 参数随每次请求下发。 - 上下文窗口:上报 CLI 的
availableContextWindows/defaultContextWindow,模型选择器可直接切换上下文窗口(如 200K/400K/1M),并通过 model-policy 参数下发。 - 旁路请求:标题生成、compaction 等 side-channel 请求走冷启动一次性调用,不占用 warm 会话。
适配原理
这一节说明本插件把 harness 的 LLM 接缝映射到 qodercli 时,每一层是怎么适配的、为什么这么设计。
1. LLM 接缝适配(ctx.llm → qodercli)
harness 通过 ctx.llm.registerAdapter(['qoder', 'qoder-byok'], adapter) 注册 QoderAdapter,实现 harness 的 LlmAdapter 契约:
| harness 接缝 | 本插件实现 |
|---|---|
providerInfo(provider) |
返回 qoder(Qoder CLI)/ qoder-byok(Qoder 自定义)的展示名 |
listModels(provider) |
实时拉取 qodercli 模型目录,按 provider 过滤(qoder 出内置、qoder-byok 出账号自定义 source === 'user') |
resolveModel(provider, model) |
从 live catalog / 静态表解析模型元数据(上下文窗口、思考档位、输出上限) |
stream(options) |
把一次 GenerateOptions 转成 qodercli 的 query() 调用并流式回传 StreamChunk |
适配的核心是 stream() 的分流:
- warm 会话路径(有
sessionId且无purpose):复用或新建一个内层query()子进程,增量喂入新消息,工具通过 MCP 往返。 - side-channel 路径(无
sessionId,或带purpose,如标题生成、compaction 摘要):走coldStream()一次性调用,不占 warm 会话。该路径复用主会话的模型(resolveQoderModelId(options.model)),保证 dsh 记录的调用目标与实际执行的模型一致。
2. 会话模型适配
- 一宿主会话对应一 warm qodercli 会话:
QoderSessionManager以宿主sessionId为键维护query()子进程,超出maxSessions按插入序 LRU 淘汰。 - 增量 feed:宿主每次请求携带完整消息列表,插件通过
planContinuation对比上一次,只把新用户轮次与改写消息渲染成纯文本 feed;工具结果不进入文本 feed(走 MCP)。 - 重建检测:当宿主 surface 被改写(例如 compaction 折叠了历史)导致消息数变少或结构变化,
planContinuation返回rebuild: true,插件 dispose 旧 warm 会话并按新 surface 冷启动。这保证了 dsh 侧的压缩与 qodercli 内部缓存不会产生双份状态。 - 模型切换:
setModel把reasoningEffort/contextWindow作为 model-policy 参数传给内层会话。
3. 工具桥接适配(MCP)
宿主工具不直接发给 qodercli,而是通过进程内 MCP server(dsh-host):
ensureTools()把宿主ToolSchema转成 zod shape 注册到 MCP server;- qodercli 的
canUseTool允许mcp__dsh-host__*前缀的工具,并记录 tool-use id; - MCP handler park 在一个 promise 上,等待宿主在下一轮请求里
deliverToolResults()回传结果; - 结果按
callId ↔ toolUseId配对投递;超时(TOOL_RESULT_TIMEOUT_MS)则返回错误让 qodercli 恢复。
这样宿主工具调用是宿主侧的普通工具轮次,qodercli 只看到 MCP 工具被"执行了一次"。
4. 模型目录适配
- 实时目录:
QoderModelCatalog向 qodercli 发get_models控制请求,TTL 缓存(默认 300s)、并发共享、超时回退静态表。 - 静态回退:
QODER_MODELS内置捕获的模型表(含deepseek-v4-flash/deepseek-v4-pro别名),CLI 不可达时使用。 - provider 分组:
listModels按source字段分流——内置模型进qoder,账号自定义模型(source === 'user')进qoder-byok,无需任何手动配置,全部从 qodercli 实时拉取。
5. 上下文窗口与压缩阈值的适配
qodercli 的 live catalog 对每个模型同时上报上限与实际窗口:
maxInputTokens: 1000000 ← 模型上限(1M)
availableContextWindows: [200000, 400000, 1000000]
defaultContextWindow: 200000 ← 请求实际使用的窗口
harness 的 compaction 引擎用 resolveModel().context.contextWindow 计算自动压缩阈值(thresholdTokens = 0.8 × contextWindow,compaction-basic 默认 thresholdRatio: 0.8),UI 上下文环也以它为分母。因此 contextWindow 必须反映请求实际使用的窗口,而不是模型上限:
contextWindow: live.defaultContextWindow ?? live.maxInputTokens ?? DEFAULT_CONTEXT_WINDOW
- 取
defaultContextWindow(实际窗口,如 200K)→ 阈值 = 160K,与 provider 真实能力对齐; maxInputTokens(上限,如 1M)只保留在availableContextWindows里供选择器切换;- 若误用上限,阈值会被放大(如 800K),压缩永远不会在 provider 拒绝前触发。
6. 上下文占用计量的适配
qodercli 的流事件 usage 帧(input_tokens / output_tokens)默认全零(无 metering 数据),因此无法直接上报真实用量。harness 的 contextPressure 投影以最近一次请求的 inputTokens 为分子(pressureTokens)驱动 UI 环与压缩判断。
适配方式:在插件侧按 harness 前端 token-meter 相同的口径估算每次请求的输入。
adapter.stream() 每次请求调用 session.recordRequestInput(system, messages),用 renderInitialFeed(system, messages) 渲染完整会话(系统提示 + 全部消息),按 rendered.length / 4 估算 token —— 与 harness estimate.ts 的 CHARS_PER_TOKEN = 4 同口径。usage() 优先返回该估算值:
if (this.estimatedInputTokens !== undefined && this.estimatedInputTokens > 0) {
return { inputTokens: this.estimatedInputTokens, ... }
}
这样 UI 上下文环、自动压缩阈值与插件上报值使用同一套估算口径,占用显示与压缩行为一致,不会出现"UI 显示 2% 而实际已接近上限"的割裂。
7. 错误分类适配
qodercli 把上下文超限、配额不足等拒绝统一报成 generic per-turn error(error_during_execution)。harness 的 overflow recovery 依赖错误码 CONTEXT_WINDOW_EXCEEDED 才会触发(prune + compact + retry)。因此插件用 dsh-llm 的共享分类器把 qodercli 的报错文本映射成 harness 可路由的错误码:
function classifyTurnError(detail: string): string {
if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE
if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE
return 'BACKEND_TURN_ERROR'
}
这样 provider 的上下文超限能触发 harness 的自动恢复,配额不足能正确展示,而不是当作普通后端错误死掉。
8. 压缩职责分工(dsh vs qoder)
- 压缩由 dsh(harness compaction-basic)执行:决定压缩范围、保留比例(默认
retainRatio: 0.16保留最近 16%)、调用 LLM 生成 checkpoint、改写会话 surface。 - qoder 插件只充当 LLM 后端:把 dsh 的消息喂给 qodercli、取回回复。压缩摘要请求通过 side-channel 走
coldStream(),并复用主会话模型(见第 1 节),保证 dsh 记录的摘要目标与实际执行一致。 - 无冲突保证:dsh 压缩改写 surface 后,插件
planContinuation检测到消息结构变化(rebuild: true),主动重建 warm 会话。qodercli 内部缓存随 dsh 压缩被作废,不存在"两边各压一遍"。 - 摘要生成默认走主会话路由;需要绕开特定 provider 配额时,可在
cordis.patch.yml给 compaction-basic 配置summarizationProvider/summarizationModel指向其它有额度的模型。
安装
在 DeepSeek Harness(dsh)中作为插件加载:
# cordis.yml 或 patch 层
- id: llm-qoder
name: '@deepseek-ai/dsh-llm-qoder'
然后选择 qoder 或 qoder-byok provider 下的模型即可(在对话框模型选择器或 Models 设置页中)。
配置
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
maxSessions |
number | 8 |
同时保持 warm 的内层 qodercli 会话上限(超出按插入序 LRU 淘汰) |
modelCacheTtlSeconds |
number | 300 |
CLI 模型目录的缓存保鲜秒数 |
源码结构
| 文件 | 职责 |
|---|---|
src/index.ts |
插件入口:ctx.llm.registerAdapter(['qoder', 'qoder-byok'], adapter) |
src/adapter.ts |
QoderAdapter:模型列表/解析/流式生成,warm 会话管理、续轮规划、side-channel 模型传递、请求输入估算 |
src/session.ts |
QoderSession:内层 query() 子进程、MCP 工具桥、SDK 流事件 → harness StreamChunk、usage 上报(真实输入估算 + 错误分类) |
src/models.ts |
实时模型目录拉取(TTL 缓存、并发共享、超时、静态回退) |
src/catalog.ts |
静态模型表与 deepseek-v4-* 别名 |
src/render.ts |
宿主消息 → 内层纯文本 feed;身份覆盖 |
src/jsonschema.ts |
dsh ToolSchema.parameters → zod shape(MCP 工具注册用) |
开发与构建
本仓库只存源码(src/);构建产物 lib/(lib/index.js + lib/types/*.d.ts)被 .gitignore 忽略,由构建脚本按需生成。
npm install
npm run build # tsc 产出 lib/types/*.d.ts,tsdown 产出 lib/index.js
npm publish # prepublishOnly 自动先构建
构建配置已就位(tsconfig.json + tsdown.config.ts),peer 依赖 @deepseek-ai/dsh-llm 和 @deepseek-ai/cordis 保持 external。
当前限制:
@deepseek-ai/dsh-llm@^0.1.0-rc.5尚未发布到公共 npm(registry 上仅有0.0.1-rc.1),因此npm install暂时无法解析该 peer 依赖,独立构建也暂不可运行。等它发布后,本仓库即可直接npm install && npm run build && npm publish。在此之前,实际构建/使用仍在 DeepSeek Harness 仓库内的plugins/llm-qoder/完成(由仓库根配置tsc+tsdown产出lib/)。
License
MIT © 2026 dsh-llm-qodersdk contributors
No comments yet. Be the first to write one.