# dsh-provider-info

一个 [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) 插件。

它的作用只有一件事：**在你选择模型的地方，旁边显示一行小字，告诉你当前选中的模型是哪家服务商（公司）提供的。**

把鼠标移动到这行小字上，会弹出一个深色小面板，列出这家服务商和当前模型的详细信息。

## 这是什么问题

DeepSeek Harness 可以同时配置多家 AI 服务商，每家服务商下面又挂着许多模型。光看模型名称，你很容易搞不清它到底走的是谁的接口、用的是哪家服务。这个插件就是想让你一眼看清楚：**我现在选的模型，是哪家提供的。**

## 它能干什么

- **模型旁边多一行小字**：显示当前模型所属服务商的名称。如果服务商没有设置名称，就显示它的编号（像 ID 一样的代号）。
- **鼠标悬停弹出小面板**，分两部分：
  - **服务商信息**：显示名称、编号、用的 API 协议（比如 OpenAI 兼容、Anthropic 等）、API 地址、密钥对应的环境变量名。
  - **当前模型信息**：模型编号、显示名、描述、支持哪些推理等级、当前用的推理等级、上下文窗口大小、最大 token、支持哪些输入类型、兼容信息。
- 没有填的信息会显示「未提供」；文字太长会自动换行。
- 切换模型后，旁边那行小字会自动跟着变。
- **悬停即显**：鼠标移到提供商标签上立刻显示面板（用本地缓存同步渲染，余量未就绪时先显示「刷新中…」，数据回来后在后台补齐），不会出现“悬停没反应、移开后才弹出”。
- **标签与选择器分离**：标签挂在 DSH 官方槽位 `conversation.input.right`（模型座左侧的官方空槽），由 DSH 布局系统自动排列，不侵入原生模型选择器；旧版 DSH 没有该槽时自动回退到兼容方式。
- **不碰密钥**：它只显示密钥对应的*环境变量名*，绝不会读取或显示密钥的真实内容。
- **余量区块**：对支持查询的厂商，悬浮浮层还会展示当前提供商的余量。**两类提供商共用同一套呈现规则**：
  - **余额型**（如 **DeepSeek**）：一行「余额」，显示账户金额（多币种，如 `¥20.38 / $0.00`）。
  - **套餐型**（如 **OpenCode Go**、**Command Code**）：按窗口逐行显示，行名是窗口周期名 `5小时 / 周 / 月`，行内容是 `已用% · $已用/$总额 · 重置倒计时`；浮层里这几行按**四列网格对齐**（标签 | 比例 | 金额 | 倒计时，金额不带括号、段间用 `·` 分隔、各列右边缘对齐，比例从 `4.00%` 到 `77.00%` 宽度不同也不会错位）；厂商提供订阅计费周期时，最后再加一行「到期」（如 `2026-10-08 · 剩 27 天`，按普通行渲染，不参与窗口列的对齐）。
    - **OpenCode Go**：`5小时 / 周 / 月` 三个滚动窗口。接口只回百分比 + 重置时间，金额按官方套餐价常量折算；接口**没有独立的订阅字段**，但月窗的重置时间就是计费周期终点（周窗锚在周一 00:00、月窗锚在订阅起算日 + 1 个月），所以它同时作为「到期」显示；「月」行照旧带自己的重置倒计时，三行窗口的呈现方式保持一致（「到期」行单独给日期，开启「显示更多信息」后附「剩 N 天」）。
    - **Command Code**（GOAT / Pro / Max 等套餐，如 `https://api.commandcode.ai/provider/v1`）：`5小时 / 周` 两个窗口 + 月度池（归一到「月」窗口，总额度 = 周窗 × 2 动态推导）+ 订阅「到期」（官方 CLI 同款内部接口，只读）。
    - 窗口时长以厂商为准：常见档位显示为 `5小时 / 周 / 月`，厂商换成别的时长就按真实时长显示（如 `5天`），不需要改代码。
  - 不支持查询的提供商（含未识别厂商）浮窗照常显示「当前暂不支持查询当前提供商」；未配置密钥显示「未配置 API Key」；查不到数据时按**原因**给话：`网络不可达` / `查询超时` / `请求过频（429）` / `服务端错误 503` / `HTTP 状态 4xx`，实在归不了类才是「查询失败」。
  - **设置 → 提供商信息** 里的「全部提供商余量」表格与浮层是同一份数据、同一套列名：`提供商 | 5小时 | 周 | 月 | 余额 | 到期 | 操作`，谁有数据填谁，没有的留空；查不了/出错的厂商，状态跟在名称后面，不占数据列。
  - 这些查询都是**只读**的，不会扣费、不会消耗 token。
  - host 端按 provider 缓存 5 分钟，反复悬浮不会频繁打厂商接口。
  - **瞬时失败会自己好**：连接被 reset（`ECONNRESET`）、429、5xx 自动重试一次（超时的第二次把等待上限压到 8 秒）；**失败结果只缓存 20 秒**（成功仍是 5 分钟），一次网络抖动不会再让设置页表格把「查询失败」挂满 5 分钟；打开设置页时首轮失败的话，1.8 秒后还会自动再查一次，不用手点刷新。原始错误会打到 host 控制台：`[provider-badge] 余量查询失败 { provider, family, error }`。
  - 余量区块带「刷新」按钮：点击立即绕开缓存、强制拉取最新余量（仍只读、不扣费）；平时悬停走 5 分钟缓存。
  - 鼠标悬停到浮窗/按钮上时，消息流滚动不会隐藏浮窗，方便查看；移开后才因滚动隐藏。
  - **刷新行为可在「设置 → 提供商信息」调整**：开启*显示悬浮窗自动刷新*（默认开）则浮窗打开时立即重新查询最新余量（绕开缓存）；或开启*定时刷新*（默认关）并设置间隔（默认 5 分钟，最低 1 分钟），在浮窗打开时定时重新查询。设置持久化于 `$DSH_HOME/dsh-provider-info.json`（权限 0600）。

## 效果图

模型选择按钮旁边的那行小字（这里显示提供商是 `opencode-go`）：

![模型选择处的提供商标签](docs/model-selector.png)

鼠标移上去弹出的详细信息面板：

![悬停查看提供商与模型详情](docs/provider-panel.png)

## 怎么安装

这个插件需要通过 DSH 的 profile 机制加载（比如 `web` profile）。

### 方式一：用命令安装

```bash
dsh plugin --profile web add dsh-provider-info
```

### 方式二：手动配置

在 profile 的依赖里加上它：

```json
// profile 的 package.json
"dependencies": { "dsh-provider-info": "^0.1.0" }
```

再在 profile 配置里声明加载它：

```yaml
# profile 配置
dsh:
  profile:
    bundles:
      - dsh-provider-info
```

装好后重启一次 `dsh web`，然后刷新页面。

## 设置

打开 **设置 → 提供商信息** 配置余量刷新行为：

- **显示悬浮窗自动刷新**（默认勾选）：鼠标移到徽章上、浮窗打开时就立即重新查询最新余量，绕开 5 分钟缓存。
- **定时刷新**（默认不勾选）+ **定时刷新间隔(分钟)**（默认 5，最低 1）：浮窗打开时按设定间隔定时重新查询余量（未开启定时刷新时，下方的间隔输入框禁用变灰）。
- **显示更多信息**（默认勾选）：勾选时窗口额外显示 `（已用/总额）` 金额、浮层显示重置倒计时、到期显示剩余天数；不勾选时余量只显示**比例数字**与**到期日期**（界面更干净），此时比例作为右对齐的值直接贴着面板右边缘，与「到期」以及上方「提供商 / 当前模型」各行的值对齐。浮层与设置页表格同时生效。
- **界面语言**（默认「跟随系统(dsh)」）：设置悬浮面板与设置页的语言——「跟随系统(dsh)」跟随 DSH 界面语言（中文界面→中文，英文界面→英文）；「中文」强制中文；「English」强制英文。强制语言只影响本插件，不会改动 DSH 自身的语言设置。

改动会保存到 `$DSH_HOME/dsh-provider-info.json` 并立即生效。


## 怎么用

不需要任何操作。打开对话界面，模型选择按钮旁边就会自动出现那行小字。鼠标移上去就能看到详细信息。

## 隐私说明

这个插件只是读取你本机正在运行的 DSH 实例里的服务商配置，用来显示信息。所有数据都只在你自己的浏览器里展示，**不会发送到任何地方**。密钥值从头到尾都不会被读取或显示，只显示密钥对应的环境变量名。

## 许可证

[MIT](LICENSE)
