@ponydev/dsh-model-usage 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.7 — 2026-10-20
4
+
5
+ ### 修复
6
+ - **凭据引用改为「路由派生优先」**:如 `opencode-go` → `OPENCODE_GO_API_KEY`(与 DSH Models 页的 `apiKeyEnv` 一致),修复已配置 Key 的供应商仍显示未配置/不显示的问题
7
+
8
+ ## 1.0.6 — 2026-10-20
9
+
10
+ ### 新增
11
+ - **供应商覆盖对齐 DSH provider 目录(43 个)**:
12
+ - 新增适配器:OpenCode Go(`zen/go/v1/usage`,5h/周/月)、Moonshot/Kimi 平台余额、MiniMax Token Plan(5h/周)、Xiaomi MiMo 余额、GitHub Copilot 配额(Premium/Chat/补全)
13
+ - `deepseek` 路由也映射到 DeepSeek 余额适配器
14
+ - 新增「每月限额」窗口标签
15
+ - `docs/providers.md`:完整覆盖矩阵(真实额度 / 订阅待确认 / 无公开接口 / customQuota 接入方式)
16
+
17
+ ### 变更
18
+ - 无公开额度接口的 provider(Anthropic/OpenAI/Google/Bedrock/Azure/Groq 等)保持隐藏,不产生错误卡片
19
+
3
20
  ## 1.0.5 — 2026-10-20
4
21
 
5
22
  ### 新增
package/README.md CHANGED
@@ -37,7 +37,7 @@ dsh plugin --profile web add @ponydev/dsh-model-usage
37
37
  dsh plugin --profile web add link:/path/to/dsh-model-usage
