dsh-deepseek-web-login
Manifest validAdds a deepseek-web provider that uses chat.deepseek.com web models in DSH, with browser login capture, PoW request signing, SSE streaming, and prompting-based tool calls.
这是什么
DSH(DeepSeek Harness)通过 ctx.llm 的 provider 适配器接入模型。本项目实现了 deepseek-web
这个 provider —— 它不调用官方 API,而是复用你已登录的网页版:PoW 挑战、会话、SSE 流式、
文件上传,全部走网页端私有接口。
于是:在模型选择器里选 DeepSeek 网页 · 快速模式,就能用网页版免费额度跑 DSH 的 agent。
DSH agent loop ──▶ ctx.llm ──▶ [deepseek-web 适配器] ──▶ chat.deepseek.com
│ ├─ PoW(SHA3 WASM 求解)
│ ├─ chat_session(每次调用临时会话,用完即删)
│ ├─ chat/completion(SSE patch 流)
│ └─ file/upload_file(图片输入)
└─ 提示词工具协议 ⇄ tool-call 块
界面预览
设置页按用途分成 6 个标签(一次只显示一页)—— 账号(登录状态 / 当前账号 / 账号库 / 手动 Token)· 模型(可用模型 / 连通性测试)· 防风控(请求节流 / 会话清理及其三个区间 / 调用台账)· 传输层(指纹 + 一键测试)· 上下文(每轮全量 / 链式投喂)· 关于(版本与更新 / 数据位置 / 风险说明)。 操作反馈条常驻在标签栏之上,切到哪一页都看得见。
设置页(真实截图,拍于拆页之前):当前账号 / 登录状态(适配器注册、凭证来源、PoW WASM、服务端校验)/ 三种登录方式(Microsoft Edge · 我的默认浏览器 · 从已登录窗口恢复)/ 手动粘贴 token。
DSH「使用统计」里看到的调用量 —— 免费网页通道,当日 1042.8 万 tokens / 164 次调用:
设置页示意图(标注各区块用途):登录状态 / 当前账号(退出当前账号 · 退出并登录其它账号) / 浏览器窗口登录 / 从已登录窗口恢复 / 手动 token / 连通性测试 / 模型列表。
核心能力
| 能力 | 说明 |
|---|---|
| 🔐 网页登录(无 API Key) | Electron 独立分区窗口里正常登录,插件旁路捕获真实 Authorization、cookie、x-hif-* 指纹头与客户端版本头 |
| 🧩 PoW 求解 | create_pow_challenge + DeepSeek 自家 sha3_wasm_bg.*.wasm 的 wasm_solve;WASM 地址自动发现(哈希随部署变化),失败自动回退 |
| 🌊 流式 | 同时兼容 response/fragments(THINK/RESPONSE 片段)与直连 thinking_content/content 两套 SSE 格式,含 {o:"APPEND"} 与裸 {v} 续段;按逻辑流去重,快照重放不重复吐字 |
| 🛠 工具调用 | 网页端没有原生 function calling → 提示词 JSON 协议 + 流式过滤器(跨包标记、围栏、多调用、假阳性回退)→ 合成 tool-call 块并给出 finish: tool-calls;工具定义按 5.6 万字符预算整段下发,超预算时列出被省略的工具名并要求模型别猜参数 |
| 🛡 格式漂移双保险 | ① 指令层显式禁止 XML/DSML 标记并说明后果(实测模型会主动拒绝该格式);② 解析层同时容忍 JSON 与 XML/DSML 两族(\|DSML\| 前缀、dsml- 连字符、裸 <invoke>、CDATA) |
| 🩹 非法 JSON 宽容修复 | 模型常把 Windows 路径写成单反斜杠:\A 是非法转义,而 \r 合法却会把 \resources 静默变成回车。多候选修复链逐字还原路径,解析不出才降级为正文(绝不静默丢内容) |
| 🖼 图片输入 | 走网页端文件上传通道(/api/v0/file/upload_file → ref_file_ids),同一张图在多处出现时自动去重(服务端拒绝重复 id)。实测:上传自造的「左红右蓝」PNG,模型答出「左=红色,右=蓝色」 |
| 🧹 会话卫生 | 每次调用新建临时会话并在结束后删除 —— 实测调用前后网页端会话列表完全一致,不污染你的聊天记录 |
| 🎛 设置面板 | 状态展示、退出当前账号 / 退出并登录其它账号(连带清除浏览器分区登录态)、浏览器登录、从已登录窗口恢复、手动粘贴 token、连通性测试(host HTTP API:/deepseek-web-login/api/*) |
登录与凭证捕获流程(点击展开)
快速开始
1. 安装
# 方式 A:从 Release 的 tgz 装配(推荐,免构建;版本号以最新 Release 为准)
dsh plugin --profile desktop add ./dsh-deepseek-web-login-0.1.3.tgz
# 方式 B:git 装配(本机需可访问 github.com)
dsh plugin --profile desktop add github:cv-superding/dsh-deepseek-web-login
--profile desktop是 DSH Desktop(Electron 应用)使用的 profile。若你跑的是 web profile,换成--profile web。 重启 DSH 后生效(本插件是 bundle 装配,重启即自动加载)。
2. 登录一次
设置 → DeepSeek 网页登录 → 浏览器窗口登录,在弹出的窗口里正常登录(手机号 / 邮箱 / 验证码均可)。 登录窗口报的是干净 Chrome UA(不含 Electron 字样,UA-CH 品牌也清过)—— 否则网页端会判定 「使用环境异常」直接拒绝服务;若仍被拦,用同一行的 用我的默认浏览器登录,按提示用 F12 取 token 粘贴。 捕获成功后窗口自动关闭,面板显示已登录。
- 凭证只存在本机
~/.dsh/web-login/deepseek-auth.json,不在仓库里;面板「当前账号 → 退出当前账号」一键清除(同时清掉浏览器分区里的登录态,保证真退出、可换号) - 凭证丢了或校验不通过:点 从已登录窗口恢复(复用上次登录的持久化分区,无需重新登录)
- 非 Electron 环境(纯 web profile):用面板里的手动粘贴 Token通道
3. 选模型开跑
模型选择器 → provider DeepSeek 网页版(免费) → DeepSeek 网页 · 快速模式,
然后照常用 agent(工具调用、思考流、贴图都可用)。
⚠️ 一个账号同时只开一个聊天窗口:多开并发会触发网页端临时封禁(1 天)——要开多个窗口就换账号,或把多余的窗口换到别的 provider。详见已知限制。
模型档位
权威依据:GET /api/v0/client/settings?scope=model 的 model_configs(按账号返回,实测 configVersion 81)
| model_type | 名称 | enabled | switchable |
|---|---|---|---|
| default | 快速模式 | ✅ | ✅ |
| expert | 专家模式 | ❌ 已停用 | ❌ |
| vision | 识图模式 | ❌ 已停用 | ❌ |
即专家 / 识图已被服务端停用并合并进「快速模式」。因此本插件只暴露两条 —— 它们
不是两个模型,而是同一个「快速模式」的 thinking_enabled 开关两档预设:
| 模型 id | thinking | 适合 |
|---|---|---|
| deepseek-chat | 关 | 工具调用、改写、检索:直接作答,最快、最省额度 |
| deepseek-reasoner | 开 | 数学、多步调试、规划:先推理再作答(推理流作为思考块回传) |
同一档位内也可用推理强度(Off/High)切换;历史里的 deepseek-pro / deepseek-expert / deepseek-vision
会按别名回退到快速模式,不报错。
容量(2026-09-11 逐字段核对 GET /api/v0/client/settings?scope=model,configVersion 81):
- 单请求输入硬上限:
input_character_limit = 2621440字符(≈2.5 MiB 字符) - 附件(file_feature)token 预算:
token_limit = 890880(开不开思考都一样) —— ⚠️ 这个数字不是上下文窗口。曾经把它当上下文窗口写进代码与文档(还写成「1M 扣输出预留」), 而 890880 = 870×1024,面板一按 ÷1024 显示就成了「870K」,看起来像「说好的 1M 缩水成 870K」。 - 上下文窗口:服务端没有这个字段,按 DeepSeek 标称的 1M 取
1048576(服务端自己的数字都是 1024 的整数倍)
本插件 contextWindow 声明 1048576, prompt 字符上限默认 1,200,000(可配)。
配置
插件 entry config(cordis.patch.yml / profile bundles 装配时的 config):
| 字段 | 默认 | 说明 |
|---|---|---|
| maxPromptChars | 1500000 | 送出 prompt 的字符上限(超出走中段截断:保系统提示+工具协议与最近回合) |
| idleTimeoutMs | 120000 | SSE 空闲超时 |
| deleteWebSessions | true | 调用后删除临时网页端会话 |
| autoContinue | true | 回答在句中被截时自动发起新请求续写(无缝拼进同一条回答);截断提示从此不出现 |
| maxContinuations | 2 | 自动续写的最大轮数(每轮是一次新的网页端请求,调高消耗更多免费额度) |
| minRequestIntervalMs | 2000 | 请求间隔区间下限(毫秒),从上次调用结束时刻算起 |
| maxRequestIntervalMs | 4000 | 间隔区间上限;实际等待在 [下限, 上限] 内随机取值(上下限相等=固定间隔) |
| allowConcurrent | false | 是否允许同一账号并发请求。默认串行,多个调用排队(FIFO) |
| sessionCleanup | deferred | 临时会话清理:immediate=结束后 1.5s 删 / deferred=攒批集中清理(默认)/ keep=不删 |
| sessionCleanupDelayMs | 90000 | deferred:从第一个会话入队起最多等多久就清理(区间未设时的兜底标量) |
| sessionCleanupBatchSize | 8 | deferred:攒够多少个立即清理(区间未设时的兜底标量) |
| cleanupBatch | 6~10(随机) | deferred:攒批阈值的区间(个)。这一轮具体攒几个 = 每次清理时在区间内随机抽 |
| cleanupDelayMs | 60000~120000(随机) | deferred:最长等待的区间(毫秒)。每轮清理重抽 |
| cleanupGapMs | 800~2500(随机) | deferred:相邻两个删除请求之间的间隔区间(毫秒)。每删一个重抽 |
| transport | chromium | 传输层:chromium=Electron 的 net.fetch(指纹与真实浏览器一致)/ node=Node 原生 fetch |
| contextMode | full | 上下文投喂:full=每轮重发全量 prompt / chained=只发增量 + 把上一条回答当父消息(见下节) |
| probeIntervalMs | 1800000 | 登录态主动探活间隔(毫秒),0=关闭。只读 users/current,零额度 |
上下文投喂:每轮全量 vs 链式增量
一次 completion 请求的 prompt 是整份对话转写(系统提示 + 工具目录 + 全部历史)。为什么必须这么发?
因为插件一直把 parent_message_id 写成 null —— 每条消息都是网页端会话里的根消息、
没有父链,服务端按消息树回溯上下文时回溯到空。这是 2026-09-12 实测判定过的行为
(同一会话内先发「记住编号 ZC-7391-KX」得 OK,再问编号答「不知道」)。
浏览器不是这么干的。参考实现里 nextParentMessageId = history?.parentMessageId ?? finalAssistantMessageId、
isFirstMessage = parent_message_id === null —— 只有会话第一条的 parent 是 null,
之后每轮都把上一条消息 id 当 parent 发上去,历史由服务端维护。
设置页「上下文」标签可以切到 链式投喂:后续轮只发新增内容,parent_message_id 指向上一条回答的
message_id(取自 SSE 首帧 event: ready 的 response_message_id)。收益是请求体小得多、
更像真人连续对话;代价是工具协议只存在于链首那条消息里,一旦服务端把早期上下文丢掉,
模型可能不按约定格式发工具调用。
所以默认仍是 full(与 0.1.61 及以前完全一致),而 chained 采用「能省则省、一有不确定就退回全量」
的策略 —— 出现下面任何一条就重新起链(发全量 + parent=null,只是多花点 token,不会错位):
| 退回全量的情形 | 为什么 |
| --- | --- |
| 本轮是新会话 / 会话轮换 / 切号 | 链属于某个具体会话,换了就不能续 |
| 固定头(系统提示 + 工具目录)变了 | 链首那份已经过期,续上去模型会照旧定义干活 |
| 历史不是严格追加(被压缩、改写、回退) | 增量算不出来 |
| 本轮新增内容为空 / 增量本身超预算 | 没有值得省的东西,或风险大于收益 |
| 上一轮流失败、被取消、或没拿到 message_id | 父消息可能不存在或已作废 |
判定逻辑是纯函数(src/context-feed.ts),测试在 tests/check-context-feed.mjs(判据)
与 tests/check-context-chain.mjs(接线与生命周期,假 transport + 假 SSE)。
请求节流:为什么需要,值该给多少
网页端对同一账号同时只能生成一条,并发生成会被直接拒绝;实测更严重的是账号级限制: 两个窗口并发生成,不到 6 分钟就触发 1 天的临时限制(登录态没坏,但期间该账号所有请求被拒)。
而 DSH 本身会并发调用同一个账号 —— 从插件日志反推 272 轮调用的起止时间,发现 16 对真重叠:
重叠的一方是主回答,另一方只有 8~17 字、耗时 1~3 秒,那是 DSH 的会话标题生成
(options.purpose === 'session-title')。也就是说你还在等回答时,另一个请求已经发往同一账号了。
所以插件默认做了两件事:串行(一次只放行一条,含标题这类辅助调用)+ 两次调用之间至少间隔 3 秒。
| 场景 | 间隔区间(min~max) | allowConcurrent |
|---|---|---|
| 推荐(默认) | 2000~4000 | false |
| 追速度、只跑短任务 | 1500~2500 | false |
| 已被限流过 / 高密度自动化(多步骤工具调用) | 5000~9000 | false |
| 完全关闭节流(不建议,会恢复并发重叠) | 0~0 | false |
| 实验:还原 DSH 原生并发 | 任意 | true ⚠️ |
为什么是区间而不是固定值:固定间隔的方差≈0,统计上就是明显的「定时器特征」;
同一场景的开源项目 cuckoo-code(从未被风控)用的正是 2000~4000ms 随机区间。
会话清理(为什么默认不是「立刻删」)
一次模型调用要发 4 个请求:建会话 → 取 PoW → completion → 删会话。其中 「每轮新建一个临时会话、用完立刻删掉」是最强的机器行为特征之一 —— 真人绝不会每 30 秒建删一次对话。
为什么不能干脆复用会话:DSH 每次把全量历史交给适配器,而网页端会话是有状态的,
复用会让服务端同时看到「会话自身的历史」和「我们重发的全量 prompt」两份上下文,很快撑爆窗口。
所以只能优化删除侧:deferred(默认)攒够 6~10 个(每轮随机)或最多等 60~120 秒
(每轮随机)后集中清理,并且优先用一个请求批量删(服务端支持的话 N 个会话只花 1 个请求;
不支持则自动回退为逐个删,此后不再尝试)。想要彻底不留记录就选 keep(请求最少,但网页端会留下临时会话)。
为什么这三个参数也是"区间 + 随机":它们原来全是死值 —— 正好攒到第 8 个动手、正好等 90 秒、 逐个删除时请求连发。固定值的方差≈0,本身就是统计上最明显的机器特征(真人不会这么精确)。 所以三个参数各给一对上下限,实际取值在区间内随机抽:
| 参数 | 谁在重抽 |
|---|---|
| cleanupBatch(攒够几个) | 每轮清理重抽一次 |
| cleanupDelayMs(最长等多久) | 每轮清理重抽一次 |
| cleanupGapMs(删除间隔) | 每删一个重抽一次 |
「删除间隔」是为"别一下子连发几十个删除请求":批量删是首选(1 个请求搞定),
但服务端一旦不接受批量删就会退化为逐个删 —— 那时如果连发,几十个删除请求会瞬间打过去,
这比"攒批"本身更像脚本。所以相邻两个删除请求之间会按 cleanupGapMs 停一下;
上限设成 0 就等于"不等待"(回到老行为)。
批量删与逐个删都串行化了:上一轮没删完时,下一次 flush 只会排在后面,不会插进来并发发请求。
间隔按「上一次调用结束」起算,所以长回答(几十秒)之后不会额外白等 —— 它只在 「结束 → 下一个开始」这段真正密集的空隙里起作用。 两种改法:① 设置页的「防风控」页里直接调 —— 并发开关 + 间隔滑块 + 三个快捷档位 + 会话清理模式与三个清理区间滑块,改完即时生效并自动落盘;② 写在插件 entry config 里(改完需重启 DSH)。 优先级:设置页保存的值 > entry config > 内置默认(设置页是显式操作,不会被配置文件里的旧值盖回去), 落盘位置
${DSH_HOME:-~/.dsh}/web-login/gate.json。「登录状态」卡里也会显示当前生效值。
传输层(指纹):默认走 Chromium 网络栈
网页端请求的网络栈决定了「在服务端眼里你是浏览器还是一个脚本」。实测三方对比(同机同日):
| | JA4 | cipher 列表哈希 | ALPN |
|---|---|---|---|
| Node fetch(undici) | t13d5212h1_… | — | h1 |
| Chrome(本机 152) | t13d1517h2_8daaf6152771_cb7bf5808d99 | 8daaf6152771 | h2 |
| 默认:net.fetch(Electron 43) | t13d1516h2_8daaf6152771_806a8c22fdea | 8daaf6152771 | h2 |
Node 的请求在 TLS 层就能被判定为非浏览器(不走 HTTP/2、cipher 数量差 3 倍多、不带 GREASE),
而且这几项调参修不了。改用 Electron 的 net.fetch 后走 Chromium 内置网络栈,
cipher 列表哈希与 Chrome 逐字节一致 —— 且零新依赖(不用 uTLS / curl-impersonate)。
唯一残留差异是扩展数 16 vs 17(内置 Chromium 150 vs 本机 Chrome 152,版本差异,属正常)。
设置页「传输层(指纹)」卡可以直接切换,并带一个零额度的一键测试 (回显指纹 / 流式 / 鉴权三项结论)。
⚠️ 切到 Chromium 后请求会跟随系统代理(Node 则完全无视代理)。
如果梯子关闭时系统代理仍指向 127.0.0.1:7897,请求会失败 —— 这时切回 node 即可。
账号库:多账号并存与一键切换
凭证失效时不用自己找路:探活失败的账号会直接标成「需要重新登录」,那一行上就有 「重新登录这个账号」按钮。它与「登录新账号」的区别只有一点 —— 不清浏览器登录态: 「登录新账号」必须先清干净(否则新窗口还是旧账号,抓回来还是它),而修同一个号正相反, 留着才可能一打开就复用上,一个密码都不用敲。修完原地更新那条记录,不改变当前正在用的账号。
cookie 的过期构成会记下来:捕获时顺手存下每个 cookie 是会话级还是持久级、最晚什么时候到期,
界面上写成 5 项 · 1 会话级 · 4 持久级 · smidV2 还剩 399 天。
⚠️ 它不是登录态寿命 —— 实测真正鉴权用的是 token(只发 token 不带 cookie 能通过,
只发 cookie 不带 token 直接被拒 40002 Missing Token),所以这只是浏览器侧的上界。
老记录 / 手动粘 token 的账号没有这份信息,界面会写「未记录(重新登录后会补上)」。
保存过的账号都在本机 ~/.dsh/web-login/accounts/,设置页「账号」标签里可以一键切换、
改备注名、移除、导出/导入备份(都弹系统对话框,自己选位置和文件)。以前换号的代价是「退出 → 清浏览器分区 → 重新登录 → 等捕获」,
现在切换即时生效(下一次请求就用新账号)。
换账号不会丢对话、也不会让模型失忆 —— 这是个常见的担心,但在这套架构下不成立: 对话记录存在 DSH 本地、每一轮请求都把整段历史完整重发、网页端不留任何会话。 账号只是"通行证 + 额度归属",换它不影响对话内容。(真正会"失忆"的是在 DSH 里开一个新会话。)
⚠️ 为什么没有「自动换号」:账号库只提供手动切换,刻意不做"检测到限流就自动换一个号继续发"。 真人不会在几分钟内换一个账号接着发消息 —— 那是极强的机器行为特征,与本文档里 传输层指纹、随机间隔、会话清理这些"降低机器可识别性"的努力直接冲突。 另外同一服务商会把多账号关联起来(同设备 / 同 IP / 同指纹 / 相近行为), 一旦被判定为"同一人的多开小号",处置通常比单账号超频更重。 账号库的目标是"在自己的多个正常账号之间切换更省事",不是"靠轮换把限流绕过去"。 导出的备份文件里是可完整登录的凭证,等同于账号本身 —— 别分享、别提交到仓库。
「登录新账号」和「退出」的区别(很容易搞混,但后果完全不同):
- 登录新账号(添加) —— 只清掉浏览器里的登录态,然后把新账号加入账号库但不切换。 你正在用的号不受影响;加完在列表里点「切换」才会用它。(多账号并存靠它。)
- 退出 —— 等于把该账号从账号库移除:本地凭证与浏览器登录态一起清掉,不是"只登出"。 想留住它就先「导出备份」。
另外「账号」页分成两个子页(登录状态 / 账号库),一次只看一半,不用滚很久。
导出/导入怎么选文件:点「导出备份…」弹系统另存为,位置与文件名自己定; 点「导入备份…」弹系统打开框选文件 —— 都不用再手打路径(导入也不再要求你先把路径抄出来)。 两条路都有回退:万一当前环境拿不到系统对话框,导出会写进插件目录并在界面回显完整路径, 导入会改为由界面读文件内容后交给宿主 —— 功能不会因此失效,只是路径由插件决定。 导入优先走"只把文件路径告诉宿主、宿主自己去读",所以凭证明文通常并不经过 HTTP。
调用台账:证明节流真的在起作用
「防风控」标签底部有一张台账卡,按天记录每次调用的结果(只留近 7 天、只记元信息, 不含任何对话内容与凭证),看两个数:
- 相邻对话间隔(中位 / p90 / 最短):固定间隔方差≈0 是"定时器特征";最短间隔尤其说明问题
—— 它直接对应"有没有连环请求"。间隔只统计
purpose === 'chat',会话标题生成是 DSH 自己发的 旁路请求,算进去会让分布失真。 - 失败分类:限流 / 账号被限制 / 登录态问题 / 网络,各占多少。只看"失败了"没用, 要知道是哪一类才谈得上对策。
登录态探活与受限倒计时
- 探活:启动后 20 秒 + 之后每 30 分钟(
probeIntervalMs)用只读的users/current确认登录态 还有效,目的是在任务跑到一半之前发现过期。零额度、可关、失败只提示不阻断。 - 受限倒计时:账号被临时限制时(
user is muted),设置页显示「还剩 X 小时 Y 分」。 注意这个状态只能从生成请求被拒里学到 —— 受限期间只读接口依然返回 200,探活探不出来。
已知限制
- 同账号同时只能开一个聊天窗口:网页端按账号限制并发生成,多开会触发服务端的临时封禁(1 天)——登录态没坏,但期间该账号所有请求都会被拒。要多窗口就换账号,或把多余的窗口换到别的 provider
- 原生 tools 不存在:工具调用靠提示词协议;模型偶发格式漂移已被解析器与指令双重兜住,但本质是模型行为,无法 100% 保证
- 工具目录有预算上限:DSH 下发的工具定义会尽量全部写进 prompt(0.1.33 前只有 2.4 万字符预算,实测 61 个工具时静默砍掉了 26 个)。工具特别多或描述特别长时仍可能装不下,此时会把没描述到的工具名列出来,让模型向用户确认参数,而不是默默砍掉
- 单次请求 60s 上限(
completion_request_timeout_ms):网页端靠sse_auto_resume续接,本插件不实现续接;流在没有FINISHED标记的情况下结束时报max-tokens,而不是假装正常完成 - 思考模式的推理过程不进上下文:历史序列化只回放正文与工具调用/结果,以省 token
- 图片:走上传通道(
/api/v0/file/upload_file→ref_file_ids)。上传失败时降级为[image attached]文本标记,并在回答开头明确告知「有 N 张图片没能传给模型(原因)」 —— 图丢了不会再无声无息(0.1.66 前只写日志,界面上看不出来)。同一张图在历史里出现多次(用户消息 +read_image工具结果内嵌)时ref_file_ids自动去重:服务端不接受重复 id(biz_code 9 / invalid ref file id),被拒后整条会话后续每轮都会失败。上传时文件名必须带受支持的图片后缀(png / jpg / jpeg / webp / gif):服务端是按文件名后缀判类型的,multipart 里的content-type说了不算 —— 而宿主给read_image这类工具结果的name是纯 sha256、没有后缀。0.1.68 起由imageUploadName()统一归一回image.<ext>(0.1.67 及以前:凡是经工具返回的图,一律传不上去) - DSH 渲染层把单个
$当行内公式(不是本插件的行为):DSH 前端的 markdown 默认开singleDollarTextMath,所以含$的文本会被渲染成公式 —— 现象是$消失、-变成−(U+2212)、|变成∣(U+2223),字母被逐个拆行而数字串(如256)仍连在一起。PowerShell / bash 命令首当其冲,看起来极像「模型输出了乱码」。判据:原文能完整复原 ⇒ 不是模型退化(退化会丢信息,编码/渲染错只是把信息换了个样子)。规避:讨论命令时套围栏代码块或行内反引号 —— 代码构造里不跑数学扩展。 temperature/stop/max_tokens网页端无对应字段,会被忽略;usage 为估算值(网页端不返回 token 计数)- 免费额度有频控;
429会带上providerRetryAfterMs交给 DSH 的重试策略 describe_image是 DSH 侧另一个独立工具(调用外部视觉模型),与本插件无关;本插件的图片能力不依赖它- 默认走 Chromium 网络栈(Electron 的
net.fetch),TLS/HTTP2 指纹与真实浏览器一致; 但它会跟随系统代理,梯子关着而系统代理仍指向它时会连不上 —— 设置页「传输层(指纹)」切回 Node 即可
测试与验证
node tests/logic-test.mjs # 111 项断言(8 个测试文件)(序列化 / 工具过滤 JSON+XML / JSON 修复 / SSE / token 解包 / 掩码)
node tests/probe-live.mjs # 线上直连探针:原始 SSE 事件流 + 时长(--big=N 验证长 prompt)
node tests/probe-xml-live.mjs # 线上验证 XML 标记场景(指令劝阻 + 解析兜底)
node tests/probe-vision.mjs # 线上验证图片通道(自造左红右蓝 PNG → 上传 → 提问)
node tests/probe-batch-live.mjs # 线上复现事故 #4 的触发条件(深度思考 + 批量 3 条带 $env:/Windows 路径的命令)
node tools/changelog-section.mjs # 从 CHANGELOG 取某版本段落(发布流程复用)
node tests/check-bundle.mjs # 产物核对(关键修复是否都进了 lib)
node tests/check-injector-guards.mjs # 复核注入器注入前校验的正则
node tests/check-fetch-injection.mjs # 传输层注入必须"每次现取"(防单测静默打到线上)
node tests/check-net-diagnostics.mjs # net.fetch 诊断通道(标记文件生命周期 + 流式探针正反向)
node tests/check-transport.mjs # 传输层选择(降级判定 + 注入层真的跟着变)
node tests/check-context-feed.mjs # 上下文投喂判据(增量/回退的五种情形)
node tests/check-context-chain.mjs # 链式投喂接线与生命周期(假 transport + 假 SSE)
node tests/check-image-refs.mjs # 图片引用组装(同一张图去重 + 图丢了要写进回答)
node tests/probe-upload-name.mjs # 真机 A/B:文件名后缀如何影响上传(需要已登录凭证)
node tests/check-account-sync.mjs # 账号库自动同步(重读节拍 + 内容签名:变了才重建列表)
node tests/check-accounts.mjs # 账号库(去重/切换/移除/导入导出/旧文件迁移)
node tests/check-smoke.mjs # 新模块能否被独立加载(循环依赖 / 版本号漂移)
CI(.github/workflows/ci.yml)在每次推送到 main 与每个 PR 上跑上面两条命令;
打 v* tag 由 .github/workflows/release.yml 自动创建 Release 并附上 tgz(用仓库自带的 token,维护者无需持有个人令牌)。
多条断言直接固化自真实事故现场:
- 工具调用里含未转义 Windows 路径,曾导致解析失败、标记泄漏成正文 → 现在必须解析成功且路径逐字还原
- SSE 去重模型错误,曾把完整回答丢成「,」「不上」「了一圈」这类 1~3 字碎片(并触发 EMPTY_RESPONSE 重试) → 现在「缩水快照」与「分歧快照」都必须被忽略,回答一字不丢(见 CHANGELOG.md 0.1.1)
- 跨包工具调用标记的 hold-back 判断失效,曾让合法 JSON 泄漏成正文(分块把
{"tool与_calls":…切开时) → 现在用真实会话日志的分块序列回归(见CHANGELOG.md0.1.2) - 模型漏写调用对象的闭合括号(批量调用时每个少一个
}),曾让整段调用 JSON 泄漏成正文 → 现在结构性补括号(仅当数组已闭合,被截断的流绝不补)+ 解析失败不再吐成正文(见CHANGELOG.md0.1.3) - 会话被自己提前删除(建会话后立刻排定 1.5s 后删除 → PoW+建连超时就删掉了正在用的会话)
→ 现在删除只发生在流结束之后;会话失效还会换新会话透明重试(见
CHANGELOG.md0.1.4) envelopeError只看外层code,把data.biz_code里的真实错误吞掉(invalid chat session id/user is muted都被降级成看不懂的「非流式响应」) → 现在识别biz_code,并区分「可恢复」「需等待」「重试没用」三类(见CHANGELOG.md0.1.4)
故障排查
| 现象 | 处理 |
|---|---|
| 面板「未登录」但登录窗口里已登录 | 点「从已登录窗口恢复」;或看 loginProgress.lastError |
| AUTH / 40003 Authorization Failed | 登录态过期 → 重新登录或恢复 |
| MISSING_CREDENTIAL | 凭证文件不存在(~/.dsh/web-login/) |
| EMPTY_RESPONSE | 可能触发频控或长上下文截断,属于默认可重试码 |
| RATE_LIMIT | 免费额度频控,稍后重试 |
| 报错里带「临时限制」/ user is muted | 账号被网页端临时限制(不是插件问题):登录态有效、建会话也正常,只有发消息被拒。消息里会给出解除时间;等解除或改用其它账号/官方 API key |
| 「浏览器窗口登录」点了没反应,host 日志有 fromPartition 报错 | DSH 把插件宿主挪到了 utility 进程(没有窗口 API)→ 0.1.7 起改用真实 Edge/Chrome + CDP 登录:面板会显示「宿主进程」,按钮变成「用 Microsoft Edge 登录」。升级插件 + 重启 DSH 即可 |
| 面板显示「Cookie / 指纹头 未捕获」 | 说明你走的是手动粘贴 token 那条路(该路径本来就没有这两项)。实测仅凭 Bearer token 即可完成校验、PoW 求解与真实生成;若日后频繁遇到 AUTH / 40003,改用「浏览器登录」获取更完整的凭证(token + cookie + 指纹头) |
| 报错 A message is being generated, please try again later. | 这个报错本身不是封号:同一账号同时只能生成一条消息(另一个窗口/标签页正在生成),0.1.9 起自动重试。但别把多开当常态 —— 多窗口并发会触发服务端的临时封禁(1 天),见已知限制;建议一个账号只保留一个窗口,多开请换账号或换 provider |
| 正文里出现 <ds_system>…</ds_system> / <system>…</system> / <ide_result_status>…</ide_result_status> | 模型在模仿系统消息格式(与转写回声同类)。这些标签 DSH 从来不产生(app.asar、已装插件、~/.dsh 原始字节搜索 0 处;会话日志里只出现在模型的输出字段),所以一律当垃圾剥离、不上屏。清单只收有现场证据的名字,不做通配 —— 否则会吃掉正常回答里讨论这些标签的段落 |
| 回复在句中截断但没报错 | 服务端在句中截断、却仍然发了 FINISHED(实测 12s 内即断,不是 60s 上限)。这类截断没有可靠信号,只能靠启发式判断尾部字符:以「,」「、」「;」「:」(中英文逗号/顿号/分号/冒号)收尾 = 明显未写完 → 自动续写;以「。」「!」「?」「)」「」」等句末标点收尾 = 认为正常结束(… 也按正常结束处理,因为省略号也可能是有意的收束语气)。判定为未完时会自动发起新请求接着写并拼进同一条回答,不再出现「可能被截断」提示。真被切断(没有 FINISHED)时走另一条判据,不受上面的尾部规则影响 |
| 回复里出现 {"tool_calls":…} 或 <tool_calls> 标记 | 模型格式漂移。解析器已两族兼容 + 修复兜底;若仍出现请把原文贴进 issue(解析不出时不再把 JSON 吐进正文:本轮无其他正文则自动重试,已有正文则给一句提示) |
| 加完账号,账号库列表里没出现 | 面板每 3 秒会自动重读一次账号库(登录流程进行中),捕获一落地就会自己出现 —— 不用关掉设置页再打开(0.1.67 前必须重开)。空闲时是 30 秒一次,用来让探活补上的账号名 / 限制状态 / 失败标记自己更新。列表内容没变时不会重建 DOM,所以不会打断你正在点的按钮 |
| 工具调用不触发 | 换说法或换 deepseek-reasoner;也可用面板「发送测试」确认链路 |
诊断工具:tools/inspect-session.mjs
怀疑「回复不对」时,不用猜 —— 直接读 DSH 的会话日志,看每一轮的原始事实: 用了哪个模型、内容块的真实长度与首尾、结束原因、以及流式分块的类型与字节数。
node tools/inspect-session.mjs # 列出会话(时间 / 大小 / 事件数 / 标题)
node tools/inspect-session.mjs <会话ID前缀> # 诊断该会话的每一轮
node tools/inspect-session.mjs --search "关键词" # 按关键词找会话
它只读本机会话日志(不联网、不上传)。典型输出:
[14:04:01] 模型: deepseek-web/deepseek-chat ctx=1048576
[14:04:01] 分块原文(文本): ["I'll check what plugins exist"," for this in the DSH ecosystem",
", and also look at the current"," GUI's capabilities.\n\n{\"tool",
"_calls\":[{\"name\":\"find_dsh", ...]
[14:04:03] 助手消息(1 块): text(len=257) head="..." tail="...\"lang\":\"zh\"}}]}"
三条经验判据:
- 文本长度 远小于 分块字节数 → 解析层丢字
- 文本里出现
{"tool_calls":…}/<tool_calls>→ 工具调用没被接住(格式漂移或 hold-back 失效) - 同一 step 出现 多次
usage/finish→ 触发了重试(通常是空响应)
报 bug 时请附这段输出(它不含凭证;如有敏感内容请先自行删减)。 上面三个真实事故(工具调用 JSON 泄漏、回答丢成碎片、合法 JSON 泄漏)都是靠它定位的。
诊断:传输层(net.fetch / TLS 指纹)
网页端请求默认由 Node 的 fetch(undici)发出,指纹与真实浏览器结构性不同。
想确认「换成 Chromium 网络栈」是否可行时,跑一次探测 —— 默认零额度,不生成、不发消息:
# 宿主进程的 HTTP 端点只有 DSH 自己的页面打得通(外部 curl 会撞同源守卫),所以走文件这条路:
echo '{"mode":"probe"}' > "$HOME/.dsh/web-login/probe-request.json"
# 重启 DSH,日志里会打出 deepseek-web: [net-fetch 探测] {...}
三步各看一个结论:
| 步骤 | 看什么 | 判定 |
|---|---|---|
| ① 指纹 | ja4 / http_version / http2_hash | 变成 t13d…h2… 且带 GREASE = 与 Chrome 一致 |
| ② 流式 | hasBody / chunks / abortedEarly | 三项都为真才能读 SSE(否则改造路线不成立) |
| ③ 鉴权 | status / body | 200 且能读出账号 = header/cookie 原样透传 |
(设置页「传输层(指纹)」卡里也有一键测试,走的就是这个接口。)
想连 DeepSeek 的 SSE 一起端到端验证(会消耗一点额度):把 probe 换成 stream。
文件被读取后会改名为 probe-request.json.done-<时间戳>,不会每次启动都重跑。
开发
npm ci # 安装开发依赖(首次 / 换版本后)
node scripts/build.mjs # 构建 host(lib/index.js) + client(lib/client.js)
node scripts/test-offline.mjs # 全量离线用例
node tests/check-bundle.mjs # 产物核对
node scripts/make-dev-copy.mjs <后缀> # 生成开发副本(见下)
构建走本地已安装的 tsdown(不再 npx 联网下载、不再要求 Bash,Windows 直接可用);
缺依赖时会明确报错并提示执行 npm ci。
迭代注意(实测坑):当前 DSH 版本移除了热重载所依赖的 loader API,而 Node ESM 模块缓存以
「解析后的文件路径」为键 —— 同一路径重新注入仍会命中旧模块实例。改代码后需换包名/换路径注入:
node scripts/make-dev-copy.mjs a1 生成副本,配合 DSW_PLUGIN_ID 同步改 client 模块 id。
装配要点:包内 cordis.patch.yml 会自注册 entry,不要再手动 insert(会撞 duplicate loader entry id);
DSH Desktop 用的是 desktop profile,而注入器的 junction 默认建在 profiles/web/,必要时手动补 junction。
文档索引
| 文档 | 内容 |
|---|---|
| README.en.md | English README |
| LICENSE · NOTICE | Apache-2.0 全文 · 版权与第三方声明 |
| docs/assets/ | 本文所有示意图(SVG 源文件,可自行改) |
| src/protocol.ts | 工具协议与流式过滤器(含每条修复策略的注释与实测案例) |
| src/webapi.ts | PoW / 会话 / SSE / 文件上传(含协议字段与端点注释) |
| src/login.ts | Electron 登录窗口与凭证捕获(含 AppKit token 解包等踩坑注释) |
贡献
欢迎 Issue / PR。提交前请先跑一遍上面的测试命令。请注意:
- 不要在 issue、日志或截图里附带 token / cookie 等凭证
- 报告工具调用格式问题时,请贴模型原始输出(含标点与反斜杠),那是最有价值的线索
致谢(协议情报来源)
本插件实现为原创代码,但网页端私有接口的行为(PoW 与 WASM 求解约定、SSE patch 流结构、
文件上传与 ref_file_ids、DSML/XML 工具标记变体)参考了以下公开项目的文档与实现,并逐项实测验证。
这些项目的源码未包含在本仓库中:
- LLM-Red-Team/deepseek-free-api —— 最早的网页端逆向 API 实现
- Fly143/deepseek-free-api —— PoW 求解器、DSML 工具标记、SSE 格式注释
- ForgetMeAI/FreeDeepseekAPI —— 浏览器登录捕获、PoW WASM 调用约定
同时感谢 DeepSeek Harness 生态与 dsh-super-injector(运行时注入 / 侧挂开发链路)。
免责声明
⚠️ 非官方项目:与 DeepSeek 无任何关联,未获其授权、认可或赞助。"DeepSeek" 为其权利人商标。
⚠️ 使用风险自负:本插件调用 chat.deepseek.com 的网页端私有接口(非官方 API), 可能违反其服务条款,并可能导致账号被限流或封禁。请自行评估、遵守其条款,仅供学习研究与个人使用。
⚠️ 凭证安全:本仓库不含任何凭证;登录态由你在本机登录后捕获,存放于
~/.dsh/web-login/。⚠️ 按现状提供:接口随时可能变更导致失效,作者不提供任何担保(见 Apache-2.0 §7)。
许可证
Apache License 2.0 —— 含专利授权与专利报复条款;不授予商标权(§6)。版权与第三方说明见 NOTICE。
交流群
用法讨论、蹲更新,或者踩到坑想吐槽,欢迎加 QQ 群:
QQ 群号:1124773537
Similar plugins
by NattoCB
DSH web_search provider that follows the conversation's routed model provider — per-provider endpoint/credential/dialect table with built-in official fallback
★ 0
MIT
JavaScript
Sep 5, 2026
dsh plugin --profile web add dsh-web-search-session-followby 2275803244-cpu
One-click provider/model switcher client plugin for the DeepSeek Harness web GUI
★ 0
MIT
JavaScript
Sep 9, 2026
dsh plugin --profile web add dsh-client-ui-model-switcherby xmuwenxiang
Deepseek Harness插件,可以直接在Deepseek Harness中使用网页版deepseek进行聊天,并将聊天过程直接迁移到Deepseek Harness进一步进行开发。主要目的是节省前期方案讨论时的token
★ 11
↓ 72/wk
Apache-2.0
TypeScript
Sep 7, 2026
dsh plugin --profile web add dsh-webchatby hiyms
Improve web search in Deepseek Harness, use server-side search service.
★ 4
↓ 124/wk
MIT
TypeScript
Sep 1, 2026
dsh plugin --profile web add dsh-web-search-providerby loommii
DSH web_search 工具的可配置化增强版:model/baseURL/auth 全可配
★ 0
JavaScript
Aug 29, 2026
dsh plugin --profile web add @loommii/dsh-web-search-plusby Ychris12138
Provider balances, subscription quotas, and token-usage analytics for the DeepSeek Harness Web GUI (dsh web).
★ 156
MIT
JavaScript
Sep 12, 2026
dsh plugin --profile web add @ychris12138/dsh-usage-stats