Skip to content

Releases: Angelyeye/dsh-cost-tracker

v1.10.1 — 安全加固:本地状态文件在 POSIX 上收紧为 owner-only(目录 0700 / 文件 0600);新增 PR …

Choose a tag to compare

@github-actions github-actions released this 24 Oct 18:43

安全加固:本地状态文件在 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…

Choose a tag to compare

@github-actions github-actions released this 24 Oct 17:54

模型目录刷新到 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 修正
Google 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 账号登录」

Choose a tag to compare

@github-actions github-actions released this 17 Oct 13:22

新增:余额查询兼容 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 外壳)花费统计整块看…

Choose a tag to compare

@github-actions github-actions released this 06 Oct 19:38

修复「桌面端(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)

  1. 新增模块 id 单一事实源 var DSH_BUNDLE_ID = "@angelyeye/dsh-cost-tracker",
    __ModuleLoader__.load({ id }) 与样式归属标记共用它;
  2. applyStyles() 改为先打标再插入:
    tag.setAttribute("data-plugin", DSH_BUNDLE_ID);
    tag.setAttribute("data-plugin-css", DSH_STYLE_TAG_ID);
    与宿主自带包的写法(tag.dataset.plugin = "<包名>")逐字对齐;
  3. 顺带加上宿主同款去重护栏(style[data-plugin-css="cost-tracker-plugin"] 已存在就不再插第二张);
  4. 文件末尾注册名护栏保留它刻意独立的字面量副本(若与 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 — 修复「配额查询不可用:火山方舟配额响应不…

Choose a tag to compare

@Angelyeye Angelyeye released this 24 Sep 04:39

修复「配额查询不可用:火山方舟配额响应不是合法 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 的出站封套里,对调用方透明)

  1. 请求侧:safeFetch 默认补 accept-encoding: identity,从源头要未压缩正文
    (调用方显式指定过就尊重调用方);
  2. 响应侧:新增 decodeCompressedBody() + withDecodedBody(),按字节判断(gzip 1f8b、
    zlib 78xx、响应头明说的 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

Choose a tag to compare

@Angelyeye Angelyeye released this 12 Sep 19:48

变更

  • 包名由 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

Choose a tag to compare

@Angelyeye Angelyeye released this 12 Sep 14:02

新增

  • 适配 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)。