@kenz1117/dsh-ui-usage-billing 0.9.16 → 0.9.18
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/README.en.md +4 -0
- package/README.md +4 -0
- package/lib/client.js +841 -618
- package/lib/index.js +553 -26
- package/lib/types/aggregate.d.ts +39 -0
- package/lib/types/client/TrendChart.d.ts +9 -3
- package/lib/types/client/UsageBilling.d.ts +13 -2
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/declarative.d.ts +55 -0
- package/lib/types/index.d.ts +13 -1
- package/lib/types/pricing-shared.d.ts +84 -0
- package/lib/types/reconcile.d.ts +60 -0
- package/lib/types/relay.d.ts +6 -0
- package/package.json +1 -1
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -111,6 +111,15 @@ export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: strin
|
|
|
111
111
|
export declare function dayStamp(time: number): string;
|
|
112
112
|
/** Local-time hour stamp `YYYY-MM-DDTHH` — the performance series bucket key. */
|
|
113
113
|
export declare function hourStamp(time: number): string;
|
|
114
|
+
/**
|
|
115
|
+
* 宿主进程的 IANA 时区名与 UTC 偏移,供面板标注「天按哪个时区切分」。
|
|
116
|
+
* `getTimezoneOffset` 是 UTC 以西的分钟数,符号与日常写法相反,故取反。
|
|
117
|
+
* @returns `{ name, offset }`,如 `{ name: "Asia/Shanghai", offset: "UTC+08:00" }`。
|
|
118
|
+
*/
|
|
119
|
+
export declare function hostTimeZone(now?: Date): {
|
|
120
|
+
name: string;
|
|
121
|
+
offset: string;
|
|
122
|
+
};
|
|
114
123
|
/** cwd 未知时工作区聚合的占位名(UI 显示 em dash,保持语言无关)。 */
|
|
115
124
|
export declare const UNKNOWN_WORKSPACE_NAME = "\u2014";
|
|
116
125
|
/** 工作区名:取 cwd 的末级目录名;无 cwd 时返回 {@link UNKNOWN_WORKSPACE_NAME}。 */
|
|
@@ -127,6 +136,11 @@ export interface UsageStatsDocument {
|
|
|
127
136
|
version: number;
|
|
128
137
|
updatedAt: number;
|
|
129
138
|
source: 'session-logs';
|
|
139
|
+
/** 宿主进程时区(IANA 名 + UTC 偏移):天按此切分,面板据此标注。 */
|
|
140
|
+
timezone?: {
|
|
141
|
+
name: string;
|
|
142
|
+
offset: string;
|
|
143
|
+
};
|
|
130
144
|
total: ModelUsage;
|
|
131
145
|
byModel: Record<string, ModelUsage>;
|
|
132
146
|
byDay: Record<string, ModelUsage>;
|
|
@@ -325,7 +339,31 @@ export interface UsageLedgerDocument {
|
|
|
325
339
|
version: 1;
|
|
326
340
|
updatedAt: number;
|
|
327
341
|
sessions: UsageLedgerSession[];
|
|
342
|
+
/** 已应用的一次性配置迁移 id 列表(随文档落盘;缺省 = 尚未跑过任何迁移)。 */
|
|
343
|
+
appliedMigrations?: string[];
|
|
328
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
347
|
+
* 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
|
|
348
|
+
* bump {@link UsageLedgerDocument.version};引擎保证幂等,重启不会重复执行。
|
|
349
|
+
*/
|
|
350
|
+
export interface LedgerMigration {
|
|
351
|
+
id: string;
|
|
352
|
+
/** 对原始文档执行就地变更;返回是否产生了需要落盘的实际修改。 */
|
|
353
|
+
apply(document: UsageLedgerDocument): boolean;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 账本迁移注册表。当前账本 schema(version 1)尚无字段变更需求,故为空表;
|
|
357
|
+
* 机制已就绪,schema 变更时在此登记幂等迁移,见 {@link LedgerMigration}。
|
|
358
|
+
*/
|
|
359
|
+
export declare const LEDGER_MIGRATIONS: readonly LedgerMigration[];
|
|
360
|
+
/**
|
|
361
|
+
* 在加载边界对账本文档应用未执行的迁移,并记录已应用 id 供写回。
|
|
362
|
+
* @param document - 从持久化读出的原始账本文档。
|
|
363
|
+
* @param migrations - 待执行的迁移注册表;缺省用模块级 {@link LEDGER_MIGRATIONS}。
|
|
364
|
+
* @returns 是否发生了需要重新落盘的修改。
|
|
365
|
+
*/
|
|
366
|
+
export declare function runLedgerMigrations(document: UsageLedgerDocument, migrations?: readonly LedgerMigration[]): boolean;
|
|
329
367
|
/** Storage seam for the durable ledger; the host supplies an atomic file implementation. */
|
|
330
368
|
export interface UsageLedgerStore {
|
|
331
369
|
load(): Promise<unknown | undefined>;
|
|
@@ -350,6 +388,7 @@ export declare function foldSession(events: readonly {
|
|
|
350
388
|
type: string;
|
|
351
389
|
time: number;
|
|
352
390
|
data: never;
|
|
391
|
+
seq?: number;
|
|
353
392
|
}[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>): SessionFold;
|
|
354
393
|
/**
|
|
355
394
|
* 增量聚合器:按会话缓存折叠结果,用日志文件的 mtime+size 作失效键——
|
|
@@ -26,18 +26,24 @@ export interface TrendPoint {
|
|
|
26
26
|
cost: number;
|
|
27
27
|
/** API calls that day (total across models). */
|
|
28
28
|
calls: number;
|
|
29
|
+
/** Total tokens that day (input+output+cache); used by the `tokens` metric. */
|
|
30
|
+
tokens?: number;
|
|
29
31
|
/** Per-model cost that day (stats key → CNY); absent entries plot zero. */
|
|
30
32
|
byModel?: Readonly<Record<string, number>>;
|
|
31
33
|
}
|
|
34
|
+
/** Metric rendered by the chart columns: daily cost (stacked by model) or total tokens. */
|
|
35
|
+
export type TrendMetric = 'cost' | 'tokens';
|
|
32
36
|
/**
|
|
33
|
-
* Render the daily
|
|
37
|
+
* Render the daily bars plus the total-calls line.
|
|
34
38
|
* @param props.data - sorted daily rows (ascending date).
|
|
35
|
-
* @param props.models - the model legend, in bar order.
|
|
39
|
+
* @param props.models - the model legend, in bar order (used by the `cost` metric).
|
|
36
40
|
* @param props.currency - display currency for the cost labels.
|
|
41
|
+
* @param props.metric - `cost` (stacked per-model CNY, default) or `tokens` (single-color total tokens).
|
|
37
42
|
*/
|
|
38
|
-
export declare function TrendChart({ data, models, currency }: {
|
|
43
|
+
export declare function TrendChart({ data, models, currency, metric }: {
|
|
39
44
|
data: readonly TrendPoint[];
|
|
40
45
|
models?: readonly TrendSeriesModel[];
|
|
41
46
|
currency?: CostCurrency;
|
|
47
|
+
metric?: TrendMetric;
|
|
42
48
|
}): React.ReactNode;
|
|
43
49
|
//# sourceMappingURL=TrendChart.d.ts.map
|
|
@@ -14,6 +14,7 @@ import type { SidebarFooterActionOwnerProps } from '@deepseek-ai/dsh-client-ui-s
|
|
|
14
14
|
import { type ClientPerf } from './PerfPanel.tsx';
|
|
15
15
|
import type { createBillingBudgetStore } from './budget-store.ts';
|
|
16
16
|
import { type CatalogModel } from './pricing.ts';
|
|
17
|
+
import type { ReconcileNotice } from '../pricing-shared.ts';
|
|
17
18
|
import { NS, type UsageBillingKey } from './locales.ts';
|
|
18
19
|
/** Model-connectivity health reported by the host model directory probe. */
|
|
19
20
|
export interface ModelHealth {
|
|
@@ -35,8 +36,8 @@ export interface ModelHealth {
|
|
|
35
36
|
/** 仪表盘分区 Tab id。 */
|
|
36
37
|
export type DashboardTab = 'overview' | 'token' | 'trends' | 'providers' | 'pricing' | 'settings';
|
|
37
38
|
/**
|
|
38
|
-
* Tab 定义(顺序即渲染顺序):概览=主数字/KPI
|
|
39
|
-
*
|
|
39
|
+
* Tab 定义(顺序即渲染顺序):概览=主数字/KPI/热力图,账单=厂商计费与订阅,
|
|
40
|
+
* 用量=Token 用量,趋势=趋势图/每轮费用,费率=模型单价表,设置=预算与峰谷提醒。
|
|
40
41
|
* 导出供测试断言 tab 与文案 key 对齐、decor 锚点落在正确分区。
|
|
41
42
|
*/
|
|
42
43
|
export declare const DASHBOARD_TABS: readonly {
|
|
@@ -121,6 +122,11 @@ interface SessionBillingRow {
|
|
|
121
122
|
export interface UsageStats {
|
|
122
123
|
/** 服务端聚合时间戳(毫秒);旧快照可能缺失。 */
|
|
123
124
|
updatedAt?: number;
|
|
125
|
+
/** 宿主进程时区(IANA 名 + UTC 偏移):天按此切分,副标题据此标注;旧快照可能缺失。 */
|
|
126
|
+
timezone?: {
|
|
127
|
+
name: string;
|
|
128
|
+
offset: string;
|
|
129
|
+
};
|
|
124
130
|
/** 月度预算(人民币元):宿主 Config 注入;未配置时不渲染预算条。 */
|
|
125
131
|
budget?: number;
|
|
126
132
|
/** 余额不足告警阈值(人民币元):宿主 Config 注入;未配置时客户端用默认值。 */
|
|
@@ -215,6 +221,11 @@ export interface UsageStats {
|
|
|
215
221
|
/** 插件版本号(服务端读自包 package.json;旧快照缺失)。 */
|
|
216
222
|
pluginVersion?: string;
|
|
217
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* 拉取官方余额差对账提示(drift 时非空),供余额面板展示;失败返回 undefined。
|
|
226
|
+
* @returns the reconcile notice, or undefined on any failure / no drift.
|
|
227
|
+
*/
|
|
228
|
+
export declare function fetchReconcile(): Promise<ReconcileNotice | undefined>;
|
|
218
229
|
/** 组件注入面:探活 + 计费指标写入(billing 自身写入,主题插件经服务读取)。 */
|
|
219
230
|
export interface UsageBillingInjected {
|
|
220
231
|
checkModels: () => Promise<ModelHealth>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locale dictionaries for the usage billing surface. */
|
|
2
|
-
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendEmpty' | 'billing.budget' | 'billing.sessions' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.pricingTip' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.anomaly' | 'billing.workspaces' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
2
|
+
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.sessions' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.pricingTip' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.anomaly' | 'billing.workspaces' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
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>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 声明端点(declarative endpoints):让用户为内置表没有的供应商自声明余额/额度接口,
|
|
3
|
+
* 不用等插件发版。用户提供「数字在哪儿」的取值路径,而非「怎么取」——没有表达式、
|
|
4
|
+
* 没有任何东西被求值,`fields` / `windows` 里写的只是响应 JSON 的点路径,唯一操作是
|
|
5
|
+
* 逐层下钻。
|
|
6
|
+
*
|
|
7
|
+
* ## 为什么把安全边界写在代码里而不是建议里
|
|
8
|
+
*
|
|
9
|
+
* 这个功能让**配置文件**决定一个携带用户 API key 的请求发往哪里。风险全在此,靠
|
|
10
|
+
* 「提醒用户小心」一点都没用:
|
|
11
|
+
*
|
|
12
|
+
* 1. `origin` 只是查找键:请求 URL 由**匹配到的 provider 的 origin** 构造,绝不由
|
|
13
|
+
* 声明的 origin 自己决定。匹配不到任何已配置的 provider 就不发请求。
|
|
14
|
+
* 2. `path` 必须单斜杠绝对路径:`//evil.example/x` 是协议相对 URL,`new URL()` 会
|
|
15
|
+
* 把它解析到别的主机;构造后还会再校验一次 origin。
|
|
16
|
+
* 3. 只发 GET,无请求体,无自定义 method / headers。
|
|
17
|
+
* 4. 凭据仍从匹配 provider 自己的 `apiKeyEnv` 取,经同一凭据 seam 解析;声明不能
|
|
18
|
+
* 指定任何凭据。
|
|
19
|
+
* 5. 跨源重定向直接失败,不跟随——那是绕过第 1 条最省事的办法。
|
|
20
|
+
* 6. 响应体有大小上限与共享超时,坏 / 恶意端点拖不住面板。
|
|
21
|
+
* 7. 声明不能覆盖内置读法:只在内置表答不上来时它才轮到。
|
|
22
|
+
*
|
|
23
|
+
* 这类行会标 `declared`,因为数字来自用户自己写的路径——取错是配置问题,界面要让
|
|
24
|
+
* 这一点看得出来;全部字段都没取到时给 `reason`,而不是留一张和「上游没返回」无从
|
|
25
|
+
* 区分的空卡。
|
|
26
|
+
*/
|
|
27
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
28
|
+
import type { DeclaredEndpointConfig, ProviderBalance } from './pricing-shared.ts';
|
|
29
|
+
/**
|
|
30
|
+
* 归一化 baseURL 为可比的 origin:`scheme://host[:port]`,剥掉 scheme 的默认端口,
|
|
31
|
+
* host 转小写,忽略路径。无法解析返回 undefined。
|
|
32
|
+
* @param baseURL - provider 的端点地址,或声明里的 origin。
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizeDeclaredOrigin(baseURL: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* 沿点路径走进已解析的响应体。
|
|
37
|
+
* 任何失败都是同一个答案 `undefined`——路径不匹配是这份响应没有那个字段,是卡片
|
|
38
|
+
* 本来就渲染得了的事实,不是错误,而且不该让已解析成功的字段被牵连。
|
|
39
|
+
* @param body - 已解析的响应 JSON。
|
|
40
|
+
* @param path - 点路径(如 `data.balance`)。
|
|
41
|
+
* @returns 路径处的值,或 undefined(路径缺失 / 中途不是对象 / 命中原型链)。
|
|
42
|
+
*/
|
|
43
|
+
export declare function readDeclaredPath(body: unknown, path: string): unknown;
|
|
44
|
+
/**
|
|
45
|
+
* 查询一组声明端点的余额/额度。每个声明独立成败,互不影响。
|
|
46
|
+
* @param ctx - host context carrying the credentials seam.
|
|
47
|
+
* @param providers - llm-pi-ai providers dict(`<route> → { baseURL?, apiKeyEnv? }`)。
|
|
48
|
+
* @param declarations - 声明端点配置列表。
|
|
49
|
+
* @returns 每个匹配到 provider 的声明一行结果;无匹配的声明不上报(不产生请求)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function queryDeclaredEndpoints(ctx: Context, providers: Readonly<Record<string, {
|
|
52
|
+
baseURL?: string;
|
|
53
|
+
apiKeyEnv?: string;
|
|
54
|
+
}>>, declarations: readonly DeclaredEndpointConfig[]): Promise<readonly ProviderBalance[]>;
|
|
55
|
+
//# sourceMappingURL=declarative.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ 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
16
|
import { type UsageLedgerStore } from './aggregate.ts';
|
|
17
|
-
import type { CustomBalanceConfig, SubscriptionPlanConfig } from './pricing-shared.ts';
|
|
17
|
+
import type { CustomBalanceConfig, DeclaredEndpointConfig, SubscriptionPlanConfig } from './pricing-shared.ts';
|
|
18
18
|
import { type IdentifiedSubscriptionPlan, type SubscriptionKeys } from './subscriptions.ts';
|
|
19
19
|
/**
|
|
20
20
|
* 回环防护守卫:仅接受回环 GET 请求(peer socket 地址 + Host 头同时校验)。
|
|
@@ -28,9 +28,14 @@ export declare function guardLoopback(req: IncomingMessage, res: ServerResponse)
|
|
|
28
28
|
export interface UsageBillingConfig {
|
|
29
29
|
/** Absolute path to a `.dsh-usage-stats.json` fallback file. */
|
|
30
30
|
statsPath?: string;
|
|
31
|
+
/** 统计快照的持久化路径;默认 `~/.dsh/.dsh-usage-stats.json`。
|
|
32
|
+
* 测试注入临时目录以隔离真实家目录(聚合失败回退与快照落盘都走此路径)。 */
|
|
33
|
+
snapshotPath?: string;
|
|
31
34
|
/** 独立持久用量账本的绝对路径;默认 `~/.dsh/.dsh-usage-ledger.json`。
|
|
32
35
|
* 账本与会话日志解耦,因此永久删除会话不会抹掉已经观测到的用量。 */
|
|
33
36
|
ledgerPath?: string;
|
|
37
|
+
/** 余额差对账基准的持久化路径;默认 `~/.dsh/.dsh-usage-reconcile.json`。 */
|
|
38
|
+
reconcilePath?: string;
|
|
34
39
|
/** 订阅制(coding / token / agent plan)provider id 列表;默认 kimi-coding、xiaomi-token-plan-cn。 */
|
|
35
40
|
subscriptionProviders?: string[];
|
|
36
41
|
/** 订阅套餐额度适配器(kimi / zai / opencode-go);默认全部内置。 */
|
|
@@ -44,6 +49,13 @@ export interface UsageBillingConfig {
|
|
|
44
49
|
lowBalanceThreshold?: number;
|
|
45
50
|
/** 自定义 Provider 余额查询(任意 HTTP 端点 + extract 规则,适配 NewApi/LiteLLM 等)。 */
|
|
46
51
|
customBalances?: readonly CustomBalanceConfig[];
|
|
52
|
+
/**
|
|
53
|
+
* 声明端点(declarative endpoints):为内置表没有的供应商自声明余额/额度接口。
|
|
54
|
+
* 绑定到某条已配置 provider 的同源地址,`fields` / `windows` 写响应里的取值路径;
|
|
55
|
+
* 安全边界(origin 绑定、单斜杠 path、只 GET、跨源重定向失败、凭据取自 apiKeyEnv)
|
|
56
|
+
* 写死在 declarative.ts。缺省空。
|
|
57
|
+
*/
|
|
58
|
+
declaredEndpoints?: readonly DeclaredEndpointConfig[];
|
|
47
59
|
/** `usage_stats` 工具注入的组合 base(默认 false:不注入);与设置命名空间同字段,
|
|
48
60
|
* 作为用户设置(设置 Tab 开关)的组合兜底。该工具占用每次请求的上下文,coding 场景多在仪表盘查看。 */
|
|
49
61
|
enableUsageStatsTool?: boolean;
|
|
@@ -60,10 +60,32 @@ export interface ProviderBalance {
|
|
|
60
60
|
isAvailable?: boolean;
|
|
61
61
|
/** 未配置/鉴权失败/网络不可达等失败原因;缺省 = 查询成功。 */
|
|
62
62
|
error?: BalanceError;
|
|
63
|
+
/** 声明端点(declaredEndpoint)出数:数字来自用户配置的取值路径,界面据此标注。 */
|
|
64
|
+
declared?: boolean;
|
|
65
|
+
/** 声明端点未解析出字段 / 安全边界拒绝时的原因文本(配置问题,而非上游故障)。 */
|
|
66
|
+
reason?: string;
|
|
67
|
+
/** 声明端点给出的滚动额度窗口(与订阅面板同款)。 */
|
|
68
|
+
windows?: readonly SubscriptionWindow[];
|
|
69
|
+
/** 订阅套餐名(声明端点读出的 plan 字段)。 */
|
|
70
|
+
plan?: string;
|
|
63
71
|
}
|
|
64
72
|
/** Response of `/api/billing/balance` consumed by the dashboard. */
|
|
65
73
|
export interface BalanceResponse {
|
|
66
74
|
balances: readonly ProviderBalance[];
|
|
75
|
+
/** 官方余额差对账提示(对账开启且当日有余额消费时下发)。 */
|
|
76
|
+
reconcile?: ReconcileNotice;
|
|
77
|
+
}
|
|
78
|
+
/** 官方余额差对账提示(drift 时携带供 UI 展示的已用/本地费用)。 */
|
|
79
|
+
export interface ReconcileNotice {
|
|
80
|
+
kind: 'baseline' | 'structure-reset' | 'flat' | 'ok' | 'drift';
|
|
81
|
+
/** 余额提供商显示名(如 DeepSeek);仅 `ok`/`drift`。 */
|
|
82
|
+
provider?: string;
|
|
83
|
+
/** 余额币种(CNY / USD)。 */
|
|
84
|
+
currency?: string;
|
|
85
|
+
/** 按余额差反推的当日消费;仅 `ok`/`drift`。 */
|
|
86
|
+
spent?: number;
|
|
87
|
+
/** 本地账本当日的官方渠道费用(CNY);仅 `ok`/`drift`。 */
|
|
88
|
+
todayOfficialCost?: number;
|
|
67
89
|
}
|
|
68
90
|
/** Quota query result status; the dashboard maps each to a row state. */
|
|
69
91
|
export type SubscriptionStatus = 'ok' | 'not-configured' | 'unauthorized' | 'rate-limited' | 'unavailable' | 'invalid-response';
|
|
@@ -98,6 +120,8 @@ export interface SubscriptionQuota {
|
|
|
98
120
|
planType?: 'code' | 'token';
|
|
99
121
|
/** 订阅月费(人民币元;code 计划用,计入「本月预计」)。 */
|
|
100
122
|
subscriptionAmount?: number;
|
|
123
|
+
/** 面向用户的配置提示(如 OpenRouter 额度接口只认 Management Key)。 */
|
|
124
|
+
hint?: string;
|
|
101
125
|
}
|
|
102
126
|
/** Response of `/api/billing/subscriptions`. */
|
|
103
127
|
export interface SubscriptionResponse {
|
|
@@ -178,4 +202,64 @@ export interface CustomBalanceConfig {
|
|
|
178
202
|
remaining: CustomBalanceExtract;
|
|
179
203
|
};
|
|
180
204
|
}
|
|
205
|
+
/** 声明端点的一个滚动窗口输入:`kind`/`minutes` 是字面值,其余是响应里的取值路径。 */
|
|
206
|
+
export interface DeclaredWindowConfig {
|
|
207
|
+
/** 窗口周期:session / weekly / monthly / billing。 */
|
|
208
|
+
kind?: SubscriptionWindow['kind'];
|
|
209
|
+
/** 窗口分钟周期(用于按 resetInSeconds 推算周期;缺省不推)。 */
|
|
210
|
+
minutes?: number;
|
|
211
|
+
/** 已用百分比(0–100,取值路径)。 */
|
|
212
|
+
usedPercent?: string;
|
|
213
|
+
/** 已用比例(0–1,取值路径)。 */
|
|
214
|
+
usedRatio?: string;
|
|
215
|
+
/** 剩余百分比(取值路径)。 */
|
|
216
|
+
remainingPercent?: string;
|
|
217
|
+
/** 已用量(取值路径)。 */
|
|
218
|
+
used?: string;
|
|
219
|
+
/** 总量(取值路径)。 */
|
|
220
|
+
limit?: string;
|
|
221
|
+
/** 重置时刻(ISO 字符串,取值路径)。 */
|
|
222
|
+
resetsAt?: string;
|
|
223
|
+
/** 距重置剩余秒数(取值路径)。 */
|
|
224
|
+
resetInSeconds?: string;
|
|
225
|
+
}
|
|
226
|
+
/** 声明端点的字段取值路径:值里写的是响应 JSON 里的点路径,不是表达式。 */
|
|
227
|
+
export interface DeclaredEndpointFields {
|
|
228
|
+
/** 余额总量(取值路径)。 */
|
|
229
|
+
total?: string;
|
|
230
|
+
/** 赠金余额(取值路径)。 */
|
|
231
|
+
granted?: string;
|
|
232
|
+
/** 已用(取值路径)。 */
|
|
233
|
+
used?: string;
|
|
234
|
+
/** 币种(取值路径)。 */
|
|
235
|
+
currency?: string;
|
|
236
|
+
/** 套餐名(取值路径)。 */
|
|
237
|
+
plan?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 一个声明端点配置(插件 config 的 `declaredEndpoints` 条目)。用户为内置表
|
|
241
|
+
* 没有的供应商自声明余额/额度接口。
|
|
242
|
+
*
|
|
243
|
+
* 安全边界写死在代码里,不靠自觉(详见 declarative.ts):
|
|
244
|
+
* - `origin` 只是查找键,请求必须发往某条**已配置** provider 的同源地址;
|
|
245
|
+
* - `path` 必须单斜杠绝对路径(`//host/x` 是协议相对 URL,会解析到别的主机);
|
|
246
|
+
* - 只发 GET、无请求体、无自定义 method/headers;
|
|
247
|
+
* - 凭据仍从匹配 provider 自己的 `apiKeyEnv` 取,声明不能指定凭据;
|
|
248
|
+
* - 跨源重定向直接失败;响应体有大小上限与超时;
|
|
249
|
+
* - 不能覆盖内置读法,只在内置表答不上来时轮到它。
|
|
250
|
+
*/
|
|
251
|
+
export interface DeclaredEndpointConfig {
|
|
252
|
+
/** 查找键:必须匹配某条已配置 provider 的 baseURL 同源地址。 */
|
|
253
|
+
origin: string;
|
|
254
|
+
/** 显示名;缺省用 `已声明`。 */
|
|
255
|
+
displayName?: string;
|
|
256
|
+
/** 余额端点(单斜杠绝对路径,基于匹配 provider 的 origin 构造)。 */
|
|
257
|
+
path: string;
|
|
258
|
+
/** 裸密钥模式:true 时不加 `Bearer ` 前缀(某些控制台接口要裸 key)。 */
|
|
259
|
+
raw?: boolean;
|
|
260
|
+
/** 字段取值路径。 */
|
|
261
|
+
fields?: DeclaredEndpointFields;
|
|
262
|
+
/** 滚动额度窗口列表。 */
|
|
263
|
+
windows?: readonly DeclaredWindowConfig[];
|
|
264
|
+
}
|
|
181
265
|
//# sourceMappingURL=pricing-shared.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 余额差交叉校验(reconcile):用官方账户余额当日变动反推消费,与本地账本
|
|
3
|
+
* 的当日官方费用比对。若二者偏差超阈值,提示用户核对价格表或近期账单——
|
|
4
|
+
* 这是让计费数据可信的兜底机制,也是对账的稳定锚点。
|
|
5
|
+
*
|
|
6
|
+
* 只对官方直连渠道(DeepSeek 官方)对账:订阅 / Coding Plan / 第三方中转的
|
|
7
|
+
* 消费不动官方余额,强行用余额差替代今日费用会把订阅用户全天的消费归零。
|
|
8
|
+
* 充值、授信、币种变化都会让旧基准失去可比性,此时重置基准而非告警。
|
|
9
|
+
*
|
|
10
|
+
* 纯函数:输入上一基准与本次余额查询结果,输出新基准与对账事件,可独立单测。
|
|
11
|
+
*/
|
|
12
|
+
/** 对账基准快照:上次余额查询时的官方账户状态,用于反推当日消费。 */
|
|
13
|
+
export interface BalanceRef {
|
|
14
|
+
/** 本地日期键(YYYY-MM-DD)。 */
|
|
15
|
+
date: string;
|
|
16
|
+
/** 当日总余额。 */
|
|
17
|
+
total: number;
|
|
18
|
+
/** 未过期赠金。 */
|
|
19
|
+
granted: number;
|
|
20
|
+
/** 充值余额。 */
|
|
21
|
+
topped: number;
|
|
22
|
+
/** 余额币种(CNY / USD)。 */
|
|
23
|
+
currency: string;
|
|
24
|
+
/** 打基准时刻(epoch ms)。 */
|
|
25
|
+
at: number;
|
|
26
|
+
}
|
|
27
|
+
/** 一次对账的结果类别。 */
|
|
28
|
+
export type ReconcileKind = 'baseline' | 'structure-reset' | 'flat' | 'ok' | 'drift';
|
|
29
|
+
/** 对账事件:`ok`/`drift`/`flat` 携带有意义的结果,`baseline`/`structure-reset` 仅为无消费的基准刷新。 */
|
|
30
|
+
export interface ReconcileEvent {
|
|
31
|
+
kind: ReconcileKind;
|
|
32
|
+
/** 余额提供商显示名(如 DeepSeek);仅 `ok`/`drift`。 */
|
|
33
|
+
provider?: string;
|
|
34
|
+
/** 按余额差反推的当日消费(CNY/USD 按余额币种);仅 `ok`/`drift`。 */
|
|
35
|
+
spent?: number;
|
|
36
|
+
/** 本地账本当日的官方渠道费用;仅 `ok`/`drift`。 */
|
|
37
|
+
todayOfficialCost?: number;
|
|
38
|
+
}
|
|
39
|
+
/** 余额查询结果的最小视图(对账只关心这三个分项与币种)。 */
|
|
40
|
+
interface BalanceSnapshot {
|
|
41
|
+
totalBalance?: number;
|
|
42
|
+
grantedBalance?: number;
|
|
43
|
+
toppedUpBalance?: number;
|
|
44
|
+
currency?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 对账:用官方余额当日变动反推消费,与本地账本当日官方费用比对。
|
|
48
|
+
* @param prevRef - 上一基准(可为 null,表示首次/基准缺失)。
|
|
49
|
+
* @param balance - 本次余额查询结果。
|
|
50
|
+
* @param todayOfficialCost - 本地账本当日的官方渠道费用(CNY)。
|
|
51
|
+
* @param dayKey - 本地日期键(YYYY-MM-DD)。
|
|
52
|
+
* @param nowMs - 当前时刻(epoch ms)。
|
|
53
|
+
* @returns 新基准与对账事件;余额不可用(无 totalBalance)时返回 `{ ref: prevRef, event: null }`。
|
|
54
|
+
*/
|
|
55
|
+
export declare function reconcileBalanceDelta(prevRef: BalanceRef | null, balance: BalanceSnapshot, todayOfficialCost: number, dayKey: string, nowMs: number): {
|
|
56
|
+
ref: BalanceRef | null;
|
|
57
|
+
event: ReconcileEvent | null;
|
|
58
|
+
};
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
package/lib/types/relay.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ export declare function parseSub2ApiUsage(data: unknown): {
|
|
|
44
44
|
export declare function parseNewApiStatus(data: unknown): {
|
|
45
45
|
windows?: readonly SubscriptionWindow[];
|
|
46
46
|
} | null;
|
|
47
|
+
/**
|
|
48
|
+
* 判断一个 baseURL 是否指向已知官方端点(而非第三方中转站)。
|
|
49
|
+
* @param baseURL - llm-pi-ai provider 路由的端点地址。
|
|
50
|
+
* @returns 官方端点返回 true(中转站面板应排除)。
|
|
51
|
+
*/
|
|
52
|
+
export declare function isOfficialBaseUrl(baseURL: string): boolean;
|
|
47
53
|
/**
|
|
48
54
|
* 查询单个中转站路由的额度。先试 Sub2API,再试 New API;任一读出额度即返回。
|
|
49
55
|
* @param ctx - host context carrying the credentials seam.
|
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": "0.9.
|
|
4
|
+
"version": "0.9.18",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|