38
38
  ```
39
39
 
40
- ## 供应商支持
40
+ ## 供应商支持(完整矩阵见 [docs/providers.md](docs/providers.md))
41
41
 
42
42
  | 供应商 | 数据接口 | 凭据引用 |
43
43
  |---|---|---|
@@ -46,6 +46,11 @@ dsh plugin --profile web add link:/path/to/dsh-model-usage
46
46
  | GLM Coding Plan(国内 / 国际) | `open.bigmodel.cn` / `api.z.ai` 额度接口(5 小时 / 每周 / **每月 MCP 工具次数**) | `ZAI_CODING_CN_API_KEY` / `ZAI_API_KEY` |
47
47
  | OpenRouter | `openrouter.ai/api/v1/credits`(兜底 `/api/v1/key`) | `OPENROUTER_API_KEY` |
48
48
  | Kimi Coding Plan | `api.kimi.com/coding/v1/usages` | `KIMI_CODING_API_KEY` / `KIMI_API_KEY` / `MOONSHOT_API_KEY` |
49
+ | Moonshot / Kimi 平台 | `api.moonshot.{ai,cn}/v1/users/me/balance` | `MOONSHOT_API_KEY` / `MOONSHOT_CN_API_KEY` |
50
+ | MiniMax Token Plan | `api.minimax.io` / `api.minimaxi.com` `/v1/token_plan/remains` | `MINIMAX_API_KEY` / `MINIMAX_CN_API_KEY` |
51
+ | OpenCode Go | `opencode.ai/zen/go/v1/usage` | `OPENCODE_API_KEY` |
52
+ | Xiaomi MiMo | `platform.xiaomimimo.com/api/v1/balance` | `XIAOMI_API_KEY` |
53
+ | GitHub Copilot | `api.github.com/copilot_internal/user` | `GITHUB_TOKEN` |
49
54
  | 其他任意供应商 | `customQuota` 配置(URL + JSON 路径映射) | 自定义 `keyRef` |
50
55
 
51
56
  `customQuota` 示例(余额式):
@@ -0,0 +1,67 @@
1
+ # 供应商覆盖矩阵
2
+
3
+ DSH(`@deepseek-ai/dsh` 0.2.0-rc.2)的 pi-ai provider 目录共 43 个 provider。本插件按「**有真实额度接口才显示,没有就隐藏**」的原则覆盖。
4
+
5
+ 图例:
6
+ - ✅ **真实额度**:调用服务商官方接口,显示余额或窗口剩余
7
+ - 🟡 **订阅/接口待确认**:接口存在但需要特定 Key 类型,失败时优雅隐藏
8
+ - ⬜ **无公开接口**:服务商未提供可用 API Key 查询的余额接口 → 不显示卡片(不会报错)
9
+ - 🔧 **customQuota**:可用配置接入(URL + JSON 路径映射)
10
+
11
+ ## 已支持(真实额度)
12
+
13
+ | Provider id | 数据接口 | 凭据引用 | 展示 |
14
+ |---|---|---|---|
15
+ | `deepseek-official` / `deepseek` | `api.deepseek.com/user/balance` | `DEEPSEEK_API_KEY` | 余额(总/充值/赠金) |
16
+ | `deepseek-account` | DSH `deepseekAccount` 服务 | 登录态 | 账号钱包 |
17
+ | `zai` / `zai-coding-cn` | `open.bigmodel.cn` / `api.z.ai` 额度接口 | `ZAI_CODING_CN_API_KEY` / `ZAI_API_KEY` | 5 小时 / 每周 / **每月 MCP 工具次数** |
18
+ | `openrouter` | `openrouter.ai/api/v1/credits`(兜底 `/api/v1/key`) | `OPENROUTER_API_KEY` | credits 余额 |
19
+ | `kimi-coding` / `kimi-coding-cn` | `api.kimi.com/coding/v1/usages` | `KIMI_CODING_API_KEY` / `KIMI_API_KEY` | 周 + 滚动窗口 |
20
+ | `moonshotai` / `moonshotai-cn` | `api.moonshot.{ai,cn}/v1/users/me/balance` | `MOONSHOT_API_KEY` / `MOONSHOT_CN_API_KEY` | 平台余额 |
21
+ | `minimax` / `minimax-cn` | `api.minimax.io` / `api.minimaxi.com` `/v1/token_plan/remains` | `MINIMAX_API_KEY` / `MINIMAX_CN_API_KEY` | 5h + 周(订阅 Key) |
22
+ | `opencode-go` | `opencode.ai/zen/go/v1/usage` | `OPENCODE_API_KEY` | 5h / 周 / 月 |
23
+ | `xiaomi*`(含 token-plan) | `platform.xiaomimimo.com/api/v1/balance` | `XIAOMI_API_KEY` | 余额 |
24
+ | `github-copilot` | `api.github.com/copilot_internal/user` | `GITHUB_TOKEN` / `GH_TOKEN` | Premium / Chat / 补全 配额 |
25
+
26
+ ## 无公开余额接口(隐藏,不报错)
27
+
28
+ | Provider id | 说明 |
29
+ |---|---|
30
+ | `anthropic` | 无用户 Key 余额接口(组织用量需 Admin Key + 控制台) |
31
+ | `openai`、`openai-codex` | 用量接口需组织 Admin Key / ChatGPT OAuth,DSH 的普通 Key 不可用 |
32
+ | `google`、`google-vertex` | 需 GCP 项目计费 API,非 Key 级 |
33
+ | `amazon-bedrock` | 需 AWS IAM/CloudWatch 计费 |
34
+ | `azure-openai-responses` | 需 Azure 订阅计费 API |
35
+ | `groq`、`cerebras`、`fireworks`、`together`、`baseten`、`nvidia`、`huggingface`、`mistral`、`meta`、`cloudflare-ai-gateway`、`cloudflare-workers-ai`、`vercel-ai-gateway`、`radius`、`ant-ling`、`opencode` | 无 Key 级余额/配额查询接口 |
36
+ | `qwen-token-plan` / `-cn` / `-individual` | 阿里云百炼暂未提供 Token Plan 用量 OpenAPI(控制台可见) |
37
+
38
+ > 这些 provider 的卡片**直接隐藏**,不影响插件运行;需要展示时用 `customQuota` 配置接入,或提 issue 附接口文档。
39
+
40
+ ## 用 customQuota 接入任意供应商
41
+
42
+ ```yaml
43
+ config:
44
+ customQuota:
45
+ my-provider:
46
+ url: https://api.example.com/quota
47
+ auth: bearer # bearer | raw | x-api-key | none
48
+ keyRef: MY_PROVIDER_API_KEY
49
+ balance: { path: data.balance, currency: CNY }
50
+ # 或窗口式
51
+ windows:
52
+ - key: rolling
53
+ label: 5h
54
+ usedPath: data.used
55
+ limitPath: data.limit
56
+ resetAtPath: data.resetAt
57
+ resetAtUnit: ms # ms | s | iso
58
+ ```
59
+
60
+ ## 新增适配器
61
+
62
+ 1. 在 `lib/adapters.js` 新增 `{ id, refs, read(env) }`
63
+ 2. `adapterFor()` 加路由匹配(注意顺序:更具体的路由在前,如 `kimi-coding` 先于 `kimi`)
64
+ 3. 返回 `{ status:'ok', balance }` 或 `{ status:'ok', windows:[{key,label,usedPct,resetAt,countBased?,remaining?,limit?}] }`
65
+ 4. 失败一律:`needs-credential` / `unauthorized` / `unsupported`(不抛未捕获异常)
66
+ 5. 只允许写死白名单域名,错误信息必须脱敏
67
+ 6. 更新本文档矩阵 + `package.json` version(patch/minor)+ `CHANGELOG.md`
package/lib/adapters.js CHANGED
@@ -53,9 +53,18 @@ export async function fetchT(url, init, ms) {
53
53
  }
54
54
 
55
55
  /** 按顺序解析凭据 ref:优先注入作用域的 credentials 服务,再回退环境变量 */
56
+ function routeKeyRef(route) {
57
+ const base = String(route || '').toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, '')
58
+ return base ? base + '_API_KEY' : null
59
+ }
60
+
56
61
  export async function resolveKey(env) {
57
62
  const cred = env.credentials
58
- for (const ref of env.refs || []) {
63
+ // 优先按路由派生的引用(如 opencode-go → OPENCODE_GO_API_KEY,与 DSH Models 页 apiKeyEnv 一致)
64
+ const refs = [...(env.refs || [])]
65
+ const derived = routeKeyRef(env.route)
66
+ if (derived && !refs.includes(derived)) refs.unshift(derived)
67
+ for (const ref of refs) {
59
68
  if (!ref) continue
60
69
  if (cred && typeof cred.resolve === 'function') {
61
70
  for (let attempt = 0; attempt < 2; attempt++) {
@@ -419,6 +428,201 @@ const openrouter = {
419
428
  },
420
429
  }
421
430
 
431
+ // OpenCode Go(订阅):GET https://opencode.ai/zen/go/v1/usage Bearer
432
+ // 响应 { usage: { rolling: { percent, resetsAt }, weekly: {...}, monthly: {...} } }
433
+ const opencodeGo = {
434
+ id: 'opencode-go',
435
+ refs: (cfg, route) => [cfg.credentials[route] || 'OPENCODE_API_KEY'],
436
+ async read(env) {
437
+ const key = await resolveKey(env)
438
+ if (!key) return { status: 'needs-credential', credential: { refs: env.refs, configured: false } }
439
+ const res = await fetchT('https://opencode.ai/zen/go/v1/usage', { headers: { authorization: 'Bearer ' + key, accept: 'application/json' } }, 10000)
440
+ if (res.status === 401 || res.status === 403) throw err('model-usage/unauthorized', 'HTTP ' + res.status + '(OpenCode Key 无效)')
441
+ if (!res.ok) throw err('model-usage/http', 'HTTP ' + res.status)
442
+ const data = await res.json()
443
+ const u = (data && data.usage) || {}
444
+ const windows = []
445
+ for (const [key2, label] of [['rolling', '5h'], ['weekly', '周'], ['monthly', '月']]) {
446
+ const it = u[key2]
447
+ if (!it || typeof it !== 'object') continue
448
+ const pct = Number(it.percent)
449
+ if (!Number.isFinite(pct)) continue
450
+ const resetRaw = it.resetsAt
451
+ const resetAt = typeof resetRaw === 'string' ? Date.parse(resetRaw) || null : (Number(resetRaw) || null)
452
+ windows.push({ key: key2 === 'rolling' ? 'rolling' : key2 === 'weekly' ? 'weekly' : 'monthly', label, usedPct: clampPct(pct), resetAt })
453
+ }
454
+ if (!windows.length) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'OpenCode Go 未返回 usage 窗口' } }
455
+ return { status: 'ok', fetchedAt: Date.now(), credential: { refs: env.refs, configured: true }, windows }
456
+ },
457
+ }
458
+
459
+ // Moonshot / Kimi 平台余额:GET https://api.moonshot.{cn,ai}/v1/users/me/balance Bearer
460
+ // 响应 { data: { available_balance, voucher_balance, cash_balance } }
461
+ const moonshotBalance = {
462
+ id: 'moonshot-balance',
463
+ refs: (cfg, route) => [cfg.credentials[route] || (/cn$/.test(route) ? 'MOONSHOT_CN_API_KEY' : 'MOONSHOT_API_KEY'), 'MOONSHOT_API_KEY', 'KIMI_API_KEY', 'MOONSHOT_CN_API_KEY'],
464
+ async read(env) {
465
+ const key = await resolveKey(env)
466
+ if (!key) return { status: 'needs-credential', credential: { refs: env.refs, configured: false } }
467
+ const cn = /cn$/.test(env.route || '')
468
+ const host = cn ? 'https://api.moonshot.cn' : 'https://api.moonshot.ai'
469
+ const res = await fetchT(host + '/v1/users/me/balance', { headers: { authorization: 'Bearer ' + key, accept: 'application/json' } }, 10000)
470
+ if (res.status === 401 || res.status === 403) throw err('model-usage/unauthorized', 'HTTP ' + res.status + '(Moonshot Key 无效)')
471
+ if (!res.ok) throw err('model-usage/http', 'HTTP ' + res.status)
472
+ const body = await res.json()
473
+ const d = (body && body.data) || body || {}
474
+ const pick = (k) => { const v = Number(d[k]); return Number.isFinite(v) ? v : null }
475
+ const available = pick('available_balance')
476
+ if (available == null) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'Moonshot 未返回 available_balance' } }
477
+ const cash = pick('cash_balance')
478
+ const voucher = pick('voucher_balance')
479
+ const cur = cn ? 'CNY' : 'USD'
480
+ return {
481
+ status: 'ok',
482
+ fetchedAt: Date.now(),
483
+ credential: { refs: env.refs, configured: true },
484
+ balance: {
485
+ currency: cur,
486
+ total: available.toFixed(2),
487
+ toppedUp: cash != null ? cash.toFixed(2) : '',
488
+ granted: voucher != null ? voucher.toFixed(2) : '',
489
+ available: available > 0,
490
+ },
491
+ }
492
+ },
493
+ }
494
+
495
+ // MiniMax Token Plan:GET {api.minimax.io|api.minimaxi.com}/v1/token_plan/remains Bearer
496
+ // 响应 model_remains[] / models[]:current_interval_remaining_percent、current_weekly_remaining_percent、
497
+ // remains_time(毫秒)、weekly_remains_time、end_time / weekly_end_time
498
+ const minimaxPlan = {
499
+ id: 'minimax-plan',
500
+ refs: (cfg, route) => [cfg.credentials[route] || (/cn$/.test(route) ? 'MINIMAX_CN_API_KEY' : 'MINIMAX_API_KEY')],
501
+ async read(env) {
502
+ const key = await resolveKey(env)
503
+ if (!key) return { status: 'needs-credential', credential: { refs: env.refs, configured: false } }
504
+ const cn = /cn$/.test(env.route || '')
505
+ const url = cn ? 'https://api.minimaxi.com/v1/token_plan/remains' : 'https://api.minimax.io/v1/token_plan/remains'
506
+ const res = await fetchT(url, { headers: { authorization: 'Bearer ' + key, accept: 'application/json' } }, 10000)
507
+ if (res.status === 401 || res.status === 403) throw err('model-usage/unauthorized', 'HTTP ' + res.status + '(需要 MiniMax 订阅 Key,不是按量付费 Key)')
508
+ if (!res.ok) throw err('model-usage/http', 'HTTP ' + res.status)
509
+ const body = await res.json()
510
+ const base = (body && body.base_resp) || {}
511
+ if (Number(base.status_code) && Number(base.status_code) !== 0) {
512
+ return { status: 'unsupported', error: { code: 'model-usage/biz', message: String(base.status_msg || base.status_code) } }
513
+ }
514
+ const rows = [body.model_remains, body.models, body.data, body.data && body.data.model_remains].find((x) => Array.isArray(x) && x.length)
515
+ if (!rows) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'MiniMax 未返回 model_remains' } }
516
+ const n = (row, keys) => {
517
+ for (const k of keys) { const v = Number(row[k]); if (Number.isFinite(v)) return v }
518
+ return null
519
+ }
520
+ // 取第一条有配额的行(多模型时展示主模型)
521
+ let picked = null
522
+ for (const row of rows) {
523
+ if (!row || typeof row !== 'object') continue
524
+ const intervalPct = n(row, ['current_interval_remaining_percent', 'interval_remaining_percent', 'current_interval_remain_percent'])
525
+ const weeklyPct = n(row, ['current_weekly_remaining_percent', 'weekly_remaining_percent', 'current_weekly_remain_percent'])
526
+ if (intervalPct == null && weeklyPct == null) continue
527
+ picked = { row, intervalPct, weeklyPct }
528
+ break
529
+ }
530
+ if (!picked) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'MiniMax 配额字段缺失' } }
531
+ const now = Date.now()
532
+ const remainsToReset = (row, keys) => {
533
+ const ms = n(row, keys)
534
+ return ms == null ? null : now + ms
535
+ }
536
+ const endAt = (row, keys) => {
537
+ const v = n(row, keys)
538
+ if (v == null) return null
539
+ return v < 1e12 ? v * 1000 : v
540
+ }
541
+ const windows = []
542
+ if (picked.intervalPct != null) {
543
+ windows.push({
544
+ key: 'rolling', label: '5h',
545
+ usedPct: clampPct(100 - picked.intervalPct),
546
+ resetAt: endAt(picked.row, ['end_time', 'current_interval_end_time', 'interval_end_time']) || remainsToReset(picked.row, ['remains_time', 'current_interval_remains_time', 'interval_remains_time']),
547
+ })
548
+ }
549
+ if (picked.weeklyPct != null) {
550
+ windows.push({
551
+ key: 'weekly', label: '周',
552
+ usedPct: clampPct(100 - picked.weeklyPct),
553
+ resetAt: endAt(picked.row, ['weekly_end_time', 'current_weekly_end_time']) || remainsToReset(picked.row, ['weekly_remains_time', 'current_weekly_remains_time']),
554
+ })
555
+ }
556
+ const plan = (typeof (picked.row.subscribe_title || picked.row.plan_name || body.plan_name) === 'string') ? String(picked.row.subscribe_title || picked.row.plan_name || body.plan_name) : ''
557
+ return { status: 'ok', fetchedAt: Date.now(), credential: { refs: env.refs, configured: true }, plan, windows }
558
+ },
559
+ }
560
+
561
+ // Xiaomi MiMo 余额:GET https://platform.xiaomimimo.com/api/v1/balance Bearer
562
+ const xiaomiBalance = {
563
+ id: 'xiaomi-balance',
564
+ refs: (cfg, route) => [cfg.credentials[route] || 'XIAOMI_API_KEY'],
565
+ async read(env) {
566
+ const key = await resolveKey(env)
567
+ if (!key) return { status: 'needs-credential', credential: { refs: env.refs, configured: false } }
568
+ const res = await fetchT('https://platform.xiaomimimo.com/api/v1/balance', { headers: { authorization: 'Bearer ' + key, accept: 'application/json' } }, 10000)
569
+ if (res.status === 401 || res.status === 403) throw err('model-usage/unauthorized', 'HTTP ' + res.status + '(Xiaomi Key 无效)')
570
+ if (!res.ok) throw err('model-usage/http', 'HTTP ' + res.status)
571
+ const body = await res.json()
572
+ const d = (body && (body.data || body.result)) || body || {}
573
+ const val = [d.balance, d.remaining, d.available_balance, d.total_balance].map(Number).find((v) => Number.isFinite(v))
574
+ if (val == null) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'Xiaomi 未返回余额字段' } }
575
+ return {
576
+ status: 'ok',
577
+ fetchedAt: Date.now(),
578
+ credential: { refs: env.refs, configured: true },
579
+ balance: { currency: 'CNY', total: val.toFixed(2), toppedUp: '', granted: '', available: val > 0 },
580
+ }
581
+ },
582
+ }
583
+
584
+ // GitHub Copilot:GET https://api.github.com/copilot_internal/user Bearer(GitHub token)
585
+ // 响应 quota_snapshots: { premium_interactions|chat|completions: { entitlement, remaining, percent_remaining } }
586
+ const githubCopilot = {
587
+ id: 'github-copilot',
588
+ refs: (cfg, route) => [cfg.credentials[route] || 'GITHUB_TOKEN', 'GH_TOKEN', 'GITHUB_COPILOT_TOKEN'],
589
+ async read(env) {
590
+ const key = await resolveKey(env)
591
+ if (!key) return { status: 'needs-credential', credential: { refs: env.refs, configured: false } }
592
+ const res = await fetchT('https://api.github.com/copilot_internal/user', {
593
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json', 'user-agent': 'dsh-model-usage' },
594
+ }, 10000)
595
+ if (res.status === 401 || res.status === 403) throw err('model-usage/unauthorized', 'HTTP ' + res.status + '(需要 GitHub Copilot token)')
596
+ if (!res.ok) throw err('model-usage/http', 'HTTP ' + res.status)
597
+ const body = await res.json()
598
+ const snap = body && body.quota_snapshots
599
+ if (!snap || typeof snap !== 'object') return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'Copilot 未返回 quota_snapshots(非 Copilot 账号或接口改版)' } }
600
+ const windows = []
601
+ let resetAt = null
602
+ const qr = Number(body.quota_reset_date)
603
+ if (Number.isFinite(qr)) resetAt = qr < 1e12 ? qr * 1000 : qr
604
+ for (const [key2, label] of [['premium_interactions', 'Premium'], ['chat', 'Chat'], ['completions', '补全']]) {
605
+ const it = snap[key2]
606
+ if (!it || typeof it !== 'object') continue
607
+ const pctRemain = Number(it.percent_remaining)
608
+ const remain = Number(it.remaining)
609
+ const ent = Number(it.entitlement)
610
+ let usedPct = null
611
+ let countBased = false
612
+ let remaining = null
613
+ let limit = null
614
+ if (Number.isFinite(pctRemain)) usedPct = clampPct(100 - pctRemain)
615
+ else if (Number.isFinite(remain) && Number.isFinite(ent) && ent > 0) usedPct = clampPct(((ent - remain) / ent) * 100)
616
+ if (Number.isFinite(remain) && Number.isFinite(ent)) { countBased = true; remaining = remain; limit = ent }
617
+ if (usedPct == null) continue
618
+ windows.push({ key: key2, label, usedPct, resetAt, countBased, remaining, limit })
619
+ }
620
+ if (!windows.length) return { status: 'unsupported', error: { code: 'model-usage/shape', message: 'Copilot 配额为空' } }
621
+ const plan = typeof body.copilot_plan === 'string' ? body.copilot_plan : ''
622
+ return { status: 'ok', fetchedAt: Date.now(), credential: { refs: env.refs, configured: true }, plan, windows }
623
+ },
624
+ }
625
+
422
626
  // 通用自定义额度:任何供应商都能通过 cordis.patch.yml 的 customQuota 配置接入
423
627
  // customQuota: { [route]: { url, auth:'bearer'|'raw'|'x-api-key'|'none', keyRef, headers,
424
628
  // balance:{path,currencyPath,currency}, windows:[{key,label,usedPath,limitPath,resetAtPath,resetAtUnit}] } }
@@ -477,11 +681,17 @@ const customQuota = {
477
681
  }
478
682
 
479
683
  export function adapterFor(route, cfg) {
480
- if (route === 'deepseek-official') return deepseekBalance
684
+ if (!route) return null
685
+ if (route === 'deepseek-official' || route === 'deepseek') return deepseekBalance
481
686
  if (route === 'deepseek-account') return deepseekAccount
482
687
  if (route === 'zai-coding-cn' || route === 'zai') return glmCoding
483
- if (route && /^openrouter/i.test(route)) return openrouter
484
- if (route && /^(kimi|moonshot)/i.test(route)) return kimiCoding
688
+ if (/^openrouter/i.test(route)) return openrouter
689
+ if (/^kimi-coding/i.test(route)) return kimiCoding
690
+ if (/^(moonshot|kimi)/i.test(route)) return moonshotBalance
691
+ if (/^minimax/i.test(route)) return minimaxPlan
692
+ if (/^opencode-go/i.test(route)) return opencodeGo
693
+ if (/^(xiaomi|mimo)/i.test(route)) return xiaomiBalance
694
+ if (/^github-copilot/i.test(route)) return githubCopilot
485
695
  if (cfg && cfg.customQuota && cfg.customQuota[route] && cfg.customQuota[route].url) return customQuota
486
696
  return null
487
697
  }
package/lib/client.js CHANGED
@@ -43,7 +43,7 @@ window.__ModuleLoader__.load({
43
43
  spendTitle: '今日实际消耗', spentWord: '支出', rechargedWord: '充值', windowSpentWord: '额度消耗', thTime: '时间', thInput: '输入', thOutput: '输出', thCache: '缓存读', thDur: '耗时', thTtft: '首字', noLog: '暂无请求记录',
44
44
  shortAccount: '账号', remainCompact: '剩 {n}%', remainPrefix: '剩 ', remainSuffix: '', statsError: '统计加载失败', retry: '重试', emptyData: '暂无数据', emptyUsage: '今天还没有调用记录',
45
45
  kpiRequests: '模型调用', kpiTokens: '消耗 Tokens', kpiCacheHit: '缓存命中率',
46
- limitMcp: '每月 MCP 工具', remainCalls: '剩余 {n}/{m} 次',
46
+ limitMcp: '每月 MCP 工具', limitMonthly: '每月限额', remainCalls: '剩余 {n}/{m} 次',
47
47
  groupCurrent: '当前会话在用', groupSub: '订阅窗口', groupApi: 'API 余额', groupWallet: '账号钱包', groupOther: '其他',
48
48
  trendTitle: '使用趋势',
49
49
  thProvider: '供应商', thModel: '模型', thRequests: '请求数', thTokens: 'Tokens',
@@ -79,7 +79,7 @@ window.__ModuleLoader__.load({
79
79
  spendTitle: 'Today\'s actual usage', spentWord: 'Spent', rechargedWord: 'Recharged', windowSpentWord: 'Quota used', thTime: 'Time', thInput: 'Input', thOutput: 'Output', thCache: 'Cache', thDur: 'Duration', thTtft: 'TTFT', noLog: 'No requests yet',
80
80
  shortAccount: 'Account', remainCompact: '{n}% left', remainPrefix: '', remainSuffix: ' left', statsError: 'Failed to load stats', retry: 'Retry', emptyData: 'No data', emptyUsage: 'No calls today yet',
81
81
  kpiRequests: 'Model calls', kpiTokens: 'Tokens', kpiCacheHit: 'Cache hit rate',
82
- limitMcp: 'MCP tools / month', remainCalls: '{n}/{m} calls left',
82
+ limitMcp: 'MCP tools / month', limitMonthly: 'Monthly', remainCalls: '{n}/{m} calls left',
83
83
  groupCurrent: 'In use this session', groupSub: 'Subscription windows', groupApi: 'API balance', groupWallet: 'Account wallet', groupOther: 'Other',
84
84
  trendTitle: 'Usage trend',
85
85
  thProvider: 'Provider', thModel: 'Model', thRequests: 'Requests', thTokens: 'Tokens',
@@ -502,6 +502,7 @@ window.__ModuleLoader__.load({
502
502
  if (w.key === 'rolling') return tr('limitRolling')
503
503
  if (w.key === 'weekly') return tr('limitWeekly')
504
504
  if (w.key === 'mcp') return tr('limitMcp')
505
+ if (w.key === 'monthly') return tr('limitMonthly')
505
506
  return w.label || ''
506
507
  }
507
508
  function WinRow({ w, tr }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponydev/dsh-model-usage",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "DSH Web 插件:实时显示模型供应商额度与用量(DeepSeek / GLM Coding Plan / OpenRouter / Kimi Coding Plan + 自定义适配器)",
5
5
  "keywords": [
6
6
  "dsh",