跳到正文
dsh-market 浏览插件 GitHub EN

log-li/dsh-peakrate

为模型选择器(每一行)与 composer 工具行显示峰谷倍率,按每个 provider 自己的时区与时段规则判定;实时目录由 host 经带信任围栏的路由下发。

Star 数 ★ 1 分类 模型与账号接入 收录于 2026-09-13 npm dsh-peakrate

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add dsh-peakrate

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

截图

README

dsh-peakrate ⛰️

给 DeepSeek Harness 的峰谷倍率徽章 —— 切过去之前,先看清每个模型此刻到底什么价。按 provider、按模型。

🌐 简体中文: README.zh.md · English: README.md

CI


每家 provider 都按自己的钟计费。DeepSeek 按北京时区分峰谷;Ollama 的窗口是 UTC;Z.ai 还有带日期区间的限时活动。同一个模型经不同 provider 路由,此刻的价可能完全不同。

dsh-peakrate 读出每个 provider 各自适用的时段规则,算出此刻落在哪一态,然后把它放到你本来就在看的地方 —— 模型选择器的每一行,以及 composer 旁边。

三种时段态,不是两种。 峰与谷是熟悉的那一对;第三种 campaign 是带日期区间与星期过滤的限时活动窗口,在有效期内优先级高于常规峰谷。

一条最要紧的规则:所有窗口都按真实时间戳判定,所以 DST 切换与跨午夜的窗口是正确的,而不是近似正确。

✨ 主要特性

  • ⛰️ 按 provider 分别判定 —— 每个 provider 用自己的 IANA 时区与自己的时段规则,不套用 DeepSeek 的时段。
  • 🌗 三种时段态 —— peak / offPeak / campaign(限时活动,优先级高于常规峰谷)。
  • ⏱️ 切换倒计时 —— 不只当前倍率,还有何时结束、会变成什么:1× · 2d 7h、2d 6h → 2×。
  • 💬 跟随主题的悬停浮层 —— 详情由页面自己绘制而非系统 tooltip,风格与 harness 一致,键盘聚焦也能唤出。
  • 📋 模型选择器每一行 —— 切换之前就能比价。选型只在这个面板里发生,信息正好落在决策点。
  • 📌 composer 工具行徽章 —— 当前模型的倍率与倒计时,抬眼可见,不用开菜单。
  • 🔄 目录实时更新 —— host 半边每 24 小时刷新共享目录,并经带信任围栏的路由下发到页面,数据更新无需重新构建即可上屏;覆盖面板里一键「立即刷新」。
  • 🔍 覆盖面板 —— 侧栏 Plugins(插件) 里本插件详情页上,逐 provider 列出「命中 / 总数 / 未收录的模型」,并对整组零命中的 provider 告警。那正是静默漏配的形态,也是本插件最不愿意隐藏的东西。
  • 🧭 npm run audit —— 离线覆盖穷举:枚举运行时的 provider × model,挑出需要人工决策的 provider。
  • 🪶 零运行时依赖 —— 时间计算只用 Intl.DateTimeFormat 与 Date,不引入日期库。
  • 🎨 只用设计 token —— 颜色来自 harness 自己的 --dsw-*,跟随明暗主题。
  • 🌐 中英双语 —— 全部文案经 harness 的 locale 服务,内置完整的中文与英文两本字典,跟随你的 harness 语言设置。

📚 目录

安装

# <profile> 是你的 DSH profile —— 通常就是 web。`--profile` 是**必填**:
# `dsh plugin` 会在该 profile 目录里把参数转发给 pnpm。
dsh plugin --profile web add dsh-peakrate

从本地检出安装:

dsh plugin --profile web add ./path/to/dsh-peakrate

前置依赖:dsh plugin 只是一个转发器,它把参数交给 profile 目录里的 pnpm 执行, 所以 pnpm 必须在 PATH 上(否则会报 pnpm not found on PATH — install pnpm to manage profile plugins)。

请注意:本插件会替换官方的模型选择器。 它接管了 conversation.input.model 槽位 —— 你从 composer 打开的选择器将变成本插件自己的组件, 而不再是内置的那个。这个接管是有意为之,且是功能超集:键盘导航、aria 接线、portal 定位、 加载 / 空 / 错误 / 重试状态、以及推理等级子菜单全部保留,倍率是按行加在上面的。 上游组件为 MIT,移植所依据的版本记录在 src/client/index.tsx。由于是替换而非扩展, 将来 DSH 若重构该选择器,可能需要重新移植 —— 见兼容性与贡献。

装完请重启 dsh web。 本插件在 host 半边声明了 settings 命名空间,而 host 代码只在启动时读取。重启后覆盖面板会出现在 侧栏 Plugins(插件)→ 本插件详情页,旁边还有一个可界面编辑的字段:后台刷新间隔。

你会看到什么

模型选择器里 —— 打开你本来就在用的选择器(即顶部那张图),每一行都带该模型此刻适用的倍率。 同一个倍率同时常驻在下方 composer 工具行,你付的什么价一直看得见。

