@kenz1117/dsh-ui-usage-billing 1.0.28 → 1.0.29

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.
@@ -10,6 +10,7 @@
10
10
  * zero while their tokens still count. Pure functions only: the persistence
11
11
  * handle is injected, so the fold is unit-testable without a host.
12
12
  */
13
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
13
14
  import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
14
15
  import type { TokenUsage } from '@deepseek-ai/dsh-llm';
15
16
  import { MODEL_KEY_ALIASES, resolveCatalogKey } from './client/pricing.ts';
@@ -17,9 +18,9 @@ export { MODEL_KEY_ALIASES, resolveCatalogKey };
17
18
  /**
18
19
  * 走订阅套餐(coding / token plan / opencode 订阅)的 provider id:这些通道的
19
20
  * 调用按套餐计费,不再按 token 计费,因此即使模型 id 与计费表撞名也一律豁免。
20
- * 与 pi-ai 内置提供方对齐(含各地区变体:qwen/xiaomi 的 token-plan、opencode 与
21
- * opencode-go、zai-coding-cn);部署可在 plugin config 的 `subscriptionProviders`
22
- * 中覆盖。
21
+ * 此列表保留为显式配置的参考基线;聚合层缺省改用与订阅卡一致的
22
+ * `isSubscriptionProviderId` 判定(覆盖本列表与全部 `*-token-plan` / `*-coding` 变体),
23
+ * 部署仍可在 plugin config 的 `subscriptionProviders` 中显式覆盖。
23
24
  */
24
25
  export declare const DEFAULT_SUBSCRIPTION_PROVIDERS: readonly string[];
25
26
  /**
@@ -28,6 +29,23 @@ export declare const DEFAULT_SUBSCRIPTION_PROVIDERS: readonly string[];
28
29
  * 调用与费用分桶展示;部署可由配置覆盖(见 {@link AggregateOptions})。
29
30
  */
30
31
  export declare function isOfficialProvider(provider: string): boolean;
32
+ /** DeepSeek 官方直连端点的归一化 origin(`siteOriginOf` 口径):有 baseURL 的路由只有它算官方。 */
33
+ export declare const OFFICIAL_DEEPSEEK_ORIGIN = "https://api.deepseek.com";
34
+ /**
35
+ * 官方渠道判定(通道优先):显式 `officialProviderIds` 配置最优先;否则看站点归组——
36
+ * 有 baseURL 的路由只有 origin 为 DeepSeek 官方域才算官方(修复:名为 `deepseek-*`
37
+ * 的中转/网关路由曾被按 id 前缀误判为官方,腾讯网关的 DeepSeek 全被计成官方渠道);
38
+ * 直连路由(配置在册、无 baseURL)退回按 provider id 前缀判定;**不在当前配置里的
39
+ * 未知路由一律不算官方**(无法核实通道,不装确定——历史路由用 `routeAliases` 归位)。
40
+ */
41
+ export declare function officialChannelOf(provider: string, ref: SiteRef, officialProviderIds?: ReadonlySet<string>): boolean;
42
+ /**
43
+ * 订阅制 provider 匹配器:显式列表(配置覆盖)或判定函数(默认
44
+ * {@link isSubscriptionProviderId},与订阅卡识别同一正则口径,修复两者不一致)。
45
+ */
46
+ export type SubscriptionMatcher = ReadonlySet<string> | ((provider: string) => boolean);
47
+ /** 一个 provider 是否走订阅套餐计费(豁免按 token 计价)。 */
48
+ export declare function isSubscriptionCall(subscription: SubscriptionMatcher, provider: string): boolean;
31
49
  /** 一个 provider 路由的站点视图(来自 llm-pi-ai providers 的 baseURL)。 */
