dsh-cost-meter 1.6.2 → 1.6.4

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.
@@ -513,9 +513,12 @@ export const VOLCENGINE_SERVICE = 'ark'
513
513
  export const VOLCENGINE_REGION = 'cn-beijing'
514
514
  export const VOLCENGINE_VERSION = '2024-01-01'
515
515
  // 管控面 Action 白名单(按序尝试;需 AK/SK+HMAC 签名,非 Bearer)。
516
- // 实测(issue #60 评论 by @sanqiPanax, v1.5.46):GetAFPUsage 无参即可返回 5h/weekly/monthly+daily 三窗,而 GetUsageDetails 裸调 400(缺 Filter.StartTime),
517
- // GetPersonalPlan 亦需额外参数;为减少一次 400 探测,优先 GetAFPUsage。
518
- export const VOLCENGINE_ACTIONS = ['GetAFPUsage', 'GetUsageDetails', 'GetPersonalPlan']
516
+ // GetCodingPlanUsage 为 CodingPlan 官方用量接口(issue #71 实测 by @suyukun):
517
+ // 无参即可返回 Result.QuotaUsage[] 三窗(session/weekly/monthly,只含 Percent),
518
+ // 置于首位;GetAFPUsage 实为 AgentPlan 接口(issue #60 评论 by @sanqiPanax,
519
+ // v1.5.46),CodingPlan 用户拿到全 0/空,GetUsageDetails 裸调 400(缺
520
+ // Filter.StartTime),GetPersonalPlan 亦需额外参数,保留作兜底变体。
521
+ export const VOLCENGINE_ACTIONS = ['GetCodingPlanUsage', 'GetAFPUsage', 'GetUsageDetails', 'GetPersonalPlan']
519
522
 
