🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

请求报错落盘 exynos967/dsh-request-error-dump

DeepSeek Harness
exynos96740 天前有提交已提供中文文档扫描:含敏感能力

模型请求失败时保存原始请求与错误响应报文

29.5/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add exynos967/dsh-request-error-dump
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · dev
装得上吗
本站已真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 40 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 5 处,展示前 5 条)
  • 写入本地文件
    src/dump-store.js:58
    dirReady = mkdir(outDir, { recursive: true }).catch((error) => {
  • 写入本地文件
    src/dump-store.js:144
    await writeFile(tmp, text, 'utf8');
  • 写入本地文件
    src/dump-store.js:145
    await rename(tmp, path);
  • 读取本地文件
    src/dump-store.js:175
    const names = (await readdir(outDir)).filter((name) => name.endsWith(FILE_SUFFIX)).sort();
  • 删除本地文件
    src/dump-store.js:178
    await Promise.all(names.slice(0, excess).map((name) => rm(join(outDir, name), { force: true })));
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 保存请求体原始JSON
  • 保存上游错误响应
  • 请求失败时落盘

DSH 插件:模型请求报错时,把完整请求体 raw JSON 和上游错误响应 raw JSON 一起写入文件

README

由 DeepSeek 最新模型翻译生成
dsh-request-error-dump DSH 插件:模型请求报错时,把这一次的完整请求体 raw JSON 与上游返回的 raw JSON 一起落盘。 上游 401 / 403 / 429 / 400 到底回了什么,DSH 界面通常只给你一句被归一化过的 "本轮运行失败"。本插件把原始报文留下来,方便事后对照。 它记录什么 每次失败写一个 JSON 文件,包含四块内容: | 区块 | 内容 | | request | 请求 URL、方法、请求头(默认脱敏)、请求体原文 body.raw 与解析后的 body.json | | response | 状态码、状态文案、响应头、上游响应原文 body.raw 与 body.json | | transportError | 连接被拒、DNS、TLS、超时等传输层异常(此时 response 为 null) | | dsh | DSH 侧上下文:turn / step / provider / failure.{message,code,status,requestId} / agent 与会话 id / correlation(本次配对靠哪条信号,见下) | 真实样例(截断): { "schema": "dsh-request-error-dump/v1", "kind": "http-error",
展开完整文档(README 剩余 1 节)
dsh-request-error-dump

DSH 插件:模型请求报错时,把这一次的完整请求体 raw JSON 与上游返回的 raw JSON 一起落盘。

上游 401 / 403 / 429 / 400 到底回了什么,DSH 界面通常只给你一句被归一化过的 "本轮运行失败"。本插件把原始报文留下来,方便事后对照。

它记录什么

每次失败写一个 JSON 文件,包含四块内容:

| 区块 | 内容 |
| request | 请求 URL、方法、请求头(默认脱敏)、请求体原文 body.raw 与解析后的 body.json |
| response | 状态码、状态文案、响应头、上游响应原文 body.raw 与 body.json |
| transportError | 连接被拒、DNS、TLS、超时等传输层异常(此时 response 为 null) |
| dsh | DSH 侧上下文:turn / step / provider / failure.{message,code,status,requestId} / agent 与会话 id / correlation(本次配对靠哪条信号,见下) |

真实样例(截断):

{
"schema": "dsh-request-error-dump/v1",
"kind": "http-error",
"capturedAt": "2026-09-22T05:27:46.919Z",
"durationMs": 15,
"request": {
"url": "https://api.deepseek.com/chat/completions",
"method": "POST",
"headers": { "content-type": "application/json", "authorization": "" },
"body": {
"present": true,
"bytes": 83,
"truncated": false,
"raw": "],\"stream\":true}",
"json": { "model": "deepseek-chat", "messages": [{ "role": "user", "content": "hi" }], "stream": true },
"source": "string"
}
},
"response": {
"status": 401,
"statusText": "Unauthorized",
"headers": { "content-type": "application/json", "x-request-id": "a1b2c3d4" },
"body": {
"present": true,
"bytes": 130,
"truncated": false,
"raw": "}",
"json": { "error": { "message": "Authentication Fails, Your api key is invalid", "type": "authentication_error", "code": "invalid_request_error" } }
}
},
"transportError": null,
"dsh": {
"turn": 4,
"step": 1,
"provider": "deepseek-official",
"agent": { "id": "main", "sessionId": "main-session-abc" },
"failure": { "message": "Authentication Fails", "code": "AUTH", "status": 401, "requestId": "a1b2c3d4" }
}
}

body.raw 是逐字节的原文,body.json 只是同一份文本的解析视图,方便直接阅读。
用 bodyFormat: raw 可以只留原文,用 bodyFormat: parsed 可以只留解析结果。

开箱即用:默认只抓模型请求

零配置。装好重启后,只有模型(LLM)请求失败才会 dump 到 ~/.dsh/request-error-dump/。

怎么判定"是模型请求"

两条信号,取并集(宁可多抓,不可漏抓):

1. 权威信号 —— agent/request-error 认领:DSH 循环在每次模型请求尝试失败时派发该事件。
被认领 = 确定是模型请求,无论 URL 长什么样;
2. 兜底信号 —— 请求结构:POST 且 JSON body 里带字符串 model / modelId,或 URL 命中少数"模型写在路径里"的形态(Gemini :generateContent、Bedrock /model//converse)。这一条用来救不走 agent 循环的模型调用(比如会话标题生成)。

为什么不用 URL 白名单?因为会静默漏抓——自定义网关的路径五花八门,调试工具最怕漏。
所以这里刻意反过来:URL 只列了少数几类,其余全靠 body 里的 model 字段和事件认领。

两个时序细节

- 不是瞬间落盘:默认延迟 flushDelayMs: 1500(1.5 秒)。这个窗口用来等 agent/request-error 来认领并补上 turn/step/provider/failure;窗口内没被认领、又不像模型请求的,直接丢弃(这就是过滤噪音的机制)。设 0 则立即写、不等上下文;
- 只抓走到 HTTP 的失败:密钥没配、路由没有适配器(NO_ADAPTER)这类发请求之前就失败的错误不会产生 dump。

默认排除的端点

excludeUrls 默认含 ['/user/balance']:DSH 桌面版的余额组件每分钟轮询一次它,密钥失效时一分钟一个 401,50 个保留位约 50 分钟就被占满。模型门禁本来就会挡掉它,这里再列一次是双保险——万一你设了 modelRequestsOnly: false,它仍然不会被记录。

- id: request-error-dump
config:
excludeUrls: ['/user/balance'] # 默认值,可按需增删

为什么监听器要 prepend

agent/request-error 是 waterfall:不调用 next() 的监听器会否决整条链。
dsh-llm-retry 在接管重试时正是返回 { kind: 'retry' } 而不调 next()。
如果本插件按默认顺序注册在它之后,被重试的失败就永远轮不到我们记录。
所以监听器用 { prepend: true } 注册在最前,自己再 return next() 把控制权交回去——
既不会漏,也不会干扰别人的恢复逻辑(已用真实 Cordis 跑过否决场景验证)。

为什么必须在 wire 层做

DSH 的适配器抛出的错误会被 normalizeLlmFailure 归一化,只保留 message / code / status / providerRetryAfterMs / requestId —— 上游响应原文在这一步就被丢掉了。所以任何只挂在 agent/request-error 或 llm/stream 上的观察者都拿不到 raw 报文。

真正还同时存在“请求体原文”和“上游响应原文”的地方,只有适配器发起 HTTP 请求的那一刻。
本插件因此包装进程全局的 fetch:

- 成功路径完全透明:2xx 直接原样返回,响应体一个字都不读,零开销;
- 失败路径:非 2xx 时先 response.clone() 再读取,调用方自己的响应体仍然可读;传输层抛错时记录异常并原样重抛;
- 捕获回调里的任何异常都会被吞掉——记录失败绝不等于请求失败。

安装

本插件零运行时依赖,可直接从目录安装。

从 GitHub 安装(推荐)
dsh plugin --profile desktop add github:exynos967/dsh-request-error-dump

本地目录(开发用)
dsh plugin --profile desktop add link:D:/dsh-workdir/test/dsh-request-error-dump

装完需要重启 DSH(dsh.profile.bundles 在启动时读取)。

版本提醒:本插件已在 Cordis 4.0.2(DSH 桌面版内置的 @deepseek-ai/cordis)上完成挂载验证。它只使用 ctx.on、ctx.effect 与进程全局 fetch,不导入任何 @deepseek-ai/ 包,因此不挑 profile,也不受 DSH 小版本升级影响。

配置

所有配置项都有可用默认值,不配也能跑。写在 profile 的 cordis.patch.yml 里覆盖该行:

- id: request-error-dump
config:
outDir: ~/.dsh/request-error-dump
maxFiles: 50
maxCaptureBytes: 0
bodyFormat: both
redactHeaders: ['authorization', 'api-key', 'cookie']
flushDelayMs: 1500
ignoreAborted: true
modelRequestsOnly: true
excludeUrls: ['/user/balance']
log: true

| 选项 | 默认值 | 说明 |
| outDir | $DSH_HOME/request-error-dump(即 ~/.dsh/request-error-dump) | 落盘目录,支持 ~ |
| maxFiles | 50 | 保留最新的 N 个 dump,更早的自动清理;0 表示不清理 |
| maxCaptureBytes | 0 | 单个体积上限(字节);默认 0 = 不截断,保持完整。截断时会标记 truncated: true 并跳过 json 视图 |
| bodyFormat | both | raw 只留原文 / parsed 只留解析结果 / both 都留 |
| redactHeaders | authorization、proxy-authorization、api-key、x-api-key、x-auth-token、x-goog-api-key、cookie、set-cookie | 要脱敏的请求头/响应头名;设为 [] 即完全原样 |
| flushDelayMs | 1500 | 延迟落盘窗口,用来等 agent/request-error 补上 DSH 上下文;0 表示立即写、不等上下文 |
| modelRequestsOnly | true | 只记录模型请求。true 时:被 agent/request-error 认领的,或请求结构像模型调用的(POST + body 带 model/modelId,或 URL 命中 Gemini/Bedrock 形态)才写;其余在窗口结束时丢弃。设 false 恢复“抓所有失败的 HTTP” |
| excludeUrls | ['/user/balance'] | 正则列表,对 "METHOD URL" 求值,命中即不记录(在模型门禁之前生效)。设为 [] 取消默认排除 |
| ignoreAborted | true | 用户主动取消(AbortError)不记录 |
| log | true | 每次落盘在 host 日志里打一行提示 |

落盘与保留
- 文件名形如 2026-09-22T05-27-46-919Z-0001.json,时间戳前缀保证字典序即时间序;
- 先写 .tmp 再 rename,不会出现半截文件;
- 超过 maxFiles 后按时间从旧到新清理;并发写入的清理串行化,不会误删新文件;
- 插件卸载/重载时会把还在等待窗口里的记录刷完,不会丢最后一次报错。

⚠️ 隐私边界

请求体里就是你的完整上下文:系统提示词、源码、工具结果、对话历史,可能还有你贴进去的密钥。插件默认只脱敏请求头里的凭据,正文一律原样保存。

- 别在不可信机器上长期开启,也别把 outDir 指向同步盘;
- 分享 dump 给他人或提交 issue 前请人工检查;
- 想连请求头也原样保留,设 redactHeaders: [](危险,默认不这么做)。

它不做什么(诚实边界)

- “模型请求”的判定不是 100% 精确:包装层是进程全局的,判定靠事件认领 + 请求结构。
极端情况下(自定义网关路径 + body 里没有 model 字段 + 该调用又不走 agent 循环)可能漏抓,此时把 modelRequestsOnly 设为 false 可退化为全抓;
- 不抓 provider 原生 HTTP 报文的全貌:抓到的是适配器真正 fetch 出去的那一份 body 与真正回来的那一份响应体,但不含 DNS/TLS 层信息、重定向链、以及适配器内部的重试次数;
- 不抓流中途失败:HTTP 200 之后 SSE 流中断/解析失败不会触发落盘(那需要缓冲整条流,代价太高);
- 不抓 FormData / Blob / ReadableStream 请求体:这些 body 一旦读取就被消费,dump 里会标记 unavailable 说明原因;
- dsh 区块的关联是启发式的:按 requestId → 状态码+模型形态 → 模型形态 → 状态码 → 最近一条 的顺序配对,并把实际命中的那条信号写进 dsh.correlation (requestId / status+model / model / status / newest),不假装确定。
命中 newest 时说明只是兜底猜测;dump 里的 URL 与时间戳始终是准的;
- 不改动任何请求:纯观察者,不重试、不修改、不注入。

工作原理

监听器注册在插件自己的上下文上(无 scope 标记)。核对过 @deepseek-ai/dsh-scope 的 scopeTarget 过滤器:scopeOf(ctx) === undefined 时一律放行,因此根级监听器能收到每个 agent 的 agent/request-error,不需要 inject 任何服务。同时 agent/request-error 是 waterfall —— Cordis 明确规定“不调用 next() 的监听器会否决整条链”,所以本插件永远 return next(),绝不干扰 dsh-llm-retry 这类拥有恢复权的监听器。

适配器 fetch(init.body = JSON.stringify(payload)) │ ▼ globalThis.fetch 包装层 ← 只在此处两段原文同时存在 │ 2xx → 原样返回(不读 body) │ 非 2xx / 抛错 → 克隆响应、组装 record ▼ dump-store 待写队列(flushDelayMs 窗口) │ agent/request-error 到达 → 按 requestId/status 认领并补上 dsh 区块 ▼ 原子写入 $DSH_HOME/request-error-dump/.json → 按 maxFiles 清理

模块划分(每个文件一个职责):

| 文件 | 职责 |
| index.js | Cordis 插件入口:配置合并、装配、卸载 |
| src/paths.js | DSH_HOME / ~ 路径解析 |
| src/record.js | 请求头归一化与脱敏、body 读取/截断/解析、record 组装 |
| src/dump-store.js | 待写队列、关联认领、原子写入、保留清理 |
| src/fetch-capture.js | globalThis.fetch 包装层(可重复安装、可还原) |
| src/annotate.js | agent/request-error 监听器(纯观察者,始终 return next()) |

测试

npm test # 32 个单元/端到端用例,零依赖
node test/cordis-integration.mjs # 在真实 @deepseek-ai/cordis 上挂载验证

cordis-integration.mjs 会在本机找到 DSH 安装目录时,用真实 Cordis 运行时加载插件、触发一次真实 waterfall 派发并校验 dump 内容;找不到时自动跳过。可用 DSH_APP_ROOT 指定安装路径。

License

MIT
预览分享卡片长什么样 ↓exynos967/dsh-request-error-dump
分享 · DeepSeek Harness Hub
exynos967/dsh-request-error-dump

模型请求失败时保存原始请求与错误响应报文

回到顶部