上图里四种倍率形态同时在场:2× 峰时、1× 与 0.5× 谷时、0.8× credits 套餐、以及限时的 Campaign 活动。 没有时段计价的模型就是不带徽章 —— 那是诚实的状态,不是漏查。

悬停徽章看详情:此刻什么态、多久之后变成什么。浮层由插件自己绘制(非系统 tooltip), 跟随明暗主题,键盘聚焦同样能唤出:

三种时段态

态 含义 颜色 图标
peak 标准价 警示(橙) 双峰山
offPeak 折扣价 成功(绿) 双谷
campaign 限时活动 成功(绿) 星芒

颜色表达「贵/便宜」,图标表达「是哪个时段态」。 图标刻意用地貌形状而不是涨跌箭头 —— 箭头会被读成「它要涨/要跌了」,而山峰只是「一个高点」。方向与形状不是同一个断言。

某个模型没有匹配到任何 profile 时,那一行什么都不显示。这是有意的:没有时段计价的模型,不该被贴上一个它并不拥有的倍率。

工作原理

provider id ──┐
              ├─► 别名 ──┐
model id ─────┘          ├─► profile ─► schedule ─► 此刻状态 ─► 徽章 + 倒计时
                         │
目录(实时或内置)────────┘
  1. 匹配:provider id 经过别名表(ollama → Ollama)。provider id 是你自己起的本地标签;别名表就是「标签 → 真实计费主体」的翻译。模型 id 随后被归一化(剥掉 :tag 后缀、统一大小写与分隔符),再按 provider 专属的模型模式匹配。
  2. 判定:用匹配到的 profile 自己的时区求值。三件事一起考虑,优先级从高到低:生效中的 override(带日期区间的活动/促销窗口)→ 法定节假日 → 常规的星期过滤与峰时窗口。
    • 节假日全天按谷价。 公布了中国法定节假日日期的 provider,节假日整天都按谷价计费,因此落在工作日的节假日不算峰时。节假日日期与所有窗口、倍率一样都来自数据源,插件内不硬编码,上游改动后下次刷新即生效。
    • override 仍然优先 —— 它是同一份数据源里更具体的规则(活动可以刻意在节假日给出另外的定价)。
  3. 呈现:结果与倒计时渲染到模型选择器、composer 工具行与覆盖面板。

时间正确性

时间计算刻意避开了「墙钟分钟数 + 1440」这类捷径 —— 那会让倒计时在 DST 边界差一小时、在窗口边缘差一整天。这里的做法是把墙钟候选点换算回真实时间戳再与 now 比较。

跨午夜窗口(23:00–09:00)归属它开始的那一天,所以凌晨那半段按开始日的日期与星期判定。这对活动 override 与常规峰时窗口完全一致。

扩展覆盖范围

判定基于一份精选目录,因此一个 provider 要么被映射,要么被明确记录为「有意不映射」。不存在第三种静默结局 —— 覆盖面板与测试套件都在强制这一点:

  • endpoint 转售别家计费的(网关)继承上游时段 —— 目录收录的是直连厂商,不是转售方;
  • 确实没有时段计价的 provider,会得到一条写明理由的显式记录;
  • 其余情况一律在覆盖面板里冒出告警。

覆盖情况

侧栏 Plugins(插件)→ 本插件详情页。它承担四件事:看清覆盖、发现漏配、改刷新间隔、按需刷新目录。

列 含义
Provider harness 所知的 provider
覆盖 该 provider 下命中 profile 的模型数
未收录的模型 没有命中任何 profile 的模型

整组零命中的 provider 会被顶到前面并告警。部分未命中刻意不告警:同一个 provider 下常混有「有/无时段计价」两类模型(比如一个 Ollama 分组里既有 DeepSeek 又有 GLM),每行都提示等于没提示。

面板顶部的数据来源行(如上图的「目录来源:远端 · 更新于 …」)说明当前用的是远端目录还是内置快照; 右侧**「立即刷新」**强制重新拉取一次。enabled 与 refreshIntervalHours 也在这里编辑,即时生效。

配置

配置写在 profile 的 cordis.patch.yml 里:

- id: peakrate
  config:
    # provider id → 目录里的 provider 名
    providerAliases:
      my-gateway: DeepSeek
    # 按 provider 限定模型模式;先匹配者胜
    modelMappings:
      - provider: my-gateway
        match: "^deepseek-v4"
        profile: deepseek-v4
      - provider: my-gateway
        match: "glm-5\\.3-flash"
        matchIsRegex: true
        profile: zai-glm-5-3-flash
    # 拉取间隔;0 表示关闭后台刷新
    refreshIntervalHours: 24
    catalogUrl: https://offpeakclock.com/pricing.json
    cachePath: ~/.dsh/peakrate/pricing.json
选项 默认 说明
enabled true 后台刷新开关。也可在覆盖面板里改。
refreshIntervalHours 24 目录刷新间隔(小时)。0 关闭。也可在面板里改。
catalogUrl 公共目录 远端目录地址。
cachePath ~/.dsh/peakrate/pricing.json 最近一次成功拉取的磁盘缓存。
providerAliases 内置表 额外的 provider id → 目录 provider 映射,覆盖内置。
modelMappings 内置表 额外的 provider + 模型模式 → profile 映射,先于内置尝试。
customProfiles [] 额外 profile:按 id 覆盖内置条目或新增;同样参与徽章判定。