520
523
  function hmacSha256(key, data) {
521
524
  return createHmac('sha256', key).update(data, 'utf8').digest()
@@ -588,8 +591,9 @@ function normalizeVolcWindowName(raw) {
588
591
  }
589
592
 
590
593
  /**
591
- * 解析火山方舟用量响应(管控面 GetUsageDetails / GetAFPUsage / GetPersonalPlan 等)。
592
- * 形似 { ResponseMetadata:..., Result: { UsageDetails:[{QuotaType,Total,Used,Remaining,ResetTime}], ... } }
594
+ * 解析火山方舟用量响应(管控面 GetCodingPlanUsage / GetUsageDetails / GetAFPUsage / GetPersonalPlan 等)。
595
+ * CodingPlan 官方形态 { Result: { QuotaUsage:[{Level:'session'|'weekly'|'monthly', Percent, ResetTimestamp, Cap}], ... } }(issue #71,
596
+ * 只返 Percent,无 used/total);或 { ResponseMetadata:..., Result: { UsageDetails:[{QuotaType,Total,Used,Remaining,ResetTime}], ... } }
593
597
  * 或 arkcli usage plan 的 { items:[{product:"coding-plan",periods:[{label:"session"/"weekly"/"monthly",percent,reset_at}]}] }
594
598
  * 或扁平窗口对象 { fiveHour:{used,limit,reset}, weekly:{...}, monthly:{...} }。
595
599
  * 容忍多种字段命名与大小写,百分比由 used/total 推导(或直接取 percent),非法窗口忽略。
@@ -640,7 +644,7 @@ export function parseVolcengineUsage(data) {
640
644
  // 尝试定位配额数组字段
641
645
  let quotaList = null
642
646
  if (root !== null && typeof root === 'object') {
643
- quotaList = root.UsageDetails ?? root.usageDetails ?? root.usages ?? root.limits ?? root.quotas ?? root.windows ?? root.periods ?? root.details ?? root.items ?? null
647
+ quotaList = root.QuotaUsage ?? root.quotaUsage ?? root.UsageDetails ?? root.usageDetails ?? root.usages ?? root.limits ?? root.quotas ?? root.windows ?? root.periods ?? root.details ?? root.items ?? null
644
648
  if (!Array.isArray(quotaList) && Array.isArray(root)) quotaList = root
645
649
  // 单个Result包装对象里可能直接含窗口字段
646
650
  if (quotaList === null && typeof root === 'object' && !Array.isArray(root)) {
@@ -660,7 +664,9 @@ export function parseVolcengineUsage(data) {
660
664
  const windows = {}
661
665
  for (const entry of quotaList) {
662
666
  if (entry === null || typeof entry !== 'object') continue
663
- const nameRaw = entry.QuotaType ?? entry.quotaType ?? entry.Type ?? entry.type ?? entry.Label ?? entry.label ?? entry.Period ?? entry.period ?? entry.Name ?? entry.name ?? entry.Window ?? entry.window ?? entry.QuotaName ?? entry.quotaName
667
+ // GetCodingPlanUsage 的窗口名字段为 Level(session/weekly/monthly,issue #71),
668
+ // 置于候选最前;其余 Action 沿用 QuotaType 等既有字段。
669
+ const nameRaw = entry.Level ?? entry.level ?? entry.QuotaType ?? entry.quotaType ?? entry.Type ?? entry.type ?? entry.Label ?? entry.label ?? entry.Period ?? entry.period ?? entry.Name ?? entry.name ?? entry.Window ?? entry.window ?? entry.QuotaName ?? entry.quotaName
664
670
  let name = normalizeVolcWindowName(nameRaw)
665
671
  if (name === null) continue
666
672
  let pct = null
package/lib/index.js CHANGED
@@ -18,10 +18,10 @@ import fs from 'node:fs'
18
18
  import { join } from 'node:path'
19
19
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
20
20
  import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
21
- import { Ledger, applyConfigPatch, localDayKey, pickBalanceInfo, reconcileBalanceDelta, zeroDay, splitLedgerApiCost, repairLedgerPricing } from './store.js'
21
+ import { Ledger, applyConfigPatch, localDayKey, pickBalanceInfo, reconcileBalanceDelta, zeroDay, splitLedgerApiCost, repairLedgerPricing, dedupeWrapperProviderDays } from './store.js'
22
22
  import { backfillLegacyLedger, importLegacyHistory, repairForkSeed, repairProviderDupes } from './backfill.js'
23
23
  import { createLlmStreamBilling } from './billing-stream.js'
24
- import { OFFICIAL_PRICING_URL, OFFICIAL_PRICING_URL_ZH, normalizePrice, parsePricingHtml, costOf, providerPriceEntryFor, buildPriceCatalog, usdFromCost } from './pricing.js'
24
+ import { OFFICIAL_PRICING_URL, OFFICIAL_PRICING_URL_ZH, normalizePrice, parsePricingHtml, costOf, providerPriceEntryFor, buildPriceCatalog, usdFromCost, isWrapperProviderId } from './pricing.js'
25
25
  import { CODING_PLAN_PROVIDERS, CODING_PLAN_PROVIDER_IDS, queryCodingPlan, scnetTokenPlanWindows, emptyCustomBalance, queryCustomBalance, normalizeVolcengineKey } from './coding-plans.js'
26
26
  import { recordSamples, buildPlanStats, canonicalWindowKey, periodStartOf, aggregateUsageSince, enabledPlanSetOf, pruneHourBuckets, suggestPlanAutoClasses } from './plan-billing.js'
27
27
  import { fetchWithRetry } from './net.js'
@@ -414,6 +414,11 @@ function makeCostUsageProjection(ledger) {
414
414
  } else {
415
415
  return state
416
416
  }
417
+ // modlens 视觉包装层(modlens-<upstream> / deepseek-modlens)把同一份 usage
418
+ // 经上游 llm.stream 再转发一次:日志里同一调用出现两套 header+usage 事件,
419
+ // 且因 provider 不同绕过 (turn, step) 键去重,投影会翻倍(issue #70)。与账本
420
+ // 钩子一致,包装层 provider 状态下的 usage 事件跳过,只统计上游真实流。
421
+ if (isWrapperProviderId(state.provider)) return state
417
422
  const buckets = {
418
423
  input: usage.inputTokens ?? 0,
419
424
  output: usage.outputTokens ?? 0,
@@ -1487,6 +1492,21 @@ export async function runStartupImports(ledger, sessionsRoot) {
1487
1492
  console.log(`[dsh-cost-meter] 包装路由重复计费清洗完成:合并 ${repaired.groups} 组重复条目,扣除 ${repaired.removedCost.toFixed(4)} USD`)
1488
1493
  }
1489
1494
  }
1495
+ // modlens 视觉包装层镜像一次性清洗(issue #70):现行 modlens 在监听器体内
1496
+ // 急切发起上游调用,逃逸 ALS 嵌套标记,上游与包装层(modlens-<upstream> /
1497
+ // deepseek-modlens)各入账一次,token/费用整体翻倍。实时钩子/投影/回放的
1498
+ // 包装层跳过已堵住新污染,此处修正历史存量:镜像对扣除包装层份,仅包装层
1499
+ // 入账的残留(provider-dedup-v1 合并后只剩字母序靠前的包装层键)改挂上游键。
1500
+ if (!ledger.migrations.includes('modlens-wrapper-dedup-v1')) {
1501
+ const repaired = dedupeWrapperProviderDays(ledger.days ?? {})
1502
+ ledger.migrations.push('modlens-wrapper-dedup-v1')
1503
+ if (repaired.removed + repaired.renamed > 0) {
1504
+ // 改挂上游键的条目 provider 变了,plan/api 分类需按新 provider 重算。
1505
+ splitLedgerApiCost(ledger)
1506
+ ledger.scheduleWrite()
1507
+ console.log(`[dsh-cost-meter] modlens 包装层重复计费清洗完成:扣除 ${repaired.removed} 条镜像,${repaired.renamed} 条改挂上游 provider`)
1508
+ }
1509
+ }
1490
1510
  // Plan/API 双轨拆分一次性迁移(issue #64):历史账本按当前分类回溯重算
1491
1511
  // apiCost(真金白银部分),plan 类条目金额只记等值;幂等,标记防重跑。
1492
1512
  if (!ledger.migrations.includes('plan-billing-split-v1')) {
@@ -1577,8 +1597,13 @@ export function apply(ctx) {
1577
1597
  // 体内再发起 ctx.llm.stream(),旧实现在瀑布每层都记账(同请求 ×2~3);
1578
1598
  // createLlmStreamBilling 用 AsyncLocalStorage 深度标记识别嵌套调用,
1579
1599
  // 只由最外层记一次。历史污染由启动期 provider-dedup-v1 清洗兜底。
1600
+ // 包装层跳过(issue #70):现行 modlens(modlens-<upstream> id 方案)在监听器
1601
+ // 体内(瀑布派发期)急切发起上游调用,逃逸 ALS 深度标记,上游与包装层两份
1602
+ // usage 都到达这里——账本对 provider 为包装层 id 的事件直接跳过,只记上游
1603
+ // 真实流;历史翻倍由启动期 modlens-wrapper-dedup-v1 清洗兜底。
1580
1604
  ctx.on('llm/stream', createLlmStreamBilling({
1581
1605
  account: (usage, model, sessionId, atMs, provider) => {
1606
+ if (isWrapperProviderId(provider)) return
1582
1607
  ledger.account({
1583
1608
  input: usage.inputTokens ?? 0,
1584
1609
  output: usage.outputTokens ?? 0,
package/lib/pricing.js CHANGED
@@ -720,6 +720,37 @@ function deepseekExplicitEntry(modelName, prices, mode) {
720
720
  return { entry, billingMode: 'deepseek-peak', priced: true }
721
721
  }
722
722
 
723
+ /**
724
+ * 是否 modlens 视觉包装层 provider id(`modlens-<upstream>` / `deepseek-modlens`)。
725
+ *
726
+ * modlens 插件(@liustack/modlens)为每条承载纯文本 DeepSeek/GLM 模型的 provider
727
+ * 路由自动注册「(modlens vision)」包装模型,provider id 形如 `modlens-opencode-go`
728
+ * (deepseek-official 路由固定为 `deepseek-modlens`)。包装层会在自身监听器体内
729
+ * 再发起一次上游 llm.stream 转发同一份 usage——该内层分发发生在瀑布派发期,
730
+ * 逃逸 billing-stream 的 ALS 深度标记,实时钩子与历史回放照单全收会把每次调用
731
+ * 记两遍(上游 + 包装层,issue #70 实测账本 token/费用整体翻倍)。统一判定函数
732
+ * 供各计费入口(实时钩子 / 会话投影 / 历史回放 / 账本清洗)跳过包装层、只记上游真实流。
733
+ */
734
+ export function isWrapperProviderId(provider) {
735
+ if (typeof provider !== 'string' || provider.length === 0) return false
736
+ return provider.startsWith('modlens-') || provider === 'deepseek-modlens'
737
+ }
738
+
739
+ /**
740
+ * 包装层 provider 对应的上游 provider id:`modlens-opencode-go` → `opencode-go`,
741
+ * `deepseek-modlens` → `deepseek-official`(modlens 对官方路由的固定包装名);
742
+ * 非包装层返回 null。供账本清洗把「仅包装层入账」的存量条目改挂回上游键。
743
+ */
744
+ export function wrapperUpstreamProvider(provider) {
745
+ if (typeof provider !== 'string' || provider.length === 0) return null
746
+ if (provider === 'deepseek-modlens') return 'deepseek-official'
747
+ if (provider.startsWith('modlens-')) {
748
+ const upstream = provider.slice('modlens-'.length)
749
+ return upstream.length > 0 ? upstream : null
750
+ }
751
+ return null
752
+ }
753
+
723
754
  /**
724
755
  * provider-aware 价格查找。provider 缺失时保持旧版 DeepSeek 行为;
725
756
  * 已知非 DeepSeek provider 未配置模型时返回 null,避免误套 DeepSeek default。
package/lib/store.js CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  priceEntryFor,
19
19
  providerPriceEntryFor,
20
20
  usdFromCost,
21
+ isWrapperProviderId,
22
+ wrapperUpstreamProvider,
21
23
  } from './pricing.js'
22
24
  import { CODING_PLAN_PROVIDER_IDS } from './coding-plans.js'
23
25
  import { DEFAULT_PLAN_PROVIDER_CLASS, PLAN_PROVIDER_IDS, billingClassOf, enabledPlanSetOf, planProviderIdOf, appendHourBucket, pruneHourBuckets, convertRecentCallsToBuckets, isRoutedThirdPartyCall } from './plan-billing.js'
@@ -140,6 +142,16 @@ export function defaultConfig() {
140
142
  plans: true, // 横条含已启用的 Coding Plan 窗口
141
143
  promptSeen: false, // 首次更新后的功能引导是否已处理(用户自主决定开关)
142
144
  },
145
+ // 进度条方向(issue #67):各条独立选择填充语义。
146
+ // balance = 余额条(官方/自定义):默认 remaining(满条起步随消耗递减);
147
+ // budget/go/plan = 预算与额度条:默认 used(空条起步随消耗填满,#57 统一口径)。
148
+ // 值只能是 'remaining' | 'used',语义为「填充代表什么」。
149
+ barDirections: {
150
+ balance: 'remaining',
151
+ budget: 'used',
152
+ go: 'used',
153
+ plan: 'used',
154
+ },
143
155
  usage: {
144
156
  position: 'cost', // Token 用量统计显示位置:cost(费用设置) | general(通用设置) | section(独立分节)
145
157
  },
@@ -361,6 +373,92 @@ export function repairLedgerPricing(ledger) {
361
373
  return { touchedDays, touchedSessions, recostedBuckets }
362
374
  }
363
375
 
376
+ /**
377
+ * modlens 视觉包装层镜像一次性清洗(issue #70)。
378
+ *
379
+ * 现行 modlens(modlens-<upstream> / deepseek-modlens)在监听器体内急切发起
380
+ * 上游 llm.stream,逃逸 billing-stream 的 ALS 嵌套标记,同一次调用在上游与
381
+ * 包装层两个 provider 键下各入账一次(六值桶逐位相同),token/费用整体翻倍。
382
+ *
383
+ * 与 provider-dedup-v1(issue #48 指纹合并,保留字母序第一个键)的遗留交互
384
+ * 需要区分三种形态,逐容器(day 与其下每个 session)处理:
385
+ * 1. 镜像对:上游键与包装层键六值桶全等 → 从容器合计扣除包装层份并删除
386
+ * (真实上游键本就存在,扣减即恢复真实值);
387
+ * 2. 包装层 ⊃ 上游(#48 合并过旧镜像、之后又积累新镜像的混存形态:
388
+ * 包装层键 = 合并残留 + 新镜像,上游键 = 新镜像):上游键是包装层键的
389
+ * 子集 → 扣除并删除上游键,包装层键改挂上游 provider 名;
390
+ * 3. 无上游键(仅有包装层入账,如 #48 合并后只剩字母序靠前的包装层键):
391
+ * 包装层键改挂上游 provider 名,合计不动;
392
+ * 4. 包装层 ⊂ 上游(同日直连 + 包装调用混存:上游键 = 直连量 + 全部包装
393
+ * 调用量,包装层键是纯镜像子集)→ 从容器合计扣除包装层份并删除。
394
+ * 六值桶互不为子集(无法安全判定)的条目不动(保守);幂等,二次运行无
395
+ * 包装层键时返回 0。调用方以账本 migrations 标记保证只跑一次。
396
+ * @param days - 账本每日记录对象(原地修改)。
397
+ * @returns { removed, renamed } 扣除删除的镜像条目数 / 改挂上游键的条目数。
398
+ */
399
+ export function dedupeWrapperProviderDays(days) {
400
+ const result = { removed: 0, renamed: 0 }
401
+ const FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'reasoning', 'calls']
402
+ const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0)
403
+ const fingerprint = (b) => FIELDS.map(f => num(b?.[f])).join('|')
404
+ const isSubset = (small, big) => FIELDS.every(f => num(small?.[f]) <= num(big?.[f]))
405
+ const subtractInto = (container, bucket) => {
406
+ for (const f of [...FIELDS, 'cost', 'apiCost']) {
407
+ container[f] = Math.max(0, num(container[f]) - num(bucket?.[f]))
408
+ }
409
+ }
410
+ const repairContainer = (container) => {
411
+ const pm = container?.byProviderModel
412
+ if (pm === null || typeof pm !== 'object' || Array.isArray(pm)) return
413
+ for (const key of Object.keys(pm)) {
414
+ const sep = key.indexOf(':')
415
+ if (sep <= 0) continue
416
+ const provider = key.slice(0, sep)
417
+ if (!isWrapperProviderId(provider)) continue
418
+ const model = key.slice(sep + 1)
419
+ const upstream = wrapperUpstreamProvider(provider)
420
+ if (upstream === null) continue
421
+ const bucket = pm[key]
422
+ const twinKey = `${upstream}:${model}`
423
+ const twin = pm[twinKey]
424
+ if (twin === undefined) {
425
+ // 形态 3:仅包装层入账——改挂上游键,容器合计不动。
426
+ delete pm[key]
427
+ pm[twinKey] = bucket
428
+ result.renamed += 1
429
+ } else if (fingerprint(twin) === fingerprint(bucket)) {
430
+ // 形态 1:经典镜像对——扣除包装层份并删除,上游键即真实值。
431
+ subtractInto(container, bucket)
432
+ delete pm[key]
433
+ result.removed += 1
434
+ } else if (isSubset(twin, bucket)) {
435
+ // 形态 2:上游键 ⊆ 包装层键(混存残留)——扣除并删除上游键,包装层改挂上游。
436
+ subtractInto(container, twin)
437
+ delete pm[key]
438
+ pm[twinKey] = bucket
439
+ result.renamed += 1
440
+ } else if (isSubset(bucket, twin)) {
441
+ // 形态 4:包装层键 ⊆ 上游键(同日直连 + 包装调用混存:上游键含直连量 +
442
+ // 全部包装调用量,包装层键是纯镜像子集)——扣除包装层份并删除。
443
+ subtractInto(container, bucket)
444
+ delete pm[key]
445
+ result.removed += 1
446
+ }
447
+ // 其余:互不为子集,无法安全判定,保守不动。
448
+ }
449
+ }
450
+ for (const day of Object.values(days ?? {})) {
451
+ if (day === null || typeof day !== 'object') continue
452
+ repairContainer(day)
453
+ if (Array.isArray(day.sessions)) {
454
+ for (const session of day.sessions) {
455
+ if (session !== null && typeof session === 'object') repairContainer(session)
456
+ }
457
+ }
458
+ }
459
+ return result
460
+ }
461
+
364
462
  function zeroSession(id) {
365
463
  return { id, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0, apiCost: 0, byProviderModel: {} }
366
464
  }
