Repository navigation
Releases: Angelyeye/dsh-cost-tracker
Release list
v1.10.1 — 安全加固:本地状态文件在 POSIX 上收紧为 owner-only(目录 0700 / 文件 0600);新增 PR …
安全加固:本地状态文件在 POSIX 上收紧为 owner-only(目录 0700 / 文件 0600);新增 PR CI 门禁(pull_request 跑 npm test)
这两项改进来自外部贡献者 @shaunpalmer 的 PR #1(Project studios/english hardening)。该 PR 整体未合并(包名改为
@shaunpalmer/…、版本回退、客户端砍掉约 90% 的界面、屏蔽配置入口、并移除了两个 UI 回归测试),但其中「文件权限收紧」与「PR 跑测试」两处是真实缺口,本次按当前代码结构重写后落地。
一、本地状态文件权限收紧(store.js / sync.js / index.js)
问题:~/.dsh/storages 下的文件走默认 umask(常见 022)创建,POSIX 上同机其他用户可读。而这里存的是完整用量明细(会话、模型、token、花费;未开启脱敏时还有 sessionId)、设备身份与同步游标、云端同步地址等。
改法:
store.js导出PRIVATE_DIR_MODE = 0o700/PRIVATE_FILE_MODE = 0o600:记录文件与实例锁的目录创建带 0700、文件写入带 0600;sync.js的device.json、cost-tracker-sync.json同样走「目录 0700 + 文件 0600」,并把两处重复的「写临时文件 → rename」收成writePrivateJson();index.js的配置文件、官方价格状态、历史导入清单也补上同样的模式;- 只影响新建:不 chmod 已有文件,避免动到用户既有部署的权限策略;Windows 忽略 mode,行为完全不变;CSV 导出(用户主动选择落地位置的产物)保持默认权限,方便分享。
测试:test/storage.test.js 用注入的 fs 断言 mkdirSync(…,{mode:0o700}) / writeFileSync(…,{mode:0o600}) 真的传进去了(跨平台可断言),并在 POSIX 上额外核对真实文件权限 0600;test/sync.test.js 在 POSIX 上核对身份目录/文件与同步目录/游标的实权限,同时验证私有权限路径不影响读写(游标可读回、不留 .tmp)。
二、新增 PR CI 门禁(.github/workflows/ci.yml)
此前仓库只有手动触发的 publish-npm.yml,PR 上没有任何检查(评审外部 PR 时只能本地 checkout 分支跑测试)。新增 CI 工作流:pull_request 与 push: main 触发,permissions: contents: read(无凭据、fork PR 也能安全跑),Node 24(与发布工作流同版本),执行与发布关卡同一条 npm test;另加 concurrency 取消同分支旧运行。不使用 npm cache —— 本包零 npm 依赖、无 lockfile,缓存只会多一次报错。
三、未采纳部分(保留在作者自己的 fork)
包名/版本/入口变更、index.safe.js 用 Proxy 屏蔽 settings 服务并临时篡改 process.argv[1]、英文重写全部模块并替换 README/CHANGELOG(会丢失发布历史,而仓库已有 README.en.md)、NZD 展示(浏览器直连第三方汇率 API,且写死单一币种)。隐私默认值(maskSessionId / includePurpose)方向合理但会改变现有云端归因口径,保持当前默认,后续以显式选项讨论。
四、测试
npm test 全绿(22 个测试文件);storage 85 项、sync 126 项断言通过。
v1.10.0 — 模型目录刷新到 2026-10-25 官方口径:15 家厂商 / 141 个条目(新增 51、改价 10、口径修正 25…
模型目录刷新到 2026-10-25 官方口径:15 家厂商 / 141 个条目(新增 51、改价 10、口径修正 25);火山方舟 Coding Plan 白名单按官方现行 13 个 Model Name 复核,并给「泛 volcengine」分支补上日期后缀容忍
一、多厂商目录:新增了哪些新模型
目录上次刷新是 2026-08-18(generatedAt 可查),两个多月里各家的新旗舰与降价款都没进来。本次逐家复核,providers 从 14 家 90 条扩到 15 家 141 条(新增 51,改价 10,其余为来源/口径修正 25;删除 0 条,下线模型一律保留并打 notes 标记,便于历史记录核对)。
| 厂商 | 本次新增(部分) | 变化要点 |
|---|---|---|
| OpenAI | gpt-6-astra / gpt-6-sol / gpt-6.1-sol / gpt-6-luna |
gpt-5.6-sol 降价 $5/$30 → $4/$20 |
| Anthropic | claude-fable-5-1、claude-mythos-5-1、claude-opus-5-5、claude-sonnet-5-5、claude-haiku-5-5 |
claude-sonnet-5 早鸟价 $2/$10 经官方 2026-08-10 公告永久化,notes 修正 |
gemini-4-argon(5 折至 2026-11-30)、gemini-3.8-flash |
3.7/3.8 Flash 促销口径两源冲突,notes 标「未核实」 | |
| Moonshot | kimi-k3.1-pro、kimi-k3.1-pro-highspeed |
kimi-k2.5 官方 2026-08-31 下线(保留 + notes) |
| 智谱 z-ai | glm-5.3-flash、glm-5.3-flashx;glm-5.3 由 unpriced 恢复精确价 $1.40/$4.40 |
官方已公布单价,原「不编造价格」占位作废 |
| xAI | grok-4.7、grok-4.20-0309-reasoning / -non-reasoning / multi-agent-0309 |
grok-4.3 补 cachedInput 0.20;各档补 >200K 分档说明 |
| Alibaba | qwen3.8-flash |
qwen3.8-max / qwen3.7-max 缓存读价按官方 10% 修正为 0.20 / 0.25 |
| MiniMax | minimax-m3.1-flash(¥1/¥0.10/¥4,按 7.2 折算) |
minimax-m3 补 >512K 档与 priority ×1.5 说明 |
| 腾讯混元 | hy4-preview(¥6/¥0.30/¥18 折算) |
hunyuan-a13b 原值实为人民币刊例价,改为美元口径 0.069/0.278;hy3 升为 official |
| 小米 MiMo | mimo-v2.6-pro / mimo-v2.6-flash / mimo-v2.6-pro-ultraspeed |
mimo-v2.5 系 2026-10-21 10:00 下线(保留 + notes) |
| Upstage | solar-mini-4 |
solar-pro4 2026-10-20 起 5 折;solar-pro3 2026-10-30 停服(notes) |
| NVIDIA | nemotron-3.5-lightning-30b-a3b、nemotron-3.5-super-vl-120b-a12b |
官方仍未公布 token 单价,保持 unpriced |
| Mistral | mistral-large-4(Public Preview)、mistral-small-2603(原 mistral-small-4.0 更名)、ministral-14b/8b/3b-2512 |
官方价表未列缓存价,缓存值 notes 标「未核实」 |
| Meta(新厂商) | muse-wonder-1.0、muse-spark-1.3、muse-spark-1.2 |
新增 provider 别名 meta / meta-ai / muse |
| OpenCode Go 档 | 补 17 条:grok-4.7、glm-5.3(原 unpriced → $1.40/$4.40)、kimi-k3.1-pro、gpt-6-luna、gemini-4-argon、hy4-preview、deepseek-v4.1-flash / v4-pro 等 |
订阅档按「固定月费 + 每模型月度美元额度」计量,token 单价与按量档一致 |
未收录的模型与原因(不编造价格):
Big Pickle/Beam/Flint 40B/LongCat 2.5 Preview:仅以网关限时免费档存在,或厂商主体未披露,无公开 token 价 → 不落表(LongCat-2.0有网关价,已收录在 OpenCode Go 档);- 智谱国内
bigmodel.cn人民币价、Kimi K2.7 / K2.6 官方 API 单价、MiniMax 国际站美元价:官方页本机抓不到正文 → 沿用网关值并在notes明确标注出处; - OpenAI / Google 官方定价页本机返回 403 / DNS 不可达 → 这两家的值取自 OpenCode Console 网关价,
sourceUrl如实写网关地址,并用 OpenRouter 端点做交叉核对(一致 / 冲突都在notes写明)。
二、火山方舟 Coding Plan 白名单(pricing.js)
- 白名单从 14 个扩到 24 个 id:按官方 2026-10「Coding Plan 个人版快速开始」复核,补入
kimi-k3、kimi-k3.1-pro、kimi-k2.8-preview、deepseek-v4.1-flash-economy、deepseek-v4-flash、glm-5.3、glm-latest、doubao-seed-2.1-lite、doubao-seed-2.0-mini、doubao-seed-evolving;glm-5.2列入(2026-08-31 起官方由glm-5.3承接)。 - 旧白名单里 9 个「4 月还在、10 月官方清单不列」的 id 不删:下线公告页本机取不到正文,删错会把仍在售的套餐内模型误标成按量;保留的代价只是极端情况下少算按量花费(方向更安全,符合本项目「宁可漏配也不能错配」的门卫原则)。
- 新增日期后缀容忍:方舟模型 id 普遍带 6 位日期后缀(
glm-5-3-flash-260828、deepseek-v4-1-flash-260910、doubao-seed-2-1-pro-260915),而normalizeModelName只去分隔符。专属 coding 端点因「整档订阅」提前 return 不受影响,但泛volcengine分支下这些 id 会漏配 → 被标成「按量 · 估算」。现新增stripVolcDateSuffix():先剥尾部\d{6}再查白名单与前缀,ep-20260413045435-2shmq这类接入点 id 仍不会被误判。 - Kimi 订阅侧无需改动(
SUBSCRIPTION_MODELS为null= 整档订阅),6.5/27.0/1.1/1.1仍是估算值——官方明确不公开套餐内 token 等效价。
三、数据纪律
- 每条新增/改动都带
sourceUrl+checkedAt: 2026-10-25;官方页优先,网关兜底时在notes写明「取自网关 / 未核实 / 两源冲突」,与仓库既有的「不编造价格」约定一致。 CATALOG_META.generatedAt同步为2026-10-25(设置页「数据日期」与云端/api/v1/protocol都会回显)。- 计费优先级不变:手动覆盖价 > 订阅归类 > 内置/同步时代精确价 > 目录 > provider 兜底 > 通用兜底。
四、测试
test/vendor-catalog.test.js新增断言:meta厂商在列、glm-5.3/gpt-6-astra/kimi-k3.1-pro可命中(防止「只加 JSON 不加别名」或「占位未清」回归)。test/pricing.test.js更新火山门卫断言到 2026-10 官方白名单(含glm-latest别名),并新增「泛 volcengine 带日期后缀仍计为订阅」与「接入点 id 仍不误判」两侧断言。- 全仓 22 个测试文件逐文件运行全绿,
npm test全绿。
v1.9.6 — 余额查询兼容 DSH「DeepSeek 账号登录」
新增:余额查询兼容 DSH「DeepSeek 账号登录」——账号态改走宿主 deepseekAccount.getBalance(),读取 Platform 钱包(充值 / 赠金);API Key 路径原样保留
一、问题:账号登录用户查不到余额
DSH 的浏览器 PKCE 账号登录(@deepseek-ai/dsh-deepseek-account + dsh-deepseek-account-platform,0.1.7-alpha.1 起引入)把授权写进 credentials 的 record deepseek-account-platform/default({version, token, issuer}),从不写 ref。而 credentials.resolve() 只查 refs 半区 —— 所以:
- 账号 token 没有 ref 名,
resolve('DEEPSEEK_API_KEY')取不到它; - 账号态推理用
x-dsh-auth-token走 Platform 网关,运行时dsh-llm-deepseek-account与dsh-llm-deepseek的 README 都写明「不会回退到 API key」,两条路由互斥; - 全 asar 扫描中
api.deepseek.com/user/balance、total_balance、granted_balance、topped_up_balance命中数均为 0 —— 说明「登录后拿 key 去查 api.deepseek.com」这条路在 0.2.x 里根本不存在。
结论:只认 DEEPSEEK_API_KEY 的旧实现,在纯账号态下必然报「未找到 DeepSeek API Key」,不是配置问题。
二、改法:新增账号余额来源,优先级三级
面板手填 API Key > 账号登录态(宿主服务) > 配置/环境里的 API Key
- 账号登录态:
ctx.get('deepseekAccount').getBalance(client),数据源为 Platform 的GET /api/v0/users/get_user_summary,返回null(未登录/凭据失效换代)|{status:'failed'}(网络/超时/协议)|{status:'ready', value:[充值钱包], bonusWallets:[赠金钱包]}。 - client 元数据三字段必填:
version/locale/timezoneOffsetSeconds会被运行时直接拼进x-client-*请求头,缺一个就抛TypeError(实测:无参 →reading 'version',{}→reading 'toLowerCase')。宿主侧一律补齐,浏览器半端再把navigator.language与本机时区传上来覆盖。 - 手填 Key 仍最高优先:那是用户显式动作,意图最强。
- 账号态失败不回退 API Key:账号已登录时回退会显示另一个账户的数字,比报错更误导;失败信息里直接给出出路(手动填 Key)。
- 旧宿主零影响:没有
deepseekAccount服务的 DSH 自动走原路径,文案与 v1.9.5 逐字一致,不做版本号判断。
三、口径与字段
- 账号态「总余额」= 同币种充值钱包合计 + 同币种赠金合计,用 BigInt 精确十进制加法(不经
Number,0.1+0.2不出现0.30000000000000004;Platform 返回的0E-16这类零值统一显示为0)。 - 账号态
available返回 null(Platform 摘要没有is_available,不把「未知」渲染成「不可用」);API Key 态仍为布尔。 - 多币种:卡片按
CNY > USD > 其它选主币种,符号与标题跟随币种(USD 不再一律显示¥),完整钱包列表放在wallets/currencies里。 - 展示精度:Platform 钱包金额实测是 16 位小数(形如
1234.5600000000000000)。界面按 DSH 官方「账号与余额」页口径截断到分(不四舍五入)、正的不足一分显示<0.01、零显示0.00、负数保留符号;同时兼容0E-16这类指数写法(指数 > 30 时不硬展开)。接口返回值保持原始精度不截断,展示与数据分离。 - 响应新增
source: 'account' | 'apikey' | 'none';total/granted/toppedUp/currency/keySource等旧字段全部保留(keySource账号态为account)。
四、顺带修复(同一轮 0.2.x 兼容审计发现)
- 历史导入对 v4 会话静默失效:0.2.x 起宿主把新会话写成
session.v4.jsonl.zstd,而import.js只枚举session.v3.jsonl.zstd/session.v3.jsonl/session.jsonl.zstd/session.jsonl四个硬编码名字 —— v4 及以后的新会话被静默跳过(导入清单里 0 个 v4、无任何报错,只表现为「历史导入不再补录新会话」)。本机实测:.dsh\sessions下 68 个 v4 + 20 个 v3 + 10 个 legacy,修复前listSessionLogs()只返回 29 个。内容层本来是兼容的(抽样实测 v4 日志能正常回放出调用(Lead 样本 1160 事件 → 132 调用;verifier 另证与同目录 v3 的调用集合一致),usage/message.source形状未变),断点只在文件名。现改为按session[.vN].jsonl[.zstd]正则识别,版本号高者优先、同版本 zstd 优先,宿主将来升 v5/v6 不需要再改枚举;新增test/session-log-v4.test.js(含 v5 前瞻与真实目录交叉核对)。 - 账号态 provider 的兜底价:账号登录的推理 provider 名是
deepseek-account,原先不在PROVIDER_RATES里。现役模型能命中 provider 无关的精确价表(实测 100% 命中、estimated:false),但表外的新模型会落到GENERIC_RATES(缓存价 0.5 而非 0.04、且无峰谷半价)→ 费用被静默高估。现把deepseek-account登记为与deepseek共用同一份兜底价(pricing.js)。 settings读取迁移到 0.2.x 的describe()(原为静默降级):0.2.x 的SettingsForms已无get()(只剩configure/describe/update/replace/mutate/write/prepareDocument),插件原来的deepseekKeyEnv()/kimiKeyEnv()/ 火山 AK 候选链会抛 TypeError 并被各自 try/catch 吞掉 → 恒走硬编码默认名。默认名与官方一致,标准部署看不出问题;但自定义apiKeyEnv的部署会去查一个不存在的 Key 名(表现为「明明配了 Key 却报未找到」)。0.2.x 的正确读法是settings.describe()—— 它返回全部条目的表单描述,row.value即该条目 volatile 字段的生效值(apiKeyEnv正是.volatile()字段)。新增settings.js做「describe() 优先、get() 兜底」的双 API 兼容读,并按 settings 实例做 10s 短缓存(describe 是全量投影,余额/Kimi/火山三个入口一次刷新会各调一次)。旧宿主行为完全不变;新增test/settings-compat.test.js(两套 API、优先级、缓存、边界,以及「0.2.x 自定义 ref 真的被用来查 Key」的端到端断言)。- 修掉既有测试腐化:
test/import.test.js的「看板读到导入记录的总花费」用dashboard {days:30}(相对当前时间)去断言固定日期 fixture(2026-09-15 起),2026-10-15 之后必然假失败 —— 它断言的是口径(导入记录要进看板)而非时间窗口,改为足够宽的窗口覆盖 fixture。这是npm test在 CI 上变红、进而挡住发布的根因。
五、新增测试
test/balance-account.test.js:用真实文件 + 真实 HTTP 处理器跑,不打模块内桩:账号 ready 投影、未登录回退 Key、手填 Key 优先(且不调用账号服务)、账号失败不静默回退、getBalance 返回 undefined 按契约外失败处理(不当作未登录回退,否则会显示另一个账户的余额)、旧宿主文案不变、client 三字段必填、精确十进制加法、客户端来源判定与币种显示、展示格式化(从 bundle 源码取函数在 VM 里求值)、账号态 provider 兜底价、settings 双 API 端到端。
test/session-log-v4.test.js:session[.vN].jsonl[.zstd] 全版本枚举(v3/v4/v5/无版本/plain/zstd/空目录/非会话文件),并对照本机真实 .dsh\sessions 校验「磁盘上的 v4 全部被枚举到」。
test/settings-compat.test.js:readSettingsSection() 的双 API 兼容读(旧宿主 get() / 0.2.x describe() / 两者都在时 describe 优先 / 都不可用 / 抛错 / 非数组 / 非对象 / ns 非法)、10s 缓存与显式失效、不同 settings 实例互不串味。
测试口径:全仓 22 个测试文件逐文件运行全绿,
npm test(CI 用的&&链)同样全绿 —— 发布工作流的 "Run tests" 关卡因此恢复可用。
说明:账号登录不会下发 API Key,因此「用账号登录直接请求 api.deepseek.com/user/balance」不可行;能查的是 Platform 钱包(与 DSH 官方「设置 → 账号与余额」同源),两者是同一 DeepSeek 账号的不同接口口径。
v1.9.5 — 修复「桌面端(Electron 外壳)花费统计整块看…
修复「桌面端(Electron 外壳)花费统计整块看板变成无样式原生控件」—— 自建 <style> 缺 data-plugin 归属标记,被 DSH 客户端模块系统认领给别的模块后在 HMR 清理时连带删除
一、根因(已复现并逐帧比对)
DSH 0.2 的客户端模块系统 @deepseek-ai/dsh-client-modules/lib/client.js 用两个函数管理插件样式表:
// 物化(materialize)一个模块的 factory 之后立刻盘点样式
const claimStyles = (id) => {
for (const el of document.querySelectorAll("style:not([data-plugin])")) el.setAttribute("data-plugin", id); // ← 无主的全认领
...
};
// 某个模块被 HMR 失效 / 重载 / 剪枝时清理它的样式
function removeOwnedStyles(id) {
for (const el of document.querySelectorAll("style[data-plugin]")) if (el.getAttribute("data-plugin") === id) el.remove();
}没打 data-plugin 的 <style> 会被「当时正在物化的那个模块」认领走。 插件旧实现只打了
data-plugin-css,于是自己的样式表被登记在别的模块名下(本机实测被
@deepseek-ai/dsh-api-remotes 认领)。该模块一旦走到 removeOwnedStyles(id)
(HMR 清单里 rev 变化 → updateManifest 的 invalidate 分支、reload() 的非托管分支、
entry 重载、prune() 剪枝、factory 抛错的 catch),连带把整块花费统计看板的 170 条
CSS 规则一起删掉,界面退化成浏览器默认的 <select>/<button>/复选框排版:卡片边框、
38 处自定义控件皮肤、图表网格与图例全部消失。
为什么只在桌面端出现:Electron 外壳把 apps/web 的原始 vite dist 从
dsh-app://app/ 提供(resources/app.asar/dsh/node_modules/@deepseek-ai/dsh-web-frontend/dist),
启动图(__ModuleLoader__ 队列引导、插件 <link rel=preload>、__DSH_CONTACT_CONFIG__)
改由 IPC 取回 ctx.webServer.collectIndexInjections() 后在运行期注入;dsh-client-hmr
随后推送的清单会让若干非托管行的 rev 与初始值不一致,触发上面那条清理路径。
浏览器里的 dsh web 是宿主直接把启动图内联进 index.html,物化顺序不同、清单 rev 与本次启动一致,
所以看不到 —— 这也解释了「同一个插件、同一份 client.js,网页版正常、桌面端无样式」。
归属标记在插入前就要写好:claimStyles 只认「当前是否已带 data-plugin」,
插入后再补标会留下一个未被认领的窗口。
二、修法(client.js)
- 新增模块 id 单一事实源
var DSH_BUNDLE_ID = "@angelyeye/dsh-cost-tracker",
__ModuleLoader__.load({ id })与样式归属标记共用它; applyStyles()改为先打标再插入:与宿主自带包的写法(tag.setAttribute("data-plugin", DSH_BUNDLE_ID); tag.setAttribute("data-plugin-css", DSH_STYLE_TAG_ID);
tag.dataset.plugin = "<包名>")逐字对齐;- 顺带加上宿主同款去重护栏(
style[data-plugin-css="cost-tracker-plugin"]已存在就不再插第二张); - 文件末尾注册名护栏保留它刻意独立的字面量副本(若与
DSH_BUNDLE_ID同源,
故障注入测试就永远测不出「注册名 ≠ 包名」了)。
三、验证
- 真机复现(桌面端 profile 的等价宿主 + 无头 Edge/CDP,1280×820,与用户截图同视口):
- 修复前:插件样式表
data-plugin = "@deepseek-ai/dsh-api-remotes"(被误认领),
模拟该模块的removeOwnedStyles→ 样式标签 1 张被删、看板立刻变成无样式原生控件,
与用户截图逐项一致; - 修复后:
data-plugin = "@angelyeye/dsh-cost-tracker",同样触发
@deepseek-ai/dsh-api-remotes的清理 → 标签存活、sheet.cssRules.length = 170、
6 张卡片仍在(border-radius: 10px、数值font-size: 24px),截图正常。 - 极端对照:把 47 个无关模块的 168 张样式全删,本插件看板依然保持有样式。
- 修复前:插件样式表
test/client-render.test.js新增 [11](7 条断言):恰好注入一张样式表、
data-plugin等于 bundle 注册名、data-plugin-css保留、正文含关键规则、
打标早于head.appendChild、注册 id 与归属标记同源;test/client-registration.test.js新增 [3b](5 条断言)钉住单一事实源与独立护栏副本,
故障注入改为改写DSH_BUNDLE_ID(同时覆盖注册与归属标记两个消费点);- 全量 19 个测试文件通过。
四、说明
- 只改客户端半端
client.js:不动任何计费/同步口径,HTTP 面、配置键、落盘路径、云端契约零改动; - 客户端半端换版后浏览器硬刷新即可生效;另需确认宿主半端也已是新版(
index.js/PLUGIN_VERSION); - 同生态插件若自建
<style>且只打data-plugin-css,会踩同一个坑(本仓库的dshmarket已按约定打标)。
v1.9.4 — 修复「配额查询不可用:火山方舟配额响应不…
修复「配额查询不可用:火山方舟配额响应不是合法 JSON(GetCodingPlanUsage)」—— 宿主全局 fetch 被 undici 包污染后不解压 gzip,插件改为自带解压兜底
一、根因(已在本机复现并定位到机制)
DSH 宿主进程里只要有插件 import 了 npm 的 undici 包,Node 内置 fetch 就不再解压
gzip:响应既没有 content-encoding,正文也是原始压缩字节。本机引入方:dshmarket/lib/net.js
(静态 import)、dsh-http-proxy 与 dsh-web-fetch-http(动态 import)。
矩阵实测(同一进程、同一凭据、真实接口):
| 阶段 | npmjs /latest |
火山 GetCodingPlanUsage |
|---|---|---|
| 纯 Node(未 import undici) | 200 · ce=gzip · 3055B · JSON OK |
200 · ce=gzip · 560B · 解析 OK(三档窗口) |
await import('undici') 之后(不装 dispatcher 也一样) |
200 · ce=null · 1506B · 原始 gzip(1f8b)· FAIL |
200 · ce=null · 326B · 原始 gzip · FAIL |
两条现场症状同源:
- 本插件配额面板:
safeFetch→ 全局 fetch → 拿到压缩字节 →JSON.parse失败 → 面板报
「配额查询不可用:火山方舟配额响应不是合法 JSON(GetCodingPlanUsage)」; - 插件市场把所有包的新版本读成
null(它的marketFetch在无代理时回退的也是全局 fetch,
日志里的… is not valid JSON就是同一形态的乱码)。
补充对照:web_fetch 工具一直正常,因为它用的是 undici 包自己的 fetch(解压正常);
content-encoding: gzip 存在时同样会中招 —— 关键在「谁解压」,不在响应长什么样。
二、修法(两层,都在 credstore.js 的出站封套里,对调用方透明)
- 请求侧:
safeFetch默认补accept-encoding: identity,从源头要未压缩正文
(调用方显式指定过就尊重调用方); - 响应侧:新增
decodeCompressedBody()+withDecodedBody(),按字节判断(gzip1f8b、
zlib78xx、响应头明说的br/deflate)自行解压,绝不只看响应头 —— 服务端忽略identity
时同样能正确解析;解压异常一律原样返回,不因为猜测丢内容。
withDecodedBody()只实现调用方真正用到的成员(status/ok/url/redirected/headers/
text/json/arrayBuffer),正文只读取一次并缓存,且对「只有text()的 fetch 替身」保持兼容。
影响面:插件全部出站请求都走 safeFetch(火山配额、Kimi 配额、云端同步与读取、官方定价页抓取),
因此这一处修复同时给这些路径兜住了同一个坑。
三、验证
- 真机端到端(先
import('undici')污染进程,再走index.js的真实接线):- 旧行为(裸全局 fetch)→ 报出与用户完全一致的错误;
- 修复后(
safeFetch+ 白名单)→action=GetCodingPlanUsage,三档窗口
fiveHour / weekly / monthly全部解析(实测 100% / 27.08% / 14.77%);
test/credstore.test.js新增 [5]:坏宿主(无content-encoding的 gzip 正文)可解析、
健康宿主(头写 gzip 但正文已解压)不二次解压、deflate 兜底、纯文本替身兼容、
非压缩内容原样返回、accept-encoding默认值与调用方覆盖;test/volcengine-host.test.js新增 [6]:端到端 —— 宿主返回未解压 gzip 时配额路由仍ok:true、
三档窗口齐全、百分比不被放大、请求确带accept-encoding: identity;- 全量 18 个测试文件通过。
四、说明
- 这是环境韧性修复,不改变任何计费/同步口径:HTTP 面、配置键、落盘路径、云端契约零改动;
- 需要重启
dsh web才生效(改动在宿主半端index.js/credstore.js); - 插件市场那条不属于本插件可控范围(其自身回退路径同样依赖全局 fetch),
可在dshmarket侧改为始终使用 undici 包的 fetch 或自行解压;本插件侧的同类风险已消除。
v1.7.0 — 包名迁移至 @angelyeye/dsh-cost-tracker
变更
- 包名由
dsh-cost-tracker改为@angelyeye/dsh-cost-tracker—— npm 上原名已被他人占用,而插件市场的 npm 映射要求「已发布包名 = 仓库package.json的name」且该包的repository指回本仓库。现已发布到 npm:https://www.npmjs.com/package/@angelyeye/dsh-cost-tracker cordis.patch.yml的 bundle 补丁同步改名。scoped 名在 YAML 里必须加引号(name: "@angelyeye/dsh-cost-tracker")——@是 YAML 的保留起始字符,不加引号会导致整个 bundle 层解析失败。- README(中/英)安装章节同步更新,并新增「从旧包名迁移」。
迁移(仅 v1.6.0 及更早的安装需要)
cordis.patch.yml 用的是同一个 loader id(dsh-cost-tracker),叠加会让两份同时加载 —— 重复的 HTTP 路由、Agent 工具与 UI 插槽。必须先清掉旧的:
- 市场安装的:
dsh plugin --profile web remove dsh-cost-tracker,再dsh plugin --profile web add @angelyeye/dsh-cost-tracker - 手工 clone 装的:删掉
~/.dsh/profiles/web/cordis.patch.yml里 id 为dsh-cost-tracker的那段- insert:,并rm -rf ~/.dsh/profiles/node_modules/dsh-cost-tracker,然后重装一次
数据不会丢:用量记录在 ~/.dsh/storages/cost-tracker-records.json,与插件目录无关。
安装
dsh plugin --profile web add @angelyeye/dsh-cost-tracker附件
dsh-cost-tracker.tgz —— 预构建安装包(资产名不含版本号,便于 latest/download/ 永久稳定引用)。
功能与数据格式无变化;单元测试 218 项全绿。
v1.6.0
新增
- 适配 V4.1 Flash 新计费规则(北京时间 2026-09-10 12:00 起生效):单价表改为按「计费时代」分版(
PRICE_ERAS),按每条记录自身的时间戳选版计费——历史记录口径不变、切换点自动生效,无需重启或改配置。- 新价格(元/百万 tokens,高峰):输入(缓存命中)0.04 / 输入(缓存未命中)2 / 输出 8;闲时为高峰半价。
- 新增模型路由:V4-Pro 与旧 V4-Flash 系请求按 V4.1 Flash 单价计费,记录以实际计费模型名入账。
- 模型名归一化匹配:
deepseek-v4.1-flash/deepseek-v4-1-flash/deepseek-v41-flash等等价写法命中同一档价,避免官方模型 ID 措辞变化导致漏计。 - 新增
cost_recompute工具(含 HTTP/api/cost-tracker/recompute):按记录自身时间戳重算已入库记录的费用与计费模型名,默认只试算,传apply: true才落盘,幂等可重复执行。 cost_prices工具与 HTTP/prices改为按版本渲染,逐时代列出生效时刻、单价与路由规则,并标出当前生效时代。
变更
priceFor(np, model, ts)新增第三个参数ts,返回值新增model(计费模型规范名)与era;记账链路改为传入记录时间戳。- DeepSeek provider 兜底单价同步至 V4.1 Flash 档,未知 deepseek 模型不再按旧价高估。
EXACT_MODELS语义收敛为 legacy(旧价)时代的单价表。
文档与打包(本次一并发布)
- README(中/英)安装章节新增「从插件市场安装」,并补齐
dsh.bundle、cordis.patch.yml、screenshots.json的说明;版本徽章与package.json对齐。 - 新增
screenshots.json,声明插件市场详情页的 4 张截图顺序。 package.json新增files白名单与engines.dsh;预构建包体积从约 1.6 MB 降至 67 KB。- 下方附件
dsh-cost-tracker.tgz即预构建安装包。
安装
dsh plugin --profile web add github:Angelyeye/dsh-cost-tracker单元测试 218 项全绿(npm test)。