@kenz1117/dsh-ui-usage-billing 0.1.0

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Real-usage aggregation: folds every persisted session log into the
3
+ * usage-stats document the dashboard renders.
4
+ *
5
+ * Each LLM call is attributed to the model of the `request/header` event that
6
+ * precedes its `assistant/message` usage event. Costs are estimated with the
7
+ * shared billing catalog (`pricing.ts`, in CNY), so only models the catalog
8
+ * prices incur a cost — subscription-plan routes and unknown models price
9
+ * zero while their tokens still count. Pure functions only: the persistence
10
+ * handle is injected, so the fold is unit-testable without a host.
11
+ */
12
+ import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
13
+ import type { TokenUsage } from '@deepseek-ai/dsh-llm';
14
+ /**
15
+ * Real provider model ids map to their billing-catalog keys. Unknown ids stay
16
+ * as-is and price zero (they are not in the catalog; subscription-plan routes
17
+ * like kimi-coding / token plans fall here and therefore cost nothing).
18
+ */
19
+ export declare const MODEL_KEY_ALIASES: Readonly<Record<string, string>>;
20
+ /**
21
+ * 走订阅套餐(coding / token / agent plan)的 provider id:这些通道的调用
22
+ * 按套餐计费,不再按 token 计费,因此即使模型 id 与计费表撞名也一律豁免。
23
+ * 部署可在 plugin config 的 `subscriptionProviders` 中覆盖。
24
+ */
25
+ export declare const DEFAULT_SUBSCRIPTION_PROVIDERS: readonly string[];
26
+ /** Aggregation tuning options. */
27
+ export interface AggregateOptions {
28
+ /** 订阅制 provider id 列表;默认 {@link DEFAULT_SUBSCRIPTION_PROVIDERS}。 */
29
+ subscriptionProviders?: readonly string[];
30
+ }
31
+ /** One model's aggregated usage plus estimated cost in CNY. */
32
+ export interface ModelUsage {
33
+ calls: number;
34
+ input: number;
35
+ output: number;
36
+ cacheHit: number;
37
+ cacheMiss: number;
38
+ cost: number;
39
+ }
40
+ /** Zeroed usage accumulator. */
41
+ export declare function emptyUsage(): ModelUsage;
42
+ /**
43
+ * Fold one token usage event into an accumulator and re-price its cost.
44
+ * The stats `input` is the TOTAL prompt tokens (cacheHit + cacheMiss), so the
45
+ * miss bucket is uncached input plus cache writes.
46
+ * @param acc - the accumulator to mutate.
47
+ * @param usage - the provider-reported usage of one call.
48
+ * @param key - the billing-catalog key this call belongs to.
49
+ * @param subscription - whether the call went through a subscription plan; such calls never cost money.
50
+ */
51
+ export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: string, subscription: boolean): void;
52
+ /** Local-time date stamp (the host runs in the user's timezone). */
53
+ export declare function dayStamp(time: number): string;
54
+ /**
55
+ * The persistence surface the aggregate reads: enough of
56
+ * `SessionPersistence` to list sessions and read each log once.
57
+ */
58
+ export type UsagePersistence = Pick<SessionPersistence, 'list' | 'readFrom'>;
59
+ /**
60
+ * Aggregate real usage from every persisted session log.
61
+ * @param persistence - the session persistence service.
62
+ * @param options - aggregation tuning (e.g. subscription-plan providers).
63
+ * @returns the usage-stats document (same shape the dashboard expects).
64
+ */
65
+ export declare function aggregateUsage(persistence: UsagePersistence, options?: AggregateOptions): Promise<unknown>;
66
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * TrendChart: dependency-free SVG combo chart of daily cost (area line) and
3
+ * daily call volume (bars) with a hover crosshair. No chart library — the
4
+ * surface stays self-contained and offline.
5
+ */
6
+ /** One day row fed to the chart. */
7
+ export interface TrendPoint {
8
+ /** ISO date `YYYY-MM-DD`. */
9
+ date: string;
10
+ /** USD cost that day. */
11
+ cost: number;
12
+ /** API calls that day. */
13
+ calls: number;
14
+ }
15
+ /**
16
+ * Render the daily trend chart.
17
+ * @param props.data - sorted daily rows (ascending date).
18
+ */
19
+ export declare function TrendChart({ data }: {
20
+ data: readonly TrendPoint[];
21
+ }): React.ReactNode;
22
+ //# sourceMappingURL=TrendChart.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * UsageBilling: sidebar footer trigger + full billing dashboard modal.
3
+ *
4
+ * The trigger sits above Settings in the sidebar footer (rail shows an icon,
5
+ * wide shows a pill with the running total). Clicking opens a centered modal
6
+ * dashboard: hero total, KPI tiles, a dependency-free SVG daily trend chart,
7
+ * a per-model billing table priced from the built-in catalog, and a pricing
8
+ * table. Data comes from the host's `/api/billing/usage-stats` endpoint;
9
+ * before real data arrives the dashboard shows an empty (zero) snapshot,
10
+ * never fabricated samples.
11
+ */
12
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
13
+ import type { SidebarFooterActionOwnerProps } from '@deepseek-ai/dsh-client-ui-sidebar/client';
14
+ import { NS } from './locales.ts';
15
+ /** Model-connectivity health reported by the host model directory probe. */
16
+ export interface ModelHealth {
17
+ /** Whether the probe completed (false while still loading). */
18
+ checked: boolean;
19
+ /** True when at least one connected provider answered its model catalog. */
20
+ available: boolean;
21
+ /** Connected provider count. */
22
+ providers: number;
23
+ /** Provider count whose catalog probe failed. */
24
+ failures: number;
25
+ /** Display names of providers that answered their model catalog (live). */
26
+ okProviders: readonly string[];
27
+ /** Display names of providers whose catalog probe failed. */
28
+ badProviders: readonly string[];
29
+ }
30
+ /** Full props type for the UsageBilling component. */
31
+ type UsageBillingProps = PropsRuntime<'sidebar.footer.action'> & SidebarFooterActionOwnerProps & InjectFace<{
32
+ checkModels: () => Promise<ModelHealth>;
33
+ }> & PropsLocale<typeof NS>;
34
+ /**
35
+ * UsageBilling: sidebar trigger plus the billing dashboard modal.
36
+ * @param props - framework-provided sidebar and locale props.
37
+ */
38
+ export declare function UsageBilling(props: UsageBillingProps): React.ReactNode;
39
+ export {};
40
+ //# sourceMappingURL=UsageBilling.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Usage billing plugin, browser half: registers the UsageBilling component
3
+ * in the sidebar.footer.action slot.
4
+ *
5
+ * Displays compact cost/token/cache metrics in the sidebar footer, above the
6
+ * Settings button, plus a model-health dot (green when any connected model
7
+ * route responds). Expands to a detailed dashboard panel on click.
8
+ */
9
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import { type UsageBillingKey } from './locales.ts';
11
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
12
+ interface LocaleNamespaceMap {
13
+ /** The usage billing surface's copy. */
14
+ usageBilling: UsageBillingKey;
15
+ }
16
+ }
17
+ /** Required services for the usage billing surface. */
18
+ export declare const inject: string[];
19
+ /**
20
+ * Client plugin body: the UsageBilling entry in the sidebar footer.
21
+ * @param ctx - client root context.
22
+ */
23
+ export declare function apply(ctx: ClientContext): void;
24
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Usage billing surface plugin, browser half: compact cost/token metrics
3
+ * displayed in the session header utilities.
4
+ *
5
+ * Shows real-time cost, token usage, cache hit rate, and model breakdown.
6
+ * Expands to a detailed dashboard panel on click.
7
+ */
8
+ export { inject, apply } from './apply.ts';
9
+ export { UsageBilling } from './UsageBilling.tsx';
10
+ export type { UsageBillingKey } from './locales.ts';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Locale dictionaries for the usage billing surface. */
2
+ export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trendEmpty' | 'billing.models' | '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';
3
+ export declare const NS = "usageBilling";
4
+ export declare const zh: Record<UsageBillingKey, string>;
5
+ export declare const en: Record<UsageBillingKey, string>;
6
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Billing engine: per-model price tables and token-usage cost estimation.
3
+ *
4
+ * Each model's price table uses its NATIVE currency: domestic providers
5
+ * (DeepSeek, 智谱, 通义…) publish RMB prices and store them directly;
6
+ * overseas providers (OpenAI, Google, xAI, Meta) publish USD.
7
+ * Cost is always computed and displayed in CNY — only USD-priced models go
8
+ * through the exchange rate, never domestic ones.
9
+ *
10
+ * Google-style two-band billing is modeled per model: Gemini's Flex tier
11
+ * prices spare-capacity traffic at -50%; DeepSeek V4 splits peak
12
+ * (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate. The estimator
13
+ * mixes both bands by a configured peak share ({@link DEFAULT_PEAK_SHARE}).
14
+ */
15
+ /**
16
+ * USD → CNY rate for display. Source: China Foreign Exchange Trade System
17
+ * mid-rate 6.7878 on 2026-08-14; rounded to 6.79. Only applies to overseas
18
+ * USD-priced models — domestic models never pass through this rate.
19
+ */
20
+ export declare const USD_TO_CNY = 6.79;
21
+ /** Default share of traffic assumed to fall in the peak band (0..1). */
22
+ export declare const DEFAULT_PEAK_SHARE = 0.5;
23
+ /**
24
+ * Model keys served through a subscription plan (e.g. a coding plan or topic
25
+ * plan) instead of metered per-token API billing. Usage through these routes
26
+ * costs no tokens: the estimator treats them as ¥0 and the billing table
27
+ * labels them 订阅包含. Add any model key your deployment serves through a
28
+ * plan here; leave empty when every route is pay-as-you-go.
29
+ */
30
+ export declare const SUBSCRIPTION_PLAN_KEYS: readonly string[];
31
+ /** Whether one stats model key is billed through a subscription plan. */
32
+ export declare function isSubscriptionPlan(key: string): boolean;
33
+ /** Usage buckets consumed by one model (counts in raw tokens). */
34
+ export interface TokenUsageBuckets {
35
+ /** Uncached input tokens. */
36
+ input: number;
37
+ /** Cache-hit input tokens. */
38
+ cacheHit: number;
39
+ /** Cache-miss input tokens (already included in `input` by some providers). */
40
+ cacheMiss: number;
41
+ /** Output tokens. */
42
+ output: number;
43
+ }
44
+ /** Per-1M-token price in the model's native currency for one billing band. */
45
+ export interface PriceBand {
46
+ /** Input (uncached) price per 1M tokens. */
47
+ input: number;
48
+ /** Cache-hit input price per 1M tokens. */
49
+ cacheHit: number;
50
+ /** Cache-miss input price per 1M tokens (absent when folded into `input`). */
51
+ cacheMiss?: number;
52
+ /** Output price per 1M tokens. */
53
+ output: number;
54
+ }
55
+ /** A model's price table, optionally split into peak/off-peak bands. */
56
+ export interface ModelPrice extends PriceBand {
57
+ /** 计价币种:国内模型直接人民币(CNY),国外模型美元(USD)。 */
58
+ currency: 'CNY' | 'USD';
59
+ /** Off-peak band (Gemini Flex / DeepSeek 低谷档); absent = flat pricing. */
60
+ offPeak?: PriceBand;
61
+ }
62
+ /** One catalog entry: identity, brand color token, and price. */
63
+ export interface ModelEntry {
64
+ /** Model key used by `.dsh-usage-stats.json` `byModel`. */
65
+ key: string;
66
+ /** Human-readable model name. */
67
+ name: string;
68
+ /** Provider label. */
69
+ provider: string;
70
+ /** CSS variable name (without the leading `--`) used as the brand accent. */
71
+ colorVar: string;
72
+ /** Price table (peak band when a split exists). */
73
+ price: ModelPrice;
74
+ /** Peak-hour window label for time-of-day priced models. */
75
+ peakHours?: string;
76
+ }
77
+ /**
78
+ * Built-in catalog of current mainstream models as of 2026-08-16, priced from
79
+ * each provider's official price page. Domestic providers are OpenAI-API
80
+ * compatible and publish RMB prices directly; overseas providers publish USD
81
+ * and convert through the exchange rate at estimate time. Retired models
82
+ * (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
83
+ * absent, as are Anthropic Claude models (their native API is not
84
+ * OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
85
+ * keys match the harness stats file so real usage prices from the catalog;
86
+ * unknown keys fall back to `other`.
87
+ *
88
+ * Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
89
+ * splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
90
+ * from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
91
+ */
92
+ export declare const MODEL_CATALOG: readonly ModelEntry[];
93
+ /** Lookup a model by its stats key; falls back to the generic `other` entry. */
94
+ export declare function modelOf(key: string): ModelEntry;
95
+ /** Resolve a price-table row by its CSS variable name (theme token or fallback color). */
96
+ export declare function resolveToken(name: string): string;
97
+ /**
98
+ * Estimate the CNY cost of one model's token usage, mixing the peak and
99
+ * off-peak bands by the given peak share (flat-priced models cost the same in
100
+ * both bands).
101
+ * @param entry - the catalog entry whose prices apply.
102
+ * @param buckets - token usage counts.
103
+ * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
104
+ * @returns the estimated cost in CNY.
105
+ */
106
+ export declare function computeCost(entry: ModelEntry, buckets: TokenUsageBuckets, peakShare?: number): number;
107
+ /** Format a CNY amount with adaptive precision. */
108
+ export declare function formatMoney(cny: number): string;
109
+ /**
110
+ * Format a per-1M-token price in its native currency (free when the rate is
111
+ * zero): CNY for domestic models, USD for overseas ones.
112
+ */
113
+ export declare function formatUnitPrice(price: number, currency?: 'CNY' | 'USD'): string;
114
+ /** Format a large token count with B/M/K suffix. */
115
+ export declare function formatTokens(value: number): string;
116
+ /** Format a percentage. */
117
+ export declare function formatPercent(value: number): string;
118
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Usage billing surface plugin, node half.
3
+ *
4
+ * Serves `/api/billing/usage-stats`: real usage aggregated from every
5
+ * persisted session log (see `aggregate.ts`) — the browser dashboard reads it
6
+ * instead of showing an empty snapshot. When `sessionPersistence` is
7
+ * unavailable (or aggregation fails), the configured `statsPath` /
8
+ * `DSH_USAGE_STATS` / conventional JSON file is served as a fallback, and a
9
+ * missing file answers `{ error }` so the dashboard shows zeros, never
10
+ * fabricated samples.
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ /** Plugin configuration. */
14
+ export interface UsageBillingConfig {
15
+ /** Absolute path to a `.dsh-usage-stats.json` fallback file. */
16
+ statsPath?: string;
17
+ /** 订阅制(coding / token / agent plan)provider id 列表;默认 kimi-coding、xiaomi-token-plan-cn。 */
18
+ subscriptionProviders?: string[];
19
+ }
20
+ /** Required services: the web server and the persisted session log store. */
21
+ export declare const inject: string[];
22
+ /**
23
+ * Host plugin body: serve real aggregated usage to the browser dashboard.
24
+ * @param ctx - host context carrying webServer and sessionPersistence.
25
+ * @param config - optional statsPath override.
26
+ */
27
+ export declare function apply(ctx: Context, config?: UsageBillingConfig): void;
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ /** Package invariant companion for `@kenz1117/dsh-ui-usage-billing`. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ export declare const name = "usage-billing-invariant";
4
+ export declare const inject: string[];
5
+ /**
6
+ * Register this package's invariant companion.
7
+ * @param ctx - Host context carrying the invariant registry.
8
+ * @returns the registration disposer after setup succeeds.
9
+ */
10
+ export declare const apply: (ctx: Context) => Promise<() => void>;
11
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@kenz1117/dsh-ui-usage-billing",
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.1.0",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kenz1117/dsh-ui-usage-billing.git"
11
+ },
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/types/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./invariant": {
21
+ "types": "./lib/types/invariant.d.ts",
22
+ "default": "./lib/invariant.js"
23
+ },
24
+ "./client": {
25
+ "types": "./lib/types/client/index.d.ts",
26
+ "default": "./lib/client.js"
27
+ },
28
+ "./src/*": "./src/*",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "dsh": {
32
+ "client": {
33
+ "inject": [
34
+ "@deepseek-ai/dsh-client-runtime",
35
+ "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-ui-conversation"
37
+ ],
38
+ "platform": "web"
39
+ }
40
+ },
41
+ "scripts": {
42
+ "bundle": "tsdown",
43
+ "watch": "tsdown --watch"
44
+ },
45
+ "license": "MIT",
46
+ "peerDependencies": {
47
+ "@deepseek-ai/dsh-api-remotes": "*",
48
+ "@deepseek-ai/dsh-client-connection": "*",
49
+ "@deepseek-ai/dsh-host-webserver": "*",
50
+ "@deepseek-ai/dsh-client-locale": "*",
51
+ "@deepseek-ai/dsh-client-runtime": "*",
52
+ "@deepseek-ai/dsh-client-ui-conversation": "*",
53
+ "@deepseek-ai/dsh-client-ui-sidebar": "*",
54
+ "@deepseek-ai/dsh-client-ui-primitives": "*",
55
+ "@deepseek-ai/dsh-client-ui-slots": "*",
56
+ "@deepseek-ai/dsh-invariants": "*",
57
+ "@deepseek-ai/dsh-llm": "*",
58
+ "@deepseek-ai/dsh-session": "*",
59
+ "@deepseek-ai/dsh-session-persistence": "*",
60
+ "@deepseek-ai/cordis": "*",
61
+ "react": "^18.2.0"
62
+ },
63
+ "dependencies": {
64
+ "clsx": "^2.1.1"
65
+ },
66
+ "devDependencies": {
67
+ "@deepseek-ai/dsh-client-locale": "workspace:^",
68
+ "@deepseek-ai/dsh-client-runtime": "workspace:^",
69
+ "@deepseek-ai/dsh-client-test-runtime": "workspace:^",
70
+ "@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
71
+ "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
72
+ "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
73
+ "@deepseek-ai/dsh-invariants": "workspace:^",
74
+ "@testing-library/react": "^16.1.0",
75
+ "@types/react": "~18.3.1",
76
+ "@deepseek-ai/cordis": "workspace:^",
77
+ "react": "^18.2.0",
78
+ "react-dom": "^18.2.0"
79
+ },
80
+ "files": [
81
+ "lib/index.js",
82
+ "lib/invariant.js",
83
+ "lib/client.js",
84
+ "lib/types/**/*.d.ts"
85
+ ]
86
+ }