32
50
  export interface ProviderRouteView {
33
51
  /** 该路由配置的端点地址;无值 = 直连厂商(非中转站)。 */
@@ -58,10 +76,16 @@ export declare function siteRefOf(provider: string, routes: Readonly<Record<stri
58
76
  export declare function siteBucketKey(ref: SiteRef): string;
59
77
  /** Aggregation tuning options. */
60
78
  export interface AggregateOptions {
61
- /** 订阅制 provider id 列表;默认 {@link DEFAULT_SUBSCRIPTION_PROVIDERS}。 */
79
+ /** 订阅制 provider id 列表;缺省按订阅卡同款 id 判定(`isSubscriptionProviderId`)。 */
62
80
  subscriptionProviders?: readonly string[];
63
- /** 官方渠道 provider id 列表;默认按 {@link isOfficialProvider} 判定(DeepSeek 官方直连)。 */
81
+ /** 官方渠道 provider id 列表;缺省按 {@link officialChannelOf} 判定(通道 origin 优先)。 */
64
82
  officialProviderIds?: readonly string[];
83
+ /**
84
+ * 历史路由别名(旧路由名 → 当前路由名):改过名 / 删除过的 provider 路由,
85
+ * 其历史用量原会落「未知路由」桶;配置别名后按目标路由归组(站点、订阅、
86
+ * 官方判定都用别名后的路由名)。
87
+ */
88
+ routeAliases?: Readonly<Record<string, string>>;
65
89
  /** 每会话折叠缓存的上限(默认 {@link DEFAULT_MAX_CACHE_SESSIONS});
66
90
  * 超限时按最近使用先后淘汰最久未用的会话,防长期运行内存膨胀。 */
67
91
  maxCacheSessions?: number;
@@ -155,11 +179,15 @@ export declare const UNKNOWN_WORKSPACE_NAME = "\u2014";
155
179
  export declare function workspaceNameOf(cwd: string | undefined): string;
156
180
  /**
157
181
  * The persistence surface the aggregate reads: enough of
158
- * `SessionPersistence` to list sessions and read each log once; `locate`
159
- * is optional — backends exposing it give the incremental cache a cheap
160
- * invalidation stamp (artifact mtime + size), others always re-fold.
182
+ * `SessionPersistence` to list sessions and read each log once. Two
183
+ * optional invalidation-stamp sources, checked in order:
184
+ * `stampOf` (host 0.1.3+: the persistence revision token, exposed by the
185
+ * host-shape adapter) then `locate` (host 0.1.2: artifact mtime + size).
186
+ * Neither present means every round re-folds.
161
187
  */
162
- export type UsagePersistence = Pick<SessionPersistence, 'list' | 'readFrom'> & Partial<Pick<SessionPersistence, 'locate'>>;
188
+ export type UsagePersistence = Pick<SessionPersistence, 'list' | 'readFrom'> & Partial<Pick<SessionPersistence, 'locate'>> & Partial<{
189
+ stampOf(id: SessionId): Promise<string | null>;
190
+ }>;
163
191
  /** The usage-stats document served to the billing dashboard. */
164
192
  export interface UsageStatsDocument {
165
193
  version: number;
@@ -429,7 +457,7 @@ export interface UsageLedgerDocument {
429
457
  * 会话费用只剩最近一段,issue #29)。
430
458
  * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
431
459
  */
432
- export declare const FOLD_VERSION = 7;
460
+ export declare const FOLD_VERSION = 8;
433
461
  /**
434
462
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
435
463
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -470,9 +498,9 @@ export declare function messageTextLength(message: unknown): number;
470
498
  * 兜底到最近一次 request/header 的状态。同时提取最新会话标题、最后活跃时间,
471
499
  * 并按轮次折叠每轮费用明细(turn/start → turn/end;调用按 (turn) 归组)。
472
500
  * @param events - the session's persisted events in log order.
473
- * @param subscriptionProviders - provider ids billed through subscription plans.
501
+ * @param subscriptionProviders - 订阅套餐匹配器(显式集合或判定函数)。
474
502
  * @param officialProviderIds - provider ids treated as the official DeepSeek channel
475
- * (default: any `deepseek`-prefixed id). Others count as third-party.
503
+ * (default: channel-origin based via {@link officialChannelOf}). Others count as third-party.
476
504
  * @param routes - 当前 provider 路由视图(中转站归组)。
477
505
  * @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
478
506
  * @param seedLength - 持久化的 fork 血缘边界(存储元数据 `inheritedEventCount`,缺省 0):
@@ -486,7 +514,7 @@ export declare function foldSession(events: readonly {
486
514
  time: number;
487
515
  data: unknown;
488
516
  seq?: number;
489
- }[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>, searchCallEstimateCny?: number, seedLength?: number): SessionFold;
517
+ }[], subscriptionProviders: SubscriptionMatcher, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>, searchCallEstimateCny?: number, seedLength?: number, routeAliases?: Readonly<Record<string, string>>): SessionFold;
490
518
  /**
491
519
  * 增量聚合器:按会话缓存折叠结果,用日志文件的 mtime+size 作失效键——
492
520
  * 日志没动的会话直接复用,只有写过的会话重新折叠;整份文档另有短 TTL
@@ -33,26 +33,6 @@ export declare function pickTokenDanceBalanceCny(data: unknown): number | undefi
33
33
  export declare function queryBalances(ctx: Context, providers: Readonly<Record<string, {
34
34
  apiKeyEnv?: string;
35
35
  }>>): Promise<readonly ProviderBalance[]>;
36
- /**
37
- * 构造云 API 3.0 TC3-HMAC-SHA256 签名(官方签名方法 v3)。导出供测试:纯函数,
38
- * 输入确定则签名确定。Action 不参与签名——它走 `X-TC-Action` 请求头。
39
- * @param secretId - 云 API SecretId。
40
- * @param secretKey - 云 API SecretKey。
41
- * @param payload - 已序列化的请求体(含 Action/Version/Region 公共参数)。
42
- * @param timestamp - 签名时间戳(秒)。
43
- * @returns Authorization 头的值。
44
- */
45
- export declare function tc3Authorization(secretId: string, secretKey: string, payload: string, timestamp: number): string;
46
- /**
47
- * 在套餐余量对象里防御性提取「剩余额度」:官方 SubPackageBalance/PackageInfo
48
- * 的字段名未稳定公开(issue #18 调研期),按语义键名扫描——命中 remaining /
49
- * balance / left 语义键直接用;命中 total 与 used 则相减推导。数字一律经
50
- * {@link toNumber} 归一化(上游可能给字符串)。
51
- * 导出供测试:纯函数。
52
- * @param source - 套餐详情里的余量对象(PackageInfo / SubPackageBalance 等)。
53
- * @returns 剩余额度(上游单位,通常为 token 数或元);提取不到返回 undefined。
54
- */
55
- export declare function pickRemainingQuota(source: unknown): number | undefined;
56
36
  /**
57
37
  * 按 extract 规则从响应 JSON 求值。导出供测试:纯函数。
58
38
  * @param rule - 提取规则(const / path / add / subtract / divide)。
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'trendEmpty' | 'budget' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'project' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'triggerMonthTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'workspaces' | 'workspacesHint' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionIncluded' | 'free' | 'official' | 'thirdParty' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapMonth' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginVersion' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenCacheHitRate' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown';
2
+ export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'trendEmpty' | 'budget' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'project' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'triggerMonthTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'workspaces' | 'workspacesHint' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionIncluded' | 'free' | 'official' | 'thirdParty' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapMonth' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginVersion' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenCacheHitRate' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown';
3
3
  export declare const NS = "usageBilling";
4
4
  export declare const zh: Record<UsageBillingKey, string>;
5
5
  export declare const en: Record<UsageBillingKey, string>;
@@ -26,6 +26,12 @@ import type { LivePricing } from '../pricing-shared.ts';
26
26
  * live rate arrives the built-in value stays in force.
27
27
  */
28
28
  export declare const USD_TO_CNY = 6.79;
29
+ /**
30
+ * 注入用户自定义模型别名(node 半区在插件启动时调用一次)。纯内存状态:
31
+ * 聚合折叠与客户端渲染共用同一份(两侧一致性由同一注入点保证)。
32
+ * @param aliases - `model id → 目录键` 映射;undefined/空 = 清除,回退内置表。
33
+ */
34
+ export declare function applyUserModelAliases(aliases: Readonly<Record<string, string>> | undefined): void;
29
35
  /**
30
36
  * 用户自定义单价(设置面板录入,localStorage 持久化):覆盖内置/models.dev/
31
37
  * dsh-spend 的全部价格来源,用于新模型上线目录未跟、或厂商未公布按量价的场景。
@@ -324,14 +330,6 @@ export declare const MODEL_KEY_ALIASES: Readonly<Record<string, string>>;
324
330
  * @returns 归一化键(字母数字小写串)。
325
331
  */
326
332
  export declare function canonModelId(id: string): string;
327
- /**
328
- * 解析真实日志模型 id → 计费目录键。先精确别名映射(既有行为);未命中时做
329
- * 归一化匹配(忽略大小写/分隔符/括号附注),命中内置目录 / 别名目标 / 兜底键 /
330
- * models.dev 补充键即返回其真实键;完全未知时保持原样(回退 other,不计费)。
331
- * 供聚合层折叠与客户端渲染共用,两侧一致。
332
- * @param id - 真实模型 id(日志里出现的形式)。
333
- * @returns 计费目录键。
334
- */
335
333
  export declare function resolveCatalogKey(id: string): string;
336
334
  /** Lookup a model by its stats key; falls back to the generic `other` entry. */
337
335
  export declare function modelOf(key: string): ModelEntry;
@@ -17,5 +17,12 @@ type ProviderLang = 'zh' | 'en';
17
17
  export declare function localizeProviderName(name: string, lang: ProviderLang): string;
18
18
  /** 判断一个厂商显示名是否包含中文(用于测试/守卫:保证映射覆盖完整)。 */
19
19
  export declare function hasCjk(name: string): boolean;
20
+ /**
21
+ * 站点桶 key(`site:<origin>` / `direct:<provider>` / `unknown`)→ 通道显示名。
22
+ * @param siteKey - 聚合文档 bySite 的桶 key。
23
+ * @param lang - 显示语言;zh(缺省)用内置中文名,en 走英文章表。
24
+ * @returns 未知路由桶返回 undefined(渲染层用 locale 文案兜底)。
25
+ */
26
+ export declare function channelDisplayName(siteKey: string, lang?: ProviderLang): string | undefined;
20
27
  export {};
21
28
  //# sourceMappingURL=provider-display.d.ts.map
@@ -13,7 +13,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
13
13
  import type { Context } from '@deepseek-ai/cordis';
14
14
  import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
15
15
  import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
16
- import { type UsageLedgerStore } from './aggregate.ts';
16
+ import { type UsageLedgerStore, type UsagePersistence } from './aggregate.ts';
17
17
  import type { CustomBalanceConfig, DeclaredEndpointConfig, SubscriptionPlanConfig } from './pricing-shared.ts';
18
18
  import { type IdentifiedSubscriptionPlan, type SubscriptionKeys } from './subscriptions.ts';
19
19
  /**
@@ -65,6 +65,18 @@ export interface UsageBillingConfig {
65
65
  * 计费但本地日志无 usage,按次估值计入今日/本月费用;默认 0.02 元/次,设 0 关闭。
66
66
  */
67
67
  searchCallEstimateCny?: number;
68
+ /**
69
+ * 历史路由别名(旧 provider 路由名 → 当前路由名):改过名/删除过的路由,其
70
+ * 历史用量原会落「未知路由」桶且站点/订阅/官方判定全部失效;配置别名后按
71
+ * 目标路由归位。例:`{ "deepseek-official": "tencent", "tencent-cloud": "tencent" }`。
72
+ */
73
+ routeAliases?: Record<string, string>;
74
+ /**
75
+ * 用户自定义模型别名(真实日志 model id → 计费目录键):目录外的新模型无需
76
+ * 等发版,配置一条别名即完成识别与计价(值必须是 MODEL_CATALOG 的既有 key,
77
+ * 如 `{ "hy4-preview": "hunyuan" }`)。聚合折叠与客户端渲染共用同一映射。
78
+ */
79
+ modelKeyAliases?: Record<string, string>;
68
80
  }
69
81
  /**
70
82
  * Create the atomic file-backed durable-ledger store. The previous complete file
@@ -107,6 +119,17 @@ export declare function resolveSubscriptionKeys(settings: SettingsProvider, cred
107
119
  keys: SubscriptionKeys;
108
120
  identified: IdentifiedSubscriptionPlan[];
109
121
  }>;
122
+ /**
123
+ * 宿主 persistence 形状适配。宿主 0.1.3 起 SessionPersistence 改为
124
+ * SessionHandle 模型(open(id,'read') 后经 handle.read(offset) 读,fork 边界
125
+ * 挂在 handle.inheritedEventCount,list 返回 {header, revision} 快照行,
126
+ * 0.1.2 的 readFrom/locate 消失)。这里按结构探测把两种宿主形状都收敛为
127
+ * 聚合层期望的 0.1.2 面貌:0.1.2 直接带 readFrom 的原样直通;0.1.3 的
128
+ * 读取转为 open → handle.read,revision 令牌经 stampOf 暴露给增量缓存。
129
+ * 候选时刻的运行时对象是宿主注入的外部形状,结构断言即 durable 收窄点。
130
+ * 导出供测试:纯函数(不触 ctx)。
131
+ */
132
+ export declare function adaptSessionPersistence(raw: unknown): UsagePersistence;
110
133
  /**
111
134
  * Host plugin body: serve real aggregated usage to the browser dashboard.
112
135
  * @param ctx - host context carrying webServer and sessionPersistence.
@@ -24,6 +24,8 @@ export interface SubscriptionKeys {
24
24
  minmaxApiKey: string;
25
25
  /** OpenRouter API key(credits 已用%)。 */
26
26
  openrouterApiKey: string;
27
+ /** 腾讯云云 API 密钥对(`<SecretId>:<SecretKey>`,管控面用,非 TokenHub 推理 key)。 */
28
+ tencentCloudApi: string;
27
29
  /** Z.ai 区域(global / bigmodel-cn)。 */
28
30
  zaiRegion: 'global' | 'bigmodel-cn';
29
31
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Tencent Cloud TokenHub control-plane access (cloud API 3.0, TC3-HMAC-SHA256).
3
+ *
4
+ * Shared by two surfaces that read the same Token Plan:
5
+ * - `balance.ts` — the balance row (absolute remaining quota);
6
+ * - `subscriptions.ts` — the subscription card (monthly window + plan name).
7
+ *
8
+ * Credentials are a cloud API key pair (`<SecretId>:<SecretKey>`), NOT the
9
+ * TokenHub inference key. Field names on the control plane are not fully
10
+ * stable, so parsing stays defensive (semantic-key scanning).
11
+ */
12
+ /** TokenHub 管控面 API 端点(cloud.tencent.cn/document/api/1823/132270)。 */
13
+ export declare const TOKENHUB_HOST = "tokenhub.tencentcloudapi.com";
14
+ /** 管控面调用超时(毫秒):余额与订阅面板共用同一预算。 */
15
+ export declare const TOKENHUB_TIMEOUT_MS = 8000;
16
+ /** 腾讯云凭据引用值格式:`<SecretId>:<SecretKey>`(分隔符取首个冒号)。 */
17
+ export declare function parseTencentCredential(value: string): {
18
+ secretId: string;
19
+ secretKey: string;
20
+ } | undefined;
21
+ /**
22
+ * 构造云 API 3.0 TC3-HMAC-SHA256 签名(官方签名方法 v3)。导出供测试:纯函数,
23
+ * 输入确定则签名确定。Action 不参与签名——它走 `X-TC-Action` 请求头。
24
+ * @param secretId - 云 API SecretId。
25
+ * @param secretKey - 云 API SecretKey。
26
+ * @param payload - 已序列化的请求体(含 Action/Version/Region 公共参数)。
27
+ * @param timestamp - 签名时间戳(秒)。
28
+ * @returns Authorization 头的值。
29
+ */
30
+ export declare function tc3Authorization(secretId: string, secretKey: string, payload: string, timestamp: number): string;
31
+ /**
32
+ * 调用一次 TokenHub 管控面接口:TC3 签名 + 超时保护,返回响应 JSON 的 `Response`。
33
+ * 业务错误(Response.Error)与 HTTP 层错误都带 `code` 抛出,调用方按
34
+ * unauthorized / unreachable / invalid 归类。
35
+ */
36
+ export declare function callTokenHub(secretId: string, secretKey: string, action: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
37
+ /** 数字归一化:上游可能给字符串数字;非有限数返回 undefined。 */
38
+ export declare function toNumber(value: unknown): number | undefined;
39
+ /**
40
+ * 在套餐余量对象里防御性提取「剩余额度」:官方 SubPackageBalance/PackageInfo
41
+ * 的字段名未稳定公开(issue #18 调研期),按语义键名扫描——命中 remaining /
42
+ * balance / left 语义键直接用;命中 total 与 used 则相减推导。数字一律经
43
+ * {@link toNumber} 归一化(上游可能给字符串)。
44
+ * 导出供测试:纯函数。
45
+ * @param source - 套餐详情里的余量对象(PackageInfo / SubPackageBalance 等)。
46
+ * @returns 剩余额度(上游单位,通常为 token 数或元);提取不到返回 undefined。
47
+ */
48
+ export declare function pickRemainingQuota(source: unknown): number | undefined;
49
+ /**
50
+ * 防御性提取「总额度」:语义键 total / limit / quota。与
51
+ * {@link pickRemainingQuota} 配对使用——两者都能取到时订阅卡才能算出
52
+ * 已用百分比窗口;只有剩余值时窗口保持为空(绝不猜总额度)。
53
+ * 注意排除键名:`RemainingQuota` / `UsedQuota` 这类键同样含 quota 子串,
54
+ * 必须先剔除剩余 / 已用语义,否则会把剩余值误当总额度(有单测守卫)。
55
+ * @param source - 套餐详情里的额度对象(PackageInfo / SubPackageBalance 等)。
56
+ */
57
+ export declare function pickTotalQuota(source: unknown): number | undefined;
58
+ /** 套餐列表里提取第一个启用套餐的 TeamId:集合字段名做候选兼容。 */
59
+ export declare function firstEnabledTeamId(inner: Record<string, unknown>): string | undefined;
60
+ //# sourceMappingURL=tc3.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kenz1117/dsh-ui-usage-billing",
3
3
  "description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
4
- "version": "1.0.28",
4
+ "version": "1.0.29",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",
@@ -59,7 +59,8 @@
59
59
  "0.1.2-alpha.3": "compatible",
60
60
  "0.1.2-alpha.4": "compatible",
61
61
  "0.1.2-alpha.5": "compatible",
62
- "0.1.2-rc.1": "compatible"
62
+ "0.1.2-rc.1": "compatible",
63
+ "0.1.3-alpha.1": "compatible"
63
64
  }
64
65
  }
65
66
  },