deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
DSH 原生模型渠道管理。两半结构:
src/index.js:轮询故障转移引擎(llm.registerAdapter 虚拟路由 roundrobin/<组id>)+ 7 天健康流水 + 测速排序 + 单模型真实请求测试通道。src/client.js:conversation.view 顶级页签「模型配置」,内含三个子页:模型配置(llm-pi-ai providers 全字段编辑、拉取上游、单模型 ⚡ 测试、供应商搜索过滤)、轮询渠道(groups 编辑 + ⚡测速 + 输入模态编辑;命名单一身份:显示名 = 组唯一 ID,保存时归一化 virtualModel.name = id,改 ID 即改名,永不漂移)、健康统计(7 天聚合)。@arcaneorion/dsh-model-selector-search(一个占座者一个插件单元,可独立启停/替换;座位遮蔽 + 搜索 + 近 7 天置顶 + 菜单向上展开都在该仓)。语义参考 pi 的 pi-provider-manager,但完全走 DSH 原生 seam(无独立 HTTP 服务/端口/token):
| pi-provider-manager | 本项目(DSH 原生) |
|---|---|
| 自建 127.0.0.1 HTTP 面板 + token | conversation.view 页签(client 半,静态 bundle) |
models.json / roundrobin/config.json + 自写原子写/bak |
settings 服务命名空间 model-channels(配置)/ model-channel-health(健康+运行态+测试结果) |
| 轮询 provider(自实现 HTTP 转发) | ctx.llm.registerAdapter(['roundrobin/<组>']),引擎内嵌套 ctx.llm.stream({provider:候选}) 转发 |
| 健康 JSONL | model-channel-health.records(settings 总线,跨会话共享) |
| 保存即热重载(自建事件) | settings watcher → 热重建虚拟路由(原生) |
| 面板模型测试(本地 HTTP 转发) | model-channel-health.testRequest 哨 → host 走真实 llm.stream → testResults[nonce] 回写 |
从 npm 装(发布版):
dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
# 然后重启 dsh --profile web 并刷新页面
本地开发用 link:(改源码即时生效)——profiles/web/package.json:
dependencies 加 "@arcaneorion/dsh-model-channel-manager": "link:/home/arcaneorion/AI/AI-DSH/plugin/model-channel-manager"dsh.profile.bundles 加 "@arcaneorion/dsh-model-channel-manager"pnpm install 后重启 dsh --profile web验证:
[model-channel-manager] booted, groups: ...llm.providers 出现 roundrobin/<组id>model-channels / model-channel-health 命名空间本包在 DSH 0.1.1-rc.2(dsh --version)上开发与实测,宿主侧依赖按该版本精确钉住:
| 宿主包 | 声明 | 用途 |
|---|---|---|
@deepseek-ai/dsh-llm |
0.1.1-rc.2 |
llm.registerAdapter / llm.stream(轮询引擎与健康采集) |
@deepseek-ai/dsh-settings |
0.1.1-rc.2 |
model-channels / model-channel-health 命名空间读写 |
@deepseek-ai/dsh-client-connection |
0.1.1-rc.2 |
client 半的 connection.api 调用 |
@deepseek-ai/dsh-client-ui-conversation |
0.1.1-rc.2 |
conversation.view 页签座位 |
@deepseek-ai/cordis |
^4.0.2 |
插件生命周期 |
@deepseek-ai/schemastery |
>=3.18.2 |
配置 schema |
换 DSH 版本(例如 0.1.2-rc.1)必须先重新验证、再放宽 peer:宿主服务与座位契约跨版本会变,
精确钉住的 peer 会在安装时报冲突——这正是它存在的意义,好过装上去静默失效。
api.settings.describe() 过滤命名空间api.settings.update({ns:'llm-pi-ai', patch:{providers}})api.settings.update({ns:'model-channels', patch:{groups}})api.settings.update({ns:'model-channel-health', patch:{speedRequest:{group,nonce}}})(host watcher 消费)settings.update({ns:'model-channel-health', patch:{testRequest:{nonce,provider,model,prompt,maxTokens}}});host 执行真实 llm.stream 后把结果写回 testResults[nonce];client 轮询 describe 直到 ok/errorapiRef 获取:ctx.get('connection').api(static client 必须在 inject 里声明 connection,apply 时捕获进闭包)宿主边界(重要):DSH apiproxy 对 settings RPC 有暴露白名单(
exposedNamespaces()= LLM provider ns +WEB_/PRODUCT_SETTINGS_NAMESPACES,2026-07 起生效)。含该边界的宿主必须放行model-channels/model-channel-health(本仓已在 harnessdsh-host-apiproxy打PLUGIN_SETTINGS_NAMESPACES补丁),否则 describe 会过滤掉这两个命名空间、写入报settings-not-exposed——轮询组保存/健康面板/测速/测试全链路静默失效。
所有 connection.api.* 调用返回 {result: {ok, value}} 包裹(dsh-client-connection 的 callUnary + zod 校验)。
resp.result.value.{...}resp.result.ok === false,错误在 resp.result.error.messagesettings.describe 的 value = {writable, hasDocument, namespaces:[{ns, value, base, user, revision, ...}]}llm.discoverModels 的 value = {models:[{id, name?, contextWindow?, maxTokens?}]}不要把 result.value 当 result 读——曾因少解一层导致整个面板静默空数据(describe 返回 namespaces 但全面板 0 provider,无任何错误提示)。
inputTokens / outputTokens / cacheReadTokens / cacheWriteTokens / reasoningTokens——来自适配器在 finish 前发出的 usage StreamChunk(rc.2 运行时 StreamChunk 契约,pi-ai done/error 事件都带);无 usage 则这些字段不写。tokenMeter 一致:input + cacheRead + cacheWrite + output(互斥计数,inputTokens 不含缓存命中)。面板「总 Token 用量」卡按统计窗口求和,副行显示 输入/输出/缓存 拆分;每模型卡底部显示该模型窗口 Tokens。llm/stream 拦截器 + 轮询引擎 streamAttempt(成功/失败/超时路径都尽量携带;pi-ai 的 error 事件同样上报部分 usage)。records 为 z.array(z.any())),无新 RPC / settings 字段;client 5s 轮询自动刷新。mcm_test_prompt,pi 同款,全局共用)llm.stream({provider, model, messages, maxTokens}) 真实调用(与正式对话同链路);60s 超时status:'ok'(ttftMs/latencyMs/text)或 status:'error'(code/error)注意:此通道依赖 host 半新代码。旧 host(未重启)无 testRequest 处理器,测试会一直「请求中」——client 现在约 66s 后超时报
POLL_TIMEOUT并提示 host 未处理(不再无限轮询)。
api.settings.update 前置的 llm.discoverModels({settingsNs:'llm-pi-ai', provider, baseURL}) 返回端点模型列表后按 pi 语义 diff:
configured = 本地已配 且端点在线的 → 默认勾选(提交保留,保持原顺序)missing = 端点有、本地无 → 默认不勾,勾选才添加stale = 本地已配但端点不在线的 → 默认不勾,提交会被清理已保留(勾选的configured) + 新添加(勾选的missing),未勾选的从 draft 删除供应商卡片头部「改名」按钮可重命名 Provider ID(约束:小写字母开头,仅小写字母/数字/连字符):
apiKeyEnv 凭据引用保持不变——凭据是 write-only 无法搬移,保持引用名原地不动即可让已存储 Key 继续生效~/.dsh/.credentials.yaml,0600,write-only 读不回),无手动按钮;清空输入框不会删除已存 key。上游 llm-pi-ai 的供应商 profile 只有 apiKeyEnv 一个密钥字段(凭据引用名),不存在内联 key 的选项——secrets 不进 settings.yaml、不随 settings.describe 下发,是有意的安全设计。normalizeCredentialRef),并对 ID + 引用双重去重——改名供应商会保留旧引用(write-only 无法搬移),只按 ID 去重会复活 provider-1 并继承已被占用的 PROVIDER_1_API_KEY(两个供应商同引用 = 共用同一把 key,写入互相覆盖)。此坑已由双重去重修复,存量撞引用靠 ⚠ 警示提示手动处理(改其中一个引用 → 重新写入)。.env > 用户 .env(credentials-local 分层);想用环境注入直接在启动环境 export 即可。曾踩坑:selected 曾初始化为 missing(只含"可加"),而 configured 项 checkbox 显示 checked:true 却不在 selected 里——应用时 kept = models.filter(m => cs.has(m.id)) 把已配置模型全部丢弃 → 已有模型消失。修复 = selected 初始化为 configured ∩ 端点。
ctx.inject(['settings'], (sctx) => {...})(settings 服务异步初始化,apply 时 ctx.get('settings') 为 undefined——曾经整个引擎静默失效,命名空间从未注册)model-channels 的虚模型/candidates/strategy/timeoutMs/cooldownMs/maxRetriesPerCandidate/speedTest;model-channel-health 的 records 7 天切片(单组 ≤2000 条)/speedResults/runtime/speedRequest+lastHandledNonce/testRequest+testResults+lastTestHandledNonce(success+2.5)/(total+5));测速失败进冷却;请求隔离按组reasoning.efforts 七档(off…max)、defaultEffort=max——原生 /model 弹窗对新模型的自动填档与展示跟随该声明;会话内显式档位的跨会话恢复由 selector 插件的档位记忆层负责(modelDirectories 拦截,存 model-channels.effortMemory).channel-manager/config.json 一次性迁入 model-channels(无遗留则忽略);完成后写 legacyMigrated 哨兵防止「清空组后重启复活」;fs 未就绪时 5s×6 重试.channel-manager/ 目录不再使用window.__ModuleLoader__.load({ id: '@arcaneorion/dsh-model-channel-manager', factory: (require) => ({ name, inject:['slots','connection'], apply }) });
react 经 require('react');样式用 ctx.effect 自管理;dsh.client: {inject:['slots','connection'], platform:'web'}(与 client.js 返回的 inject 一致)+ exports['./client'] 使 client-modules 自动扫描挂载。
client bundle 按内容 hash 服务且 no-cache:改 client.js 后刷新浏览器即可生效,无需重启 DSH。host 改动才需重启。
reasoningEfforts 缺失(undefined)的 model 正确渲染(|| {} 兜底)@arcaneorion/dsh-model-selector-search(原生座位遮蔽、搜索、向上展开菜单、effort 档位未实现等边界见该仓 README);本插件不再注册任何座位ctx.get('settings') 在 apply 时为 undefined → 整个引擎静默不生效(无报错、无命名空间)。必须 ctx.inject(['settings'], ...)。{result:{ok,value}} 只解到 result 找不到 namespaces/models → 面板静默空。解包函数校验 ok === false 抛错(否则失败也显示成功)。node --check/acorn 只能保证语法,无法确保 return 在函数体内——曾把 return 行整行删进函数体外(cards is not defined,页面白屏 "Failed to load plugins")。改完后用真实浏览器验证。connection 注入:static client 必须 inject:['connection'] 并在 apply 捕获 ctx.get('connection').api;在渲染组件里 ctx.get('connection') 拿不到(renderer 只收 standardProps)。动态插件 client 没有 connection 服务(动态 catalog 里没有)。chm-3 与静态包都注册 conversation.view id models 会出两个同名页签;静态化后停掉动态插件。profiles/web/package.json 的 dependencies/bundles 后必须 pnpm install + 重启(symlink 需重建)。dsh --profile web --no-open --port 3081 起临时实例读日志/settings;主实例 3080 是用户进程,改动 host 后必须用户重启。emitted 上抛,组层以 CHANNEL_MIDSTREAM_FAIL 直接终结。healthScope.get(),client 轮询加 55 次上限。settings-not-exposed——升级宿主前先打 PLUGIN_SETTINGS_NAMESPACES 补丁(见「数据通道」)。Object.keys().length + 1 在删除中间项后撞已有键(provider 覆盖草稿、group 被 host seen-set 静默去重消失)。用 uniqueSuffixName 取第一个未占用后缀。ctx.effect 注册条目只有显式 disposer 才移除;Promise.race 超时路径跳过后面的 guard.dispose() 会按超时次数泄漏。race 包 try/finally。streamAttempt 侧自行 recordHealth,否则轮询组流量几乎不进健康统计。@deepseek-ai/dsh-settings-file 落盘是注释保留型叶子 diff(patchNode),对 map 键序是盲的——纯重排(值不变)在文件层是零 diff,setIn 对已存在键原地替换不挪位,新键只 append。settings 服务的内存 user 层顺序确实变了(运行中一切正常),但文件永远是创建时序,重启即还原。修复:顺序存成数组数据——model-channels ns 里 providerOrder: [...] 字段(数组走 wholesale replace 真实落盘),client 加载时按它重排渲染,未列出的 provider append 在后。llm-pi-ai 的 mutate 照旧(当次会话内存序即刻生效)。注:原生 Models 页本无拖拽交互,其顺序由 directory 决定(catalog 内置序 + settings 键序拼接)恒定;要原生排序持久需上游修 patchNode。回归测试见 tests/provider-order-persistence.test.cjs。{...pVal} 浅拷贝再覆盖面板字段,空值靠覆盖后删键而非忽略。模型对象同理。Math.min(remaining, 30000),timeoutMs>30s 与动态超时 min(120s, ttft×2) 在 >30s 区间全部退化为 30s 切候选。guard 必须覆盖全量 remaining。另:用户主动 abort 不进健康流水(isAbortLike 三形态 + 终止块 ABORTED 跳过),否则污染成功率与 smart 键 reliability。conversation.input.model 是单占位 seat,cell = slot 本身;原生无 priority(= 0),插件同名注册同不传 → exact-priority 撞格直接抛错(「already has a registration at priority 0」→ apply 失败 → 整个插件含模型配置页签加载失败,面板全白)。规则:同 cell 多 entry 按 priority 升序、数值最小者渲染,遮蔽原生传 priority: -1。注意 slot-catalog 的「Do NOT pass priority」只适用于动态包(guard 自动分配);静态 bundle 必须自己传。另:mock 验证 slots.register 不会暴露 occupancy 检查(mock 不抛)——验证座位替换必须复刻真实 SlotCore 撞格语义。选择器拆出后,回归测试随代码迁至 ../model-selector-search/tests/slot-priority.test.cjs。DSH_HOME=/tmp/dsh-diag dsh ...):临时实例与主实例共用 ~/.dsh 会并发写同一会话日志与 session_projcache.json——两进程各自的 seq 计数器交错追加,日志出现重复 seq → corrupt session log: seq gap in committed region → 会话 resume 直接拒绝,表现为该会话内模型目录加载失败(选择器「暂无可用模型」)。修复:解压 jsonl 删掉多余事件即可(后续 seq 连续则天然对齐),用 session-persistence-jsonl 的 scanLog 校验后压缩回写;杀进程前务必备份。LlmAdapter 多一个必需的 prepareCall(provider, model, signal) → Promise<{model, stream}>(主分发路径 llm.stream/llm.prepareCall 都先走它再 adapterCall.stream(options);adapter.stream 在 rc.2 服务层从不直调)。缺它的症状极具迷惑性:注册/目录/菜单全正常,真实发对话才报 registration.adapter.prepareCall is not a function。实现对齐 llm-pi-ai 的快照模式:prepare 时捕获一份配置快照,元数据与 dispatch 都出自同一代。回归:tests/adapter-contract.test.cjs(T3 直接解析安装版 d.ts 的 LlmAdapter 方法集做契约同步)。CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。