@@ -506,6 +604,8 @@ const VALIDATION_MESSAGES = {
506
604
  cornerFlag: 'corner.{field} 必须是布尔值',
507
605
  quotaStrip: 'quotaStrip 非法',
508
606
  quotaStripFlag: 'quotaStrip.{field} 必须是布尔值',
607
+ barDirections: 'barDirections 非法',
608
+ barDirectionValue: 'barDirections.{field} 必须是 remaining / used',
509
609
  usage: 'usage 非法',
510
610
  usagePosition: 'usage.position 必须是 cost / general / section',
511
611
  planBilling: 'planBilling 非法',
@@ -583,6 +683,8 @@ const VALIDATION_MESSAGES = {
583
683
  cornerFlag: 'corner.{field} must be a boolean',
584
684
  quotaStrip: 'Invalid quotaStrip',
585
685
  quotaStripFlag: 'quotaStrip.{field} must be a boolean',
686
+ barDirections: 'Invalid barDirections',
687
+ barDirectionValue: 'barDirections.{field} must be remaining / used',
586
688
  usage: 'Invalid usage',
587
689
  usagePosition: 'usage.position must be cost / general / section',
588
690
  planBilling: 'Invalid planBilling',
@@ -831,6 +933,18 @@ export function applyConfigPatch(current, patch) {
831
933
  if (typeof quotaStrip[field] !== 'boolean') errors.push(vmsg(locale, 'quotaStripFlag', { field }))
832
934
  }
833
935
  }
936
+ // 进度条方向(issue #67):四组条各自 remaining / used,非法值整体报错
937
+ // (客户端提交完整对象,与 corner/quotaStrip 同策略)。
938
+ const barDirections = candidate.barDirections
939
+ if (barDirections === null || typeof barDirections !== 'object' || Array.isArray(barDirections)) {
940
+ errors.push(vmsg(locale, 'barDirections'))
941
+ } else {
942
+ for (const field of ['balance', 'budget', 'go', 'plan']) {
943
+ if (barDirections[field] !== 'remaining' && barDirections[field] !== 'used') {
944
+ errors.push(vmsg(locale, 'barDirectionValue', { field }))
945
+ }
946
+ }
947
+ }
834
948
  // Token 用量统计显示位置校验。
835
949
  const usage = candidate.usage
836
950
  if (usage === null || typeof usage !== 'object' || Array.isArray(usage)) {
@@ -1036,6 +1150,15 @@ export function sanitizeConfig(raw) {
1036
1150
  strip.enabled = strip.enabled === true
1037
1151
  strip.promptSeen = strip.promptSeen === true
1038
1152
  for (const key of ['budget', 'go', 'plans']) strip[key] = strip[key] !== false
1153
+ // 进度条方向(issue #67):非法值逐键回落默认(balance=remaining,其余=used)。
1154
+ const dirs = out.barDirections !== null && typeof out.barDirections === 'object' && !Array.isArray(out.barDirections)
1155
+ ? out.barDirections : {}
1156
+ out.barDirections = {
1157
+ balance: dirs.balance === 'used' ? 'used' : 'remaining',
1158
+ budget: dirs.budget === 'remaining' ? 'remaining' : 'used',
1159
+ go: dirs.go === 'remaining' ? 'remaining' : 'used',
1160
+ plan: dirs.plan === 'remaining' ? 'remaining' : 'used',
1161
+ }
1039
1162
  // codingPlans:逐家收敛(非法条目整家回落默认;只保留已知提供商)。
1040
1163
  const plans = {}
1041
1164
  if (out.codingPlans !== null && typeof out.codingPlans === 'object') {
@@ -199,6 +199,13 @@ const configSchema = z.object({
199
199
  plans: z.boolean(),
200
200
  promptSeen: z.boolean(),
201
201
  }),
202
+ // 进度条方向(issue #67):balance/budget/go/plan 各自 remaining | used。
203
+ barDirections: z.object({
204
+ balance: z.enum(['remaining', 'used']),
205
+ budget: z.enum(['remaining', 'used']),
206
+ go: z.enum(['remaining', 'used']),
207
+ plan: z.enum(['remaining', 'used']),
208
+ }).optional(),
202
209
  // Token 用量统计显示位置(当前固定 cost;键保留供读侧白名单)。
203
210
  usage: z.object({ position: z.enum(['cost', 'general', 'section']) }).optional(),
204
211
  // Plan/API 双轨计费分类(issue #64):providers 值 auto/plan/api;models 为 provider:model 显式覆盖。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cost-meter",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(9 家,含 SCNet Token Plan 本地 Credits 计量与火山方舟 Volcano Ark Coding Plan AK/SK 签名查询)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 9 vendors (incl. local credits metering for the SCNet Token Plan and Volcano Ark Coding Plan via AK/SK signature), plan/API dual-track billing with per-1% & full-window Token Plan usage estimates, custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -61,9 +61,9 @@
61
61
  "docs/provider-pricing.json"
62
62
  ],
63
63
  "dependencies": {
64
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
65
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
66
- "zod": "^4.4.3"
64
+ "@deepseek-ai/dsh-credentials": "0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-home-paths": "0.1.0-rc.6",
66
+ "zod": "4.4.3"
67
67
  },
68
68
  "repository": {
69
69
  "type": "git",
@@ -86,5 +86,11 @@
86
86
  "en",
87
87
  "language"
88
88
  ],
89
- "license": "MIT"
89
+ "license": "MIT",
90
+ "scripts": {
91
+ "build": "node scripts/build.mjs"
92
+ },
93
+ "devDependencies": {
94
+ "esbuild": "^0.28.1"
95
+ }
90
96
  }