549 lines
21 KiB
Markdown
549 lines
21 KiB
Markdown
# 智谱 GLM Coding Plan 用量查询对接文档
|
|
|
|
> **文档目的**:为后续开发者(或 AI 助手)提供智谱 GLM Coding Plan 用量查询的完整对接方案,基于 cc-switch 源码逆向 + 官方文档查证,内容**经过用户实测验证**(用户在 cc-switch 里能看到自己的套餐用量)。
|
|
>
|
|
> **权威来源**:以 `farion1231/cc-switch` 源码实现为准。该实现用户已验证可用。
|
|
>
|
|
> **本地参考源码**:`D:\AI Code\ESP-32\espcc-switch\`(已克隆,可直接查阅)
|
|
|
|
---
|
|
|
|
## 一、背景
|
|
|
|
ESP32-S3-RLCD-4.2 小屏需要显示智谱 GLM Coding Plan 的:
|
|
- 5 小时限额已用百分比 + 重置时间
|
|
- 周限额已用百分比 + 重置时间(新套餐才有)
|
|
- token 消耗统计
|
|
- 套餐层级
|
|
|
|
智谱官方**没有公开的用量查询 API 文档**,但通过逆向 cc-switch 找到了实际可用的工作端点。
|
|
|
|
## 二、关键结论(一句话总结)
|
|
|
|
| 问题 | 答案 |
|
|
|------|------|
|
|
| 用什么端点查? | `GET /api/monitor/usage/quota/limit` |
|
|
| 怎么鉴权? | **纯 API Key**,放 Authorization 头,**不加 `Bearer ` 前缀** |
|
|
| 需要浏览器 Cookie 吗? | **不需要**(跟之前社区工具 coding-plan-monitor 不一样) |
|
|
| ESP-32 能直接用吗? | **能**,纯 HTTP GET,`esp_http_client` 可 1:1 复现 |
|
|
| 怎么区分 5h vs 周? | 看 `limits[].unit` 字段:**3=5h, 6=周** |
|
|
| 怎么判断老/新套餐? | `limits[]` 里 `TOKENS_LIMIT` 条目数量(1=老无周, 2=新有周) |
|
|
| 能拿到绝对 token 数吗? | **不能**,quota/limit 只给百分比。绝对 token 要调 `model-usage` 端点 |
|
|
|
|
---
|
|
|
|
## 三、核心 API
|
|
|
|
### 端点
|
|
|
|
```
|
|
国内:GET https://open.bigmodel.cn/api/monitor/usage/quota/limit
|
|
国际:GET https://api.z.ai/api/monitor/usage/quota/limit
|
|
```
|
|
|
|
**域名选择**:
|
|
- 用户的 API Key 配在 `open.bigmodel.cn` → 用国内
|
|
- 用户的 API Key 配在 `api.z.ai` → 用国际
|
|
- 两条路径(path)完全一致,只是域名不同
|
|
|
|
**域名选择参考代码**(来自 cc-switch `coding_plan.rs:302-308`):
|
|
```rust
|
|
fn zhipu_quota_base(base_url: &str) -> &str {
|
|
if base_url.contains("bigmodel.cn") {
|
|
"https://open.bigmodel.cn"
|
|
} else {
|
|
"https://api.z.ai"
|
|
}
|
|
}
|
|
```
|
|
|
|
### 请求
|
|
|
|
```http
|
|
GET /api/monitor/usage/quota/limit HTTP/1.1
|
|
Host: open.bigmodel.cn
|
|
Authorization: <你的智谱 API_KEY>
|
|
Content-Type: application/json
|
|
Accept-Language: en-US,en
|
|
```
|
|
|
|
**关键点**:
|
|
- 方法:`GET`
|
|
- 鉴权:`Authorization` 头直接放 API Key 原值,**不要加 `Bearer ` 前缀**
|
|
- cc-switch 在 `coding_plan.rs:319` 显式注释:`// 注意:智谱不加 Bearer 前缀`
|
|
- 这跟 OpenAI、Anthropic 等标准 Bearer 鉴权不同,**是个例外**
|
|
- Content-Type: `application/json`
|
|
- Accept-Language: `en-US,en`(cc-switch 加了这个,建议照抄)
|
|
- **无请求体**
|
|
- **无查询参数**
|
|
- 建议超时:15 秒
|
|
- **不需要浏览器 Cookie**
|
|
- **不需要伪装 User-Agent**
|
|
|
|
### 返回 JSON 结构
|
|
|
|
```jsonc
|
|
{
|
|
"success": true,
|
|
"msg": "操作成功",
|
|
"data": {
|
|
"level": "Max",
|
|
"limits": [
|
|
{
|
|
"type": "TOKENS_LIMIT",
|
|
"unit": 3,
|
|
"number": 5,
|
|
"percentage": 32.5,
|
|
"nextResetTime": 1773734366338
|
|
},
|
|
{
|
|
"type": "TOKENS_LIMIT",
|
|
"unit": 6,
|
|
"number": 7,
|
|
"percentage": 45.0,
|
|
"nextResetTime": 1774663282997
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### 字段说明
|
|
|
|
| 字段路径 | 类型 | 含义 |
|
|
|---------|------|------|
|
|
| `success` | bool | 业务级成功标记,false 时读 `msg` |
|
|
| `msg` | string | 错误文案(success=false 时有用) |
|
|
| `data.level` | string | 套餐层级(如 `"Max"` / `"Pro"` / `"Lite"`) |
|
|
| `data.limits[]` | array | 限额窗口数组 |
|
|
| `data.limits[].type` | string | `"TOKENS_LIMIT"`(我们要的) / `"TIME_LIMIT"`(使用时长,跳过) |
|
|
| `data.limits[].unit` | int | **窗口类型,核心字段**:`3` = 5 小时,`6` = 每周 |
|
|
| `data.limits[].number` | int | 数量(5h 时为 5,周时为 7) |
|
|
| `data.limits[].percentage` | float | 已用百分比 0-100 |
|
|
| `data.limits[].nextResetTime` | int(ms) | 重置毫秒时间戳;0% 用量时可能缺失 |
|
|
|
|
---
|
|
|
|
## 四、字段→业务含义映射
|
|
|
|
| 屏幕显示 | 字段路径 | 备注 |
|
|
|---------|----------|------|
|
|
| 5 小时限额已用% | `limits[type=TOKENS_LIMIT && unit=3].percentage` | 0-100 |
|
|
| 5 小时重置时间 | `limits[type=TOKENS_LIMIT && unit=3].nextResetTime` | ms 时间戳 |
|
|
| 周限额已用% | `limits[type=TOKENS_LIMIT && unit=6].percentage` | **新套餐才有**,老套餐查不到 |
|
|
| 周限额重置时间 | `limits[type=TOKENS_LIMIT && unit=6].nextResetTime` | 同上 |
|
|
| 套餐层级 | `data.level` | 字符串 |
|
|
|
|
### 时间戳转换规则(用于显示)
|
|
|
|
根据之前 UI 设计讨论的结果,重置时间的显示策略:
|
|
- 距离当前时间 **≤ 24 小时** → 显示 `HH:MM`(如 `18:42`)
|
|
- 距离当前时间 **> 24 小时** → 显示 `MM-DD`(如 `06-28`)
|
|
|
|
参考实现(`formatResetTime`):
|
|
```javascript
|
|
function formatResetTime(resetMs) {
|
|
const diff = resetMs - Date.now();
|
|
const absDiff = Math.abs(diff);
|
|
const date = new Date(resetMs);
|
|
const pad = n => String(n).padStart(2, '0');
|
|
if (absDiff <= 24 * 3600 * 1000) {
|
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
} else {
|
|
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
}
|
|
}
|
|
```
|
|
|
|
ESP-32 C 实现可用 `<time.h>` 的 `localtime_r` + `difftime`。
|
|
|
|
---
|
|
|
|
## 五、⚠️ 三个必避的坑
|
|
|
|
### 坑 1:必须用 `unit` 字段区分 5h 和周,**不能按重置时间排序**
|
|
|
|
**问题**:cc-switch 在 issue #3036 踩过——每周周期末尾,周窗口会比 5 小时窗口**更早**重置,按 `nextResetTime` 升序排序必然把"5h"和"周"标反。
|
|
|
|
**正确做法**:只看 `unit` 字段:
|
|
- `unit == 3` → 5 小时窗口
|
|
- `unit == 6` → 周窗口
|
|
|
|
**只有当 `unit` 缺失或未识别时**,才回落到启发式(无 reset 优先归 5h,其余按 reset 升序填空槽)。
|
|
|
|
**源码引用**:`cc-switch/src-tauri/src/services/coding_plan.rs:226-234` 有显式注释说明这个坑。
|
|
|
|
### 坑 2:老套餐 vs 新套餐的判定
|
|
|
|
**判定方式不是某个标志位,而是 `limits[]` 数组里 `type==TOKENS_LIMIT` 条目的数量**:
|
|
|
|
- **老套餐**(2026-02-12 前订阅):只返回 **1 条** `TOKENS_LIMIT`(只有 5h,无周)
|
|
- **新套餐**:返回 **2 条** `TOKENS_LIMIT`(5h + 周)
|
|
|
|
**当前用户**:用的是老套餐(无周限额),所以查出来只会看到 1 条 `TOKENS_LIMIT`,没有 `unit=6` 的条目。
|
|
|
|
**代码处理建议**:用两个可空 slot(`five_hour` / `weekly`),有就填、没有就留空。屏幕显示时:
|
|
- 5h 没查到 → 显示 "—"
|
|
- 周限额没查到 → 显示 "无限制" 或 "—" 或 "OLD PLAN"
|
|
|
|
**源码参考**:`cc-switch/src-tauri/src/services/coding_plan.rs:279-291` 实现了天然降级逻辑。
|
|
|
|
### 坑 3:智谱接口**只给百分比,不给绝对 token 数**
|
|
|
|
**问题**:cc-switch 在 `coding_plan.rs:282-289` 明确:`used_value_usd` / `max_value_usd` 都是 `None`。智谱接口只返回 `percentage`,**不返回绝对 token 数**。
|
|
|
|
**影响**:之前 UI 设计里说的"token 消耗按 M/B 显示"**不能从 quota/limit 接口拿到**。
|
|
|
|
**解决方案**:要拿绝对 token 数,得调另一个端点:
|
|
```
|
|
GET /api/monitor/usage/model-usage?startTime=...&endTime=...
|
|
```
|
|
- 参数:`startTime` / `endTime` URL 编码后的 `"yyyy-MM-dd HH:mm:ss"` 格式
|
|
- 官方插件 zai-coding-plugins 的默认窗口:昨天当前小时整点 → 今天当前小时末秒
|
|
- **cc-switch 没用这个接口,所以返回字段结构需要实测确认**(官方插件只是 `JSON.stringify` 原样打印,没固定字段名)
|
|
|
|
**建议**:
|
|
1. 先做百分比显示(已用 X% + 重置时间),这是 quota/limit 能直接拿到的
|
|
2. 绝对 token 数等 model-usage 端点字段确认后再加,或者干脆**本地累加**(每次调 LLM 时把 `response.usage.total_tokens` 累计起来,自己统计今日/本周消耗)
|
|
|
|
---
|
|
|
|
## 六、ESP-32 实现参考(伪代码)
|
|
|
|
```c
|
|
#include "esp_http_client.h"
|
|
#include "cJSON.h"
|
|
|
|
// 1. 构造请求
|
|
esp_http_client_config_t config = {
|
|
.url = "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
|
|
.method = HTTP_METHOD_GET,
|
|
.timeout_ms = 15000,
|
|
.cert_pem = bigmodel_root_ca, // 需要 HTTPS 根证书
|
|
.user_agent = "esp32-rlcd", // 随便写,没限制
|
|
};
|
|
|
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
|
|
|
// 关键:Authorization 不加 Bearer 前缀!
|
|
char auth_header[256];
|
|
snprintf(auth_header, sizeof(auth_header), "%s", api_key);
|
|
esp_http_client_set_header(client, "Authorization", auth_header);
|
|
esp_http_client_set_header(client, "Content-Type", "application/json");
|
|
esp_http_client_set_header(client, "Accept-Language", "en-US,en");
|
|
|
|
// 2. 发请求
|
|
esp_err_t err = esp_http_client_perform(client);
|
|
if (err != ESP_OK) { /* 错误处理 */ }
|
|
|
|
int status = esp_http_client_get_status_code(client);
|
|
if (status == 401 || status == 403) {
|
|
// 凭据失效
|
|
} else if (status != 200) {
|
|
// 其他错误
|
|
}
|
|
|
|
// 3. 读响应体
|
|
char buffer[4096];
|
|
int len = esp_http_client_read(client, buffer, sizeof(buffer) - 1);
|
|
buffer[len] = '\0';
|
|
|
|
// 4. 解析 JSON
|
|
cJSON *root = cJSON_Parse(buffer);
|
|
cJSON *data = cJSON_GetObjectItem(root, "data");
|
|
cJSON *level = cJSON_GetObjectItem(data, "level");
|
|
const char *plan_level = level->valuestring; // "Max"
|
|
|
|
cJSON *limits = cJSON_GetObjectItem(data, "limits");
|
|
cJSON *limit;
|
|
cJSON_ArrayForEach(limit, limits) {
|
|
cJSON *type = cJSON_GetObjectItem(limit, "type");
|
|
if (!cJSON_IsString(type) || strcmp(type->valuestring, "TOKENS_LIMIT") != 0) {
|
|
continue; // 跳过 TIME_LIMIT
|
|
}
|
|
|
|
cJSON *unit = cJSON_GetObjectItem(limit, "unit");
|
|
cJSON *pct = cJSON_GetObjectItem(limit, "percentage");
|
|
cJSON *reset = cJSON_GetObjectItem(limit, "nextResetTime");
|
|
|
|
switch (unit->valueint) {
|
|
case 3: // 5 小时窗口
|
|
five_hour_pct = pct->valuedouble;
|
|
five_hour_reset_ms = reset ? reset->valuedouble : 0;
|
|
break;
|
|
case 6: // 周窗口(新套餐才有)
|
|
weekly_pct = pct->valuedouble;
|
|
weekly_reset_ms = reset ? reset->valuedouble : 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
esp_http_client_cleanup(client);
|
|
|
|
// 5. 转换时间戳为显示字符串(24h 内显示 HH:MM, 否则 MM-DD)
|
|
// 使用 <time.h> 的 localtime_r + difftime
|
|
```
|
|
|
|
---
|
|
|
|
## 七、鉴权验证(写代码前先做)
|
|
|
|
写代码前,**先用 curl 验证 API Key 能不能直接调通**。这是最省时间的步骤——避免代码写完才发现鉴权走不通。
|
|
|
|
```bash
|
|
curl -X GET \
|
|
-H "Authorization: <你的智谱 API_KEY>" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept-Language: en-US,en" \
|
|
https://open.bigmodel.cn/api/monitor/usage/quota/limit
|
|
```
|
|
|
|
**预期响应**(老套餐):
|
|
```json
|
|
{
|
|
"success": true,
|
|
"msg": "操作成功",
|
|
"data": {
|
|
"level": "Max",
|
|
"limits": [
|
|
{
|
|
"type": "TOKENS_LIMIT",
|
|
"unit": 3,
|
|
"number": 5,
|
|
"percentage": 32.5,
|
|
"nextResetTime": 1773734366338
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**判定**:
|
|
- ✅ 返回 200 + 有 `data.limits` → API Key 路线可行,ESP-32 可以直接复现
|
|
- ❌ 返回 401/403 → API Key 无效或权限不够
|
|
- ❌ 返回空 body 或非 JSON → 可能被反爬,但根据 cc-switch 经验这种情况**不应该发生**(cc-switch 用纯 API Key 跑通了)
|
|
|
|
---
|
|
|
|
## 八、源码引用清单
|
|
|
|
**权威实现**(cc-switch,用户已验证):
|
|
|
|
| 文件 | 行 | 作用 |
|
|
|------|-----|------|
|
|
| `src-tauri/src/services/coding_plan.rs` | 302-324 | `query_zhipu` 端点+请求头构造 |
|
|
| `src-tauri/src/services/coding_plan.rs` | 319 | "// 注意:智谱不加 Bearer 前缀" |
|
|
| `src-tauri/src/services/coding_plan.rs` | 218-224 | `classify_zhipu_window` 按 unit 分类 |
|
|
| `src-tauri/src/services/coding_plan.rs` | 226-234 | unit 字段坑的注释(issue #3036) |
|
|
| `src-tauri/src/services/coding_plan.rs` | 238-292 | `parse_zhipu_token_tiers` 解析逻辑 |
|
|
| `src-tauri/src/services/coding_plan.rs` | 279-291 | 老/新套餐天然降级(可空 slot) |
|
|
| `src-tauri/src/services/coding_plan.rs` | 282-289 | 只读 percentage,绝对值为 None |
|
|
| `src-tauri/src/services/subscription.rs` | 301, 308 | `TIER_FIVE_HOUR` / `TIER_WEEKLY_LIMIT` 常量 |
|
|
| `src-tauri/src/services/coding_plan.rs` | 1224-1240 | 测试:老套餐单 tier 降级到 5h |
|
|
| `src-tauri/src/services/coding_plan.rs` | 1316-1333 | 测试:unit 字段覆盖 reset 排序 |
|
|
|
|
**本地路径**:`D:\AI Code\ESP-32\espcc-switch\`(已克隆)
|
|
|
|
**辅助参考**(官方插件):
|
|
- `zai-coding-plugins/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs` — 调用了 3 个端点(quota/limit + model-usage + tool-usage),quota/limit 字段处理跟 cc-switch 一致
|
|
|
|
**辅助参考**(社区工具,鉴权方式不同,**不要照抄**):
|
|
- `JinHanAI/coding-plan-monitor/src/providers/zhipu.ts` — 走浏览器 Cookie,不支持 API Key 直连
|
|
|
|
---
|
|
|
|
## 九、官方文档(背景参考)
|
|
|
|
- [智谱AI开放文档 - Coding Plan FAQ](https://docs.bigmodel.cn/cn/coding-plan/faq) — 5h + 周限额机制说明
|
|
- [智谱AI开放文档 - 套餐概览](https://docs.bigmodel.cn/cn/coding-plan/overview) — 各套餐限额参考表
|
|
- [智谱AI开放文档 - 用量查询插件](https://docs.bigmodel.cn/cn/coding-plan/extension/usage-query-plugin) — 官方插件介绍
|
|
- [智谱AI开放文档 - HTTP API 调用](https://docs.bigmodel.cn/cn/guide/develop/http/introduction) — 基础 HTTP 调用规范
|
|
- [cc-switch GitHub](https://github.com/farion1231/cc-switch) — 权威实现来源
|
|
- [cc-switch Issue #1857 - 用量统计与计费追踪](https://github.com/farion1231/cc-switch/issues/1857)
|
|
- [cc-switch Issue #1588 - 智谱用量查询脚本示例](https://github.com/farion1231/cc-switch/issues/1588)
|
|
|
|
---
|
|
|
|
## 十、对接 Checklist(给 codex 用的执行清单)
|
|
|
|
- [ ] **Step 1**:用 curl 验证 API Key 能调通(参考第七节)
|
|
- [ ] **Step 2**:在 ESP-32 项目里加 HTTPS 根证书(open.bigmodel.cn 的根 CA)
|
|
- [ ] **Step 3**:实现 `zhipu_query_quota()` 函数,封装 HTTP GET + JSON 解析
|
|
- [ ] **Step 4**:实现 `unit` 字段分类(3=5h, 6=周),不要按时间排序
|
|
- [ ] **Step 5**:实现老/新套餐降级(周限额查不到时显示"无限制")
|
|
- [ ] **Step 6**:实现时间戳→显示字符串转换(24h 内 HH:MM,否则 MM-DD)
|
|
- [ ] **Step 7**:绝对 token 数(如果需要)→ 调 `model-usage` 端点实测字段,或本地累加每次 LLM 调用的 `usage.total_tokens`
|
|
- [ ] **Step 8**:把查询结果通过 BLE/USB 推送到板子(参考现有 `metrics_*` 模块的推送方式)
|
|
|
|
---
|
|
|
|
## 附:接口对比速查表
|
|
|
|
| 项目 | 端点 | 鉴权 | 用户验证 | 推荐用 |
|
|
|------|------|------|----------|--------|
|
|
| **cc-switch** | `/api/monitor/usage/quota/limit` | 纯 API Key(无 Bearer) | ✅ 用户已验证 | ✅ **本文档基准** |
|
|
| zai-coding-plugins(官方) | 同上 + `model-usage` + `tool-usage` | API Key(无 Bearer) | ✅ 官方插件 | 辅助参考 |
|
|
| coding-plan-monitor(社区) | 同上 | 浏览器 Cookie | ⚠️ 仅浏览器 | ❌ 不照抄 |
|
|
|
|
---
|
|
|
|
## 十一、token 使用量获取方案
|
|
|
|
> **核心结论**:`quota/limit` 端点**只返回百分比,不返回绝对 token 数**。要拿"今日用了多少 token",有**三种方案**,各有取舍。
|
|
|
|
### 三种方案对比
|
|
|
|
| 方案 | 原理 | 优点 | 缺点 | 推荐度 |
|
|
|------|------|------|------|--------|
|
|
| **A. 调 model-usage 端点** | 查询时间窗口内的 token 消耗明细 | 服务端权威数据,无需本地存储 | ⚠️ 字段结构官方未公开,需实测 | ⭐⭐⭐ |
|
|
| **B. 本地累加(推荐)** | 每次调 LLM 时累计响应里的 `usage.totalTokens` | 字段确定(智谱对话补全 API 标准),实现简单 | 只统计"经过本代理的"调用,跨设备/跨工具会漏 | ⭐⭐⭐⭐⭐ |
|
|
| **C. 只显示百分比** | 直接用 quota/limit 的 `percentage` | 零开发量,已有数据 | 没有绝对值,信息量少 | ⭐⭐ |
|
|
|
|
### 方案 A:model-usage 端点(需实测字段)
|
|
|
|
**端点**:
|
|
```
|
|
GET https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=...&endTime=...
|
|
```
|
|
|
|
**请求**(跟 quota/limit 同款鉴权):
|
|
```http
|
|
GET /api/monitor/usage/model-usage?startTime=2026-06-23%2000%3A00%3A00&endTime=2026-06-23%2023%3A59%3A59 HTTP/1.1
|
|
Host: open.bigmodel.cn
|
|
Authorization: <API_KEY> ← 不加 Bearer
|
|
Content-Type: application/json
|
|
Accept-Language: en-US,en
|
|
```
|
|
|
|
**参数**:
|
|
- `startTime` / `endTime`:URL 编码后的 `"yyyy-MM-dd HH:mm:ss"` 格式
|
|
- 官方插件默认窗口:昨天当前小时整点 → 今天当前小时末秒
|
|
|
|
**返回 JSON 结构(⚠️ 推断,需实测)**:
|
|
```jsonc
|
|
{
|
|
"success": true,
|
|
"code": 200,
|
|
"msg": "ok",
|
|
"data": {
|
|
"usageList": [ // 字段名推断, 实测可能不同
|
|
{
|
|
"modelName": "glm-4.6",
|
|
"totalTokens": 123456,
|
|
"promptTokens": 100000, // 智谱用驼峰命名(不是 prompt_tokens)
|
|
"completionTokens": 23456
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**⚠️ 字段未公开的证据**:
|
|
- 官方插件 `query-usage.mjs:167` 调 model-usage 时**没传 postProcessor**,line 147 直接 `JSON.stringify(outputData)` 原样打印给 LLM
|
|
- 官方 agent 文档 `usage-query-agent.md` 也没描述字段结构
|
|
- 说明官方故意不固定字段文档(可能经常变),让 LLM 运行时自适应解读
|
|
|
|
**实测步骤**(必做):
|
|
```bash
|
|
curl -X GET \
|
|
-H "Authorization: <你的 API_KEY>" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept-Language: en-US,en" \
|
|
"https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=$(date -d 'yesterday' +%Y-%m-%d)%2000%3A00%3A00&endTime=$(date +%Y-%m-%d)%2023%3A59%3A59"
|
|
```
|
|
拿到真实 JSON 后,根据实际字段名写解析代码。
|
|
|
|
### 方案 B:本地累加(推荐 ⭐)
|
|
|
|
**原理**:智谱对话补全 API 的**每次响应**都带 `usage` 字段,本地累加即可。
|
|
|
|
**智谱对话补全响应里的 usage 字段**(官方文档确认):
|
|
```jsonc
|
|
{
|
|
"choices": [ ... ],
|
|
"usage": {
|
|
"promptTokens": 123, // 输入 token(驼峰命名!)
|
|
"completionTokens": 456, // 输出 token
|
|
"totalTokens": 579 // 总计
|
|
}
|
|
}
|
|
```
|
|
|
|
**⚠️ 命名坑**:智谱用**驼峰**(`promptTokens`),不是 OpenAI 的下划线(`prompt_tokens`)。解析时别搞混。
|
|
|
|
**实现方式**(cc-switch 就是这么做的):
|
|
- cc-switch 在 `src-tauri/src/proxy/usage/parser.rs:17-22` 定义了 `TokenUsage` 结构
|
|
- 每次代理 LLM 请求时,从响应里解析 `usage`,写入本地 SQLite(`usage_stats.rs`)
|
|
- 查询时 `get_usage_summary` 聚合统计
|
|
|
|
**ESP-32 上的简化实现**:
|
|
- 如果电脑端有代理服务(像 cc-switch 那样),在代理层累加 token,定期推给板子
|
|
- 如果板子自己调 LLM,在板子上累加(但 ESP-32 存储有限,建议只存今日/本周两个计数器)
|
|
|
|
**为什么推荐**:
|
|
1. 字段确定(智谱对话补全 API 标准,有官方文档)
|
|
2. 实现简单(就是累加)
|
|
3. 不依赖未公开的 model-usage 端点
|
|
4. cc-switch 验证过这条路可行
|
|
|
|
### 方案 C:只显示百分比
|
|
|
|
直接用 `quota/limit` 的 `TOKENS_LIMIT.percentage`,显示"5H 已用 32%"。
|
|
|
|
适合 MVP 阶段——零开发量,数据现成。后续再加方案 A 或 B 补绝对值。
|
|
|
|
### 给 codex 的建议
|
|
|
|
1. **第一阶段**:用方案 C(百分比),先把 quota/limit 跑通
|
|
2. **第二阶段**:如果电脑端有代理服务,用方案 B(本地累加)——字段确定、实现简单
|
|
3. **第三阶段**:如果需要服务端权威数据,用方案 A——但**必须先 curl 实测字段**,不要按本文档的推断结构写代码
|
|
|
|
---
|
|
|
|
## 十二、cc-switch 的 token 统计实现(方案 B 参考)
|
|
|
|
cc-switch 不调 model-usage 端点,而是**本地 SQLite 累加**。这是方案 B 的最佳参考。
|
|
|
|
### 核心文件
|
|
|
|
| 文件 | 作用 |
|
|
|------|------|
|
|
| `src-tauri/src/proxy/usage/parser.rs` | 从 LLM 响应解析 `TokenUsage`(支持 Claude/OpenAI 格式) |
|
|
| `src-tauri/src/services/usage_stats.rs` | SQLite 存储 + 聚合查询(`get_usage_summary`) |
|
|
| `src-tauri/src/services/session_usage_codex.rs` | Codex 专用 session 用量 |
|
|
|
|
### TokenUsage 结构(parser.rs:17-22)
|
|
|
|
```rust
|
|
pub struct TokenUsage {
|
|
pub input_tokens: u32,
|
|
pub output_tokens: u32,
|
|
pub cache_read_tokens: u32,
|
|
pub cache_creation_tokens: u32,
|
|
}
|
|
```
|
|
|
|
### 解析入口
|
|
|
|
- `from_claude_response`(parser.rs:66)— 解析 Claude API 响应的 `usage.input_tokens` / `output_tokens`
|
|
- 也支持 OpenAI 兼容格式(`prompt_tokens` / `completion_tokens`)
|
|
|
|
### 统计查询
|
|
|
|
`get_usage_summary`(usage_stats.rs:505-664)支持按时间范围聚合,返回各模型的 token 总量。
|
|
|
|
**本地路径**:`D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs`
|
|
|
|
---
|
|
|
|
## Sources(token 使用量部分)
|
|
|
|
- [智谱AI开放文档 - 核心参数(Token 定义)](https://docs.bigmodel.cn/cn/guide/start/concept-param) — promptTokens/completionTokens/totalTokens 定义
|
|
- [智谱AI开放文档 - 对话补全 API](https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8) — usage 字段在响应里的位置
|
|
- [智谱AI开放文档 - API 价格](https://open.bigmodel.cn/pricing) — token 计费说明
|
|
- [GitHub Issue zhayujie/CowAgent#2289](https://github.com/zhayujie/CowAgent/issues/2289) — 智谱驼峰命名 vs OpenAI 下划线命名的兼容性问题
|
|
- [cc-switch Discussion #1038 - 智谱用量查询脚本](https://github.com/farion1231/cc-switch/discussions/1038) — model-usage 端点调用示例
|
|
- 本地源码:`D:\esp\zai-coding-plugins\plugins\glm-plan-usage\skills\usage-query-skill\scripts\query-usage.mjs`(官方插件,证实 model-usage 字段未公开)
|
|
- 本地源码:`D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs`(本地累加实现)
|