modelMappings 默认是前缀匹配;写 matchIsRegex: true 则按正则。非法正则不抛错,只是永不命中。

customProfiles 完全生效(含徽章)。host 会把合并后的目录经 /peakrate/catalog 下发给页面, 所以自定义 profile 与内置条目一样参与徽章判定 —— 按 id 覆盖内置条目,或追加新条目。 实测:给 deepseek-v4 写一条 peak: 9× 的自定义 profile,下发的目录与徽章都变成 9×。

数据来源与新鲜度

目录来自 offpeakclock.com/pricing.json(schemaVersion: 1),一份社区维护的各 provider 峰谷时段快照。

一次更新如何到达你的屏幕:

  1. host 在启动时以及每 refreshIntervalHours(默认 24 小时)拉取目录,校验后写入磁盘缓存。
  2. host 通过 GET /peakrate/catalog 下发当前目录,并在 POST /peakrate/catalog(即「立即刷新」按钮)时按需重新拉取。
  3. client 启动时请求一次,拿到即重渲染。若请求失败 —— 离线、首次运行、被围栏拒绝 —— 会静默回退到构建期内置快照,所以徽章绝不会因为网络问题而消失。

因为页面读的是 host 的实时目录,数据更新无需重新构建或重装插件即可上屏。内置快照作为离线兜底始终保留。

路由带信任围栏

/peakrate/catalog 不是开放端点。它施加与 harness 自身 /api 相同的 browser-trust fence,防的是浏览器针对本地 HTTP 服务打开的两条 confused-deputy 通道:

  • DNS rebinding —— Host 头(rebinding 无法伪造它)必须是回环或 trustedHosts 里的 authority,否则 403。
  • 跨站请求 —— Sec-Fetch-Site: cross-site 直接拒绝;带 Origin 时要求它与 Host 同源。

它是信任围栏,不是认证层 —— 网络可达性仍归 webserver 管。该端点提供的是公开的定价数据。

拉取到的目录会被严格校验:未知 schema 版本、非法时钟值、零长度窗口、非 YYYY-MM-DD 日期、重复 profile id,都在解析期就被拒绝,而不是在之后产生一个错误判定。

架构

模块 职责
index.ts host:目录拉取/缓存、用户配置、settings 命名空间、下发路由
catalog.ts 解析并校验目录文档
catalog-route.ts 带信任围栏的 /peakrate/catalog 路由
schedule.ts · matching.ts · coverage.ts 纯函数:时段判定与倒计时 / provider+模型匹配 / 覆盖报告
client/ 三处呈现、运行时目录拉取与回退、选择器 fork、图标与样式

schedule.ts、matching.ts、coverage.ts 不依赖 DSH 运行时,所有时间边界与匹配规则都由单测覆盖。

三处呈现

位置 槽位 类型
composer 工具行 conversation.input.left 追加
模型选择器 conversation.input.model 有意接管(功能超集)
覆盖面板 plugins.bundle.config 追加,以 bundle 的 npm 包名(dsh-peakrate)为 key

模型选择器的接管是官方组件的完整超集 —— 键盘导航、aria 接线、portal 定位、加载/空/错误/重试态、推理等级二级菜单,一个都不少。上游包是 MIT,移植版本记录在 src/client/ModelSelect.tsx 中;test/bundle-contract.test.ts 的守卫测试会在任何其他自带 UI 槽位被遮蔽时让构建失败。

兼容性与贡献

已实测范围

DeepSeek Harness macOS 上的 0.1.7-rc.2
安装路径 用已发布的 npm 包在一个新建 profile 上做过端到端验证(dsh plugin --profile … add dsh-peakrate)—— bundle 注册、目录路由、三处呈现、控制台零错误
尚未验证 其他 DSH 版本;Linux 与 Windows;已经自定义过模型选择器的 profile

最可能出问题的是选择器接管:它替换的是一个 shipped-UI 槽位,因此上游一旦改动选择器结构, 就需要刷新移植。移植所依据的版本记录在 src/client/ModelSelect.tsx。

  • 需要提供 conversation.input.model 与 plugins.bundle.config 槽位的 DeepSeek Harness 构建。选择器移植自 @deepseek-ai/dsh-client-ui-model-selection@0.1.7-rc.2。
  • peer 依赖:@deepseek-ai/cordis、@deepseek-ai/schemastery。
  • 提交遵循 Conventional Commits;变更日志遵循 Keep a Changelog。
  • 发版:推一个 v* tag 即触发 release workflow —— 跑 typecheck 与测试、构建、带 provenance 发布到 npm,并从 CHANGELOG.md 生成 GitHub Release。
  • 欢迎在 github.com/log-li/dsh-peakrate 提 issue 与 PR。

贡献者

贡献者 角色
@log-li 作者与维护者

许可

MIT © Logan Lin

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。