@rayadesu/dsh-llm-billing 0.1.0-rc.8 → 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.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record both hashes with:
4
4
  # git hash-object README.md README.zh.md
5
- README.md: 655288ebdb37cee4ecd3edfad8b3953e3a4e87ee
6
- README.zh.md: e8ac4a2716ffe57200bb9757e1f0c6dabaf77c2f
5
+ README.md: c51e3f984ce4eca27bc3998068c6a068e5c8e617
6
+ README.zh.md: bf1385e6414657c262e153c805ced50c04a2575a
package/README.md CHANGED
@@ -16,7 +16,7 @@ Add the plugin to a composition (a `cordis.yml` row) and give it a credential. I
16
16
  # baseURL: https://api.deepseek.com
17
17
  ```
18
18
 
19
- The plugin registers the `billing` Remote with three methods: `getBalance()` (the parsed `/user/balance` snapshot), `getSessionSpend(sessionId)` (one session's billed cost), and `getTodaySpend()` (every session's billed cost on the current Beijing-time calendar day). The spend prices each `assistant/message` event's billed tokens (cache-hit input, cache-miss input including cache writes, and output including reasoning) at the official rate of the event's own Beijing-time peak/off-peak hour, then sums per model.
19
+ The plugin registers the `billing` Remote with three methods: `getBalance()` (the parsed `/user/balance` snapshot), `getSessionSpend(sessionId)` (one session's billed cost), and `getTodaySpend()` (every session's billed cost on the current Beijing-time calendar day). The spend prices each `assistant/message` event's billed tokens (cache-hit input, cache-miss input including cache writes, and output including reasoning) at the official rate of the event's own Beijing-time peak/off-peak classification — peak windows apply weekdays (Monday–Friday) only, and weekends are always off-peak — then sums per model.
20
20
 
21
21
  ## Configuration
22
22
 
@@ -25,7 +25,7 @@ The plugin registers the `billing` Remote with three methods: `getBalance()` (th
25
25
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential-reference (environment-variable) name resolved per call. |
26
26
  | `baseURL` | `$DEEPSEEK_BASE_URL` then `https://api.deepseek.com` | Endpoint base; `/user/balance` is appended. |
27
27
  | `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | Advisory display rows, in presentation order. |
28
- | `billing.peakHours` | 09:00–12:00, 14:00–18:00 (Beijing) | Peak-hour windows; all other hours are off-peak. |
28
+ | `billing.peakHours` | 09:00–12:00, 14:00–18:00 (Beijing, weekdays) | Peak-hour windows, applied weekdays (Mon–Fri) only; weekends and all other hours are off-peak. |
29
29
  | `billing.models` | Published V4 rates | Per-model peak/off-peak price rows (`cacheHitInput`, `cacheMissInput`, `output`, in CNY per 1M tokens). |
30
30
 
31
31
  Override one model without dropping the others by supplying a non-empty `billing.models` list; an empty or omitted list falls back to the published defaults.
package/README.zh.md CHANGED
@@ -16,7 +16,7 @@
16
16
  # baseURL: https://api.deepseek.com
17
17
  ```
18
18
 
19
- 插件注册 `billing` Remote,含三个方法:`getBalance()`(解析后的 `/user/balance` 快照)、`getSessionSpend(sessionId)`(单个会话的计费花费)与 `getTodaySpend()`(当前北京时间自然日内所有会话的计费花费合计)。会话花费把每条 `assistant/message` 事件的计费 token(缓存命中输入、含缓存写入的未命中输入、含推理的输出)按事件自身发生时刻(北京时间)所在的峰/谷单价计价,再按模型汇总。
19
+ 插件注册 `billing` Remote,含三个方法:`getBalance()`(解析后的 `/user/balance` 快照)、`getSessionSpend(sessionId)`(单个会话的计费花费)与 `getTodaySpend()`(当前北京时间自然日内所有会话的计费花费合计)。会话花费把每条 `assistant/message` 事件的计费 token(缓存命中输入、含缓存写入的未命中输入、含推理的输出)按事件自身发生时刻(北京时间)所在的峰/谷单价计价——高峰窗口仅周一至周五适用,周末全天按低谷价——再按模型汇总。
20
20
 
21
21
  ## 配置
22
22
 
@@ -25,7 +25,7 @@
25
25
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次调用时解析的凭据引用(环境变量)名。 |
26
26
  | `baseURL` | `$DEEPSEEK_BASE_URL`,其次 `https://api.deepseek.com` | 端点基础地址;会追加 `/user/balance`。 |
27
27
  | `models` | V4 Flash + V4 Pro + V4 Flash Vision Exp | 展示用的模型行,按展示顺序。 |
28
- | `billing.peakHours` | 09:00–12:00、14:00–18:00(北京) | 高峰时段窗口;其余时段为低谷。 |
28
+ | `billing.peakHours` | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 |
29
29
  | `billing.models` | 官方 V4 费率 | 每个模型的峰/谷单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token)。 |
30
30
 
31
31
  只想覆盖某个模型而不丢其它,就提供一个非空的 `billing.models` 列表;空或省略则回退到官方默认费率。
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import { LlmError, assertUsableApiKey } from "@deepseek-ai/dsh-llm";
3
3
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
4
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
5
5
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
- //#region lib/types/balance.js
6
+ //#region ../../../../DeepSeek-Harness-chat-billing/packages/llm-billing/lib/types/balance.js
7
7
  /**
8
8
  * DeepSeek account-balance capability: the `GET /user/balance` transport and
9
9
  * the Remote gateway that exposes one snapshot to trusted clients. The fetch
@@ -196,7 +196,8 @@ let DeepSeekBalanceGateway = (() => {
196
196
  return this.options.fetchBalance();
197
197
  }
198
198
  /**
199
- * Read one session's billed spend, priced per event by its peak/off-peak hour.
199
+ * Read one session's billed spend, priced per event by its Beijing-time
200
+ * peak/off-peak hour and weekday (weekends are always off-peak).
200
201
  * @param sessionId - the session whose spend to compute.
201
202
  * @returns the session's total cost plus one row per priced model.
202
203
  */
@@ -205,7 +206,7 @@ let DeepSeekBalanceGateway = (() => {
205
206
  }
206
207
  /**
207
208
  * Read today's billed spend across every session, priced per event by its
208
- * Beijing-time calendar day and peak/off-peak hour.
209
+ * Beijing-time calendar day, hour, and weekday (weekends are always off-peak).
209
210
  * @returns today's total cost plus one row per priced model.
210
211
  */
211
212
  getTodaySpend() {
@@ -214,7 +215,7 @@ let DeepSeekBalanceGateway = (() => {
214
215
  };
215
216
  })();
216
217
  //#endregion
217
- //#region lib/types/billing.js
218
+ //#region ../../../../DeepSeek-Harness-chat-billing/packages/llm-billing/lib/types/billing.js
218
219
  /**
219
220
  * DeepSeek billing: the peak/off-peak pricing table and the per-session spend
220
221
  * pricing. Pure functions over session events and the pricing table, so the
@@ -222,7 +223,11 @@ let DeepSeekBalanceGateway = (() => {
222
223
  * a key.
223
224
  * @module @rayadesu/dsh-llm-billing/billing
224
225
  */
225
- /** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
226
+ /**
227
+ * Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
228
+ * applied on weekdays (Monday–Friday) only — weekends are always off-peak
229
+ * (effective 2026-08-23).
230
+ */
226
231
  const DEFAULT_PEAK_HOURS = [{
227
232
  start: 9,
228
233
  end: 12
@@ -298,23 +303,35 @@ function resolveBilling(config) {
298
303
  function beijingHour(now) {
299
304
  return new Date(now.getTime() + 8 * 36e5).getUTCHours();
300
305
  }
306
+ /**
307
+ * The Beijing (Asia/Shanghai, UTC+8, no DST) weekday of a timestamp, as
308
+ * `getUTCDay()`: `0` is Sunday, `6` is Saturday.
309
+ */
310
+ function beijingWeekday(now) {
311
+ return new Date(now.getTime() + 8 * 36e5).getUTCDay();
312
+ }
301
313
  /** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
302
314
  function beijingDayKey(now) {
303
315
  return new Date(now.getTime() + 8 * 36e5).toISOString().slice(0, 10);
304
316
  }
305
317
  /**
306
- * Whether a timestamp falls inside any peak-hour window (Beijing time).
318
+ * Whether a timestamp falls inside any peak-hour window (Beijing time,
319
+ * weekdays Monday–Friday only). Weekends (Saturday and Sunday) are always
320
+ * off-peak, matching the published peak-hours rule.
307
321
  * @param billing - resolved pricing with peak-hour windows.
308
322
  * @param now - the moment to classify.
309
- * @returns true during peak hours.
323
+ * @returns true during a weekday peak hour.
310
324
  */
311
325
  function isPeak(billing, now) {
326
+ const weekday = beijingWeekday(now);
327
+ if (weekday === 0 || weekday === 6) return false;
312
328
  const hour = beijingHour(now);
313
329
  return billing.peakHours.some(({ start, end }) => hour >= start && hour < end);
314
330
  }
315
331
  /**
316
332
  * Price a set of billed events at the official per-model rates, applying the
317
- * peak/off-peak table per event by its Beijing-time hour. Each
333
+ * peak/off-peak table per event by its Beijing-time hour and weekday (peak
334
+ * windows apply Monday–Friday only; weekends are off-peak). Each
318
335
  * `assistant/message` event with usage contributes cache-hit input, cache-miss
319
336
  * input (uncached input plus cache writes), and output (reasoning included)
320
337
  * tokens at the rate of its own timestamp, with the three component costs
@@ -412,7 +429,7 @@ function computeTodaySpend(events, billing, catalog, now = /* @__PURE__ */ new D
412
429
  return priceEvents(events.filter((event) => beijingDayKey(new Date(event.time)) === day), billing, catalog);
413
430
  }
414
431
  //#endregion
415
- //#region lib/types/index.js
432
+ //#region ../../../../DeepSeek-Harness-chat-billing/packages/llm-billing/lib/types/index.js
416
433
  /**
417
434
  * DeepSeek account balance and session-spend provider, as a standalone host
418
435
  * plugin. It resolves the DeepSeek endpoint and API key from its own config and
package/lib/invariant.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region lib/types/invariant.js
1
+ //#region ../../../../DeepSeek-Harness-chat-billing/packages/llm-billing/lib/types/invariant.js
2
2
  /**
3
3
  * Package-owned invariant companion for `@rayadesu/dsh-llm-billing`.
4
4
  * @module @rayadesu/dsh-llm-billing/invariant
@@ -55,14 +55,15 @@ export declare class DeepSeekBalanceGateway extends TypertRemoteService {
55
55
  */
56
56
  getBalance(): Promise<DeepSeekBalance>;
57
57
  /**
58
- * Read one session's billed spend, priced per event by its peak/off-peak hour.
58
+ * Read one session's billed spend, priced per event by its Beijing-time
59
+ * peak/off-peak hour and weekday (weekends are always off-peak).
59
60
  * @param sessionId - the session whose spend to compute.
60
61
  * @returns the session's total cost plus one row per priced model.
61
62
  */
62
63
  getSessionSpend(sessionId: SessionId): Promise<DeepSeekSessionSpend>;
63
64
  /**
64
65
  * Read today's billed spend across every session, priced per event by its
65
- * Beijing-time calendar day and peak/off-peak hour.
66
+ * Beijing-time calendar day, hour, and weekday (weekends are always off-peak).
66
67
  * @returns today's total cost plus one row per priced model.
67
68
  */
68
69
  getTodaySpend(): Promise<DeepSeekTodaySpend>;
@@ -165,7 +165,8 @@ let DeepSeekBalanceGateway = (() => {
165
165
  return this.options.fetchBalance();
166
166
  }
167
167
  /**
168
- * Read one session's billed spend, priced per event by its peak/off-peak hour.
168
+ * Read one session's billed spend, priced per event by its Beijing-time
169
+ * peak/off-peak hour and weekday (weekends are always off-peak).
169
170
  * @param sessionId - the session whose spend to compute.
170
171
  * @returns the session's total cost plus one row per priced model.
171
172
  */
@@ -174,7 +175,7 @@ let DeepSeekBalanceGateway = (() => {
174
175
  }
175
176
  /**
176
177
  * Read today's billed spend across every session, priced per event by its
177
- * Beijing-time calendar day and peak/off-peak hour.
178
+ * Beijing-time calendar day, hour, and weekday (weekends are always off-peak).
178
179
  * @returns today's total cost plus one row per priced model.
179
180
  */
180
181
  getTodaySpend() {
@@ -32,7 +32,7 @@ export interface BillingConfigModel {
32
32
  /** Off-peak price. */
33
33
  offPeak: DeepSeekTokenPrice;
34
34
  }
35
- /** One peak-hour window on a 24h Beijing-time clock. */
35
+ /** One peak-hour window on a 24h Beijing-time clock, applied weekdays only. */
36
36
  export interface PeakHourWindow {
37
37
  /** Inclusive start hour, `0`–`23`. */
38
38
  start: number;
@@ -41,12 +41,19 @@ export interface PeakHourWindow {
41
41
  }
42
42
  /** Optional billing configuration; omission uses the published defaults. */
43
43
  export interface BillingConfig {
44
- /** Peak-hour windows in Beijing time. */
44
+ /**
45
+ * Peak-hour windows in Beijing time, applied on weekdays (Monday–Friday)
46
+ * only; weekends (Saturday and Sunday) are always off-peak.
47
+ */
45
48
  peakHours?: PeakHourWindow[];
46
- /** Per-model pricing rows; omission uses the V4 Flash and V4 Pro defaults. */
49
+ /** Per-model pricing rows; omission uses the V4 Flash, V4 Pro, and V4 Flash Vision defaults. */
47
50
  models?: BillingConfigModel[];
48
51
  }
49
- /** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
52
+ /**
53
+ * Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
54
+ * applied on weekdays (Monday–Friday) only — weekends are always off-peak
55
+ * (effective 2026-08-23).
56
+ */
50
57
  export declare const DEFAULT_PEAK_HOURS: {
51
58
  start: number;
52
59
  end: number;
@@ -72,10 +79,12 @@ export interface ResolvedBilling {
72
79
  */
73
80
  export declare function resolveBilling(config: BillingConfig | undefined): ResolvedBilling;
74
81
  /**
75
- * Whether a timestamp falls inside any peak-hour window (Beijing time).
82
+ * Whether a timestamp falls inside any peak-hour window (Beijing time,
83
+ * weekdays Monday–Friday only). Weekends (Saturday and Sunday) are always
84
+ * off-peak, matching the published peak-hours rule.
76
85
  * @param billing - resolved pricing with peak-hour windows.
77
86
  * @param now - the moment to classify.
78
- * @returns true during peak hours.
87
+ * @returns true during a weekday peak hour.
79
88
  */
80
89
  export declare function isPeak(billing: ResolvedBilling, now: Date): boolean;
81
90
  /**
@@ -5,7 +5,11 @@
5
5
  * a key.
6
6
  * @module @rayadesu/dsh-llm-billing/billing
7
7
  */
8
- /** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
8
+ /**
9
+ * Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
10
+ * applied on weekdays (Monday–Friday) only — weekends are always off-peak
11
+ * (effective 2026-08-23).
12
+ */
9
13
  export const DEFAULT_PEAK_HOURS = [
10
14
  { start: 9, end: 12 },
11
15
  { start: 14, end: 18 },
@@ -22,6 +26,13 @@ export const DEFAULT_MODEL_PRICING = [
22
26
  peak: { cacheHitInput: 0.30, cacheMissInput: 9.0, output: 27.0 },
23
27
  offPeak: { cacheHitInput: 0.15, cacheMissInput: 4.5, output: 13.5 },
24
28
  },
29
+ // deepseek-v4-flash-vision-exp bills at the same rates as deepseek-v4-flash;
30
+ // images are converted to tokens at the same per-token price.
31
+ {
32
+ model: 'deepseek-v4-flash-vision-exp',
33
+ peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
34
+ offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
35
+ },
25
36
  ];
26
37
  /**
27
38
  * Resolve optional configuration to a pricing table, defaulting omitted or
@@ -48,23 +59,36 @@ export function resolveBilling(config) {
48
59
  function beijingHour(now) {
49
60
  return new Date(now.getTime() + 8 * 3_600_000).getUTCHours();
50
61
  }
62
+ /**
63
+ * The Beijing (Asia/Shanghai, UTC+8, no DST) weekday of a timestamp, as
64
+ * `getUTCDay()`: `0` is Sunday, `6` is Saturday.
65
+ */
66
+ function beijingWeekday(now) {
67
+ return new Date(now.getTime() + 8 * 3_600_000).getUTCDay();
68
+ }
51
69
  /** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
52
70
  function beijingDayKey(now) {
53
71
  return new Date(now.getTime() + 8 * 3_600_000).toISOString().slice(0, 10);
54
72
  }
55
73
  /**
56
- * Whether a timestamp falls inside any peak-hour window (Beijing time).
74
+ * Whether a timestamp falls inside any peak-hour window (Beijing time,
75
+ * weekdays Monday–Friday only). Weekends (Saturday and Sunday) are always
76
+ * off-peak, matching the published peak-hours rule.
57
77
  * @param billing - resolved pricing with peak-hour windows.
58
78
  * @param now - the moment to classify.
59
- * @returns true during peak hours.
79
+ * @returns true during a weekday peak hour.
60
80
  */
61
81
  export function isPeak(billing, now) {
82
+ const weekday = beijingWeekday(now);
83
+ if (weekday === 0 || weekday === 6)
84
+ return false;
62
85
  const hour = beijingHour(now);
63
86
  return billing.peakHours.some(({ start, end }) => hour >= start && hour < end);
64
87
  }
65
88
  /**
66
89
  * Price a set of billed events at the official per-model rates, applying the
67
- * peak/off-peak table per event by its Beijing-time hour. Each
90
+ * peak/off-peak table per event by its Beijing-time hour and weekday (peak
91
+ * windows apply Monday–Friday only; weekends are off-peak). Each
68
92
  * `assistant/message` event with usage contributes cache-hit input, cache-miss
69
93
  * input (uncached input plus cache writes), and output (reasoning included)
70
94
  * tokens at the rate of its own timestamp, with the three component costs
@@ -34,9 +34,9 @@ export interface Config {
34
34
  apiKeyEnv?: string;
35
35
  /** Endpoint base; defaults to `$DEEPSEEK_BASE_URL`, then `https://api.deepseek.com`. */
36
36
  baseURL?: string;
37
- /** Advisory display rows, in presentation order; defaults to V4 Flash and V4 Pro. */
37
+ /** Advisory display rows, in presentation order; defaults to V4 Flash, V4 Pro, and V4 Flash Vision Exp. */
38
38
  models?: BillingModel[];
39
- /** Pricing table and peak-hour windows; omission uses the published defaults. */
39
+ /** Pricing table and peak-hour windows; omission uses the published defaults. Peak windows apply weekdays (Monday–Friday) only; weekends are always off-peak. */
40
40
  billing?: BillingConfig;
41
41
  }
42
42
  export declare const Config: z<Config>;
@@ -22,6 +22,7 @@ export const PUBLIC_BASE_URL = 'https://api.deepseek.com';
22
22
  const DEFAULT_MODELS = [
23
23
  { id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash' },
24
24
  { id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
25
+ { id: 'deepseek-v4-flash-vision-exp', name: 'DeepSeek-V4-Flash-Vision-Exp' },
25
26
  ];
26
27
  const billingModel = z.object({
27
28
  id: z.string().required(),
@@ -46,14 +46,14 @@ export interface DeepSeekSessionSpendModel {
46
46
  /** Billed cost of output tokens (reasoning included) in CNY. */
47
47
  outputCost: number;
48
48
  }
49
- /** The billed spend of one session, priced per event by its Beijing-time peak/off-peak hour. */
49
+ /** The billed spend of one session, priced per event by its Beijing-time hour and weekday (peak hours apply Monday–Friday only; weekends are off-peak). */
50
50
  export interface DeepSeekSessionSpend {
51
51
  /** Total billed cost in CNY across every priced model. */
52
52
  total: number;
53
53
  /** One row per model that reported usage AND has a pricing row; empty when the session has no priced usage. */
54
54
  models: readonly DeepSeekSessionSpendModel[];
55
55
  }
56
- /** The billed spend of every session on one Beijing-time calendar day, priced per event by its peak/off-peak hour. */
56
+ /** The billed spend of every session on one Beijing-time calendar day, priced per event by its Beijing-time hour and weekday (peak hours apply Monday–Friday only; weekends are off-peak). */
57
57
  export interface DeepSeekTodaySpend {
58
58
  /** Total billed cost in CNY across every priced model and every session. */
59
59
  total: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rayadesu/dsh-llm-billing",
3
3
  "description": "Standalone DeepSeek account-balance and session-spend provider exposed through the billing Remote",
4
- "version": "0.1.0-rc.8",
4
+ "version": "0.1.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -72,4 +72,4 @@
72
72
  "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8",
73
73
  "@deepseek-ai/cordis": "^4.0.1"
74
74
  }
75
- }
75
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 WilliamLIiii
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,71 +0,0 @@
1
- /**
2
- * DeepSeek account-balance capability: the `GET /user/balance` transport and
3
- * the Remote gateway that exposes one snapshot to trusted clients. The fetch
4
- * takes an already-resolved endpoint and bearer token so the registering
5
- * plugin stays the one owner of credential policy; the gateway carries only a
6
- * `fetchBalance` thunk for the same reason.
7
- * @module @rayadesu/dsh-llm-billing/balance
8
- */
9
- import type { Context } from '@deepseek-ai/cordis';
10
- import type { SessionId } from '@deepseek-ai/dsh-session';
11
- import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
12
- import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySpend } from './types.ts';
13
- /**
14
- * Parse and validate the DeepSeek balance response at the wire boundary.
15
- * @param body - decoded JSON response body.
16
- * @returns the validated, detached balance snapshot.
17
- * @throws {@link LlmError} with code `TRANSPORT` when the body is malformed.
18
- */
19
- export declare function parseDeepSeekBalance(body: unknown): DeepSeekBalance;
20
- /**
21
- * Fetch one account-balance snapshot from `{baseURL}/user/balance`.
22
- * @param baseURL - resolved endpoint base; `/user/balance` is appended.
23
- * @param apiKey - resolved bearer token for this endpoint.
24
- * @param signal - optional cancellation.
25
- * @returns the validated balance snapshot.
26
- * @throws {@link LlmError} for transport, HTTP, or malformed-response failures.
27
- */
28
- export declare function fetchDeepSeekBalance(baseURL: string, apiKey: string, signal?: AbortSignal): Promise<DeepSeekBalance>;
29
- /** Thunks the plugin binds to its own resolution and history access. */
30
- export interface DeepSeekBalanceGatewayOptions {
31
- /** Fetch one balance snapshot through the plugin's resolved facts. */
32
- fetchBalance: () => Promise<DeepSeekBalance>;
33
- /** Compute one session's billed spend through the plugin's resolved facts. */
34
- fetchSessionSpend: (sessionId: SessionId) => Promise<DeepSeekSessionSpend>;
35
- /** Compute today's billed spend across every session through the plugin's resolved facts. */
36
- fetchTodaySpend: () => Promise<DeepSeekTodaySpend>;
37
- }
38
- /**
39
- * Remote-only service exposing the DeepSeek account balance and session spend.
40
- * The plugin that owns connection, credential, and session-history resolution
41
- * constructs it with the matching thunks, so the Remote boundary never sees an
42
- * endpoint, key, or the session store.
43
- */
44
- export declare class DeepSeekBalanceGateway extends TypertRemoteService {
45
- private readonly options;
46
- /**
47
- * Register the balance Remote under the `billing` namespace.
48
- * @param ctx - owning plugin context.
49
- * @param options - balance and spend thunks bound to the plugin's facts.
50
- */
51
- constructor(ctx: Context, options: DeepSeekBalanceGatewayOptions);
52
- /**
53
- * Read the current DeepSeek account balance.
54
- * @returns the validated balance snapshot.
55
- */
56
- getBalance(): Promise<DeepSeekBalance>;
57
- /**
58
- * Read one session's billed spend, priced per event by its peak/off-peak hour.
59
- * @param sessionId - the session whose spend to compute.
60
- * @returns the session's total cost plus one row per priced model.
61
- */
62
- getSessionSpend(sessionId: SessionId): Promise<DeepSeekSessionSpend>;
63
- /**
64
- * Read today's billed spend across every session, priced per event by its
65
- * Beijing-time calendar day and peak/off-peak hour.
66
- * @returns today's total cost plus one row per priced model.
67
- */
68
- getTodaySpend(): Promise<DeepSeekTodaySpend>;
69
- }
70
- export default DeepSeekBalanceGateway;
71
- //# sourceMappingURL=balance.d.ts.map
@@ -1,187 +0,0 @@
1
- /**
2
- * DeepSeek account-balance capability: the `GET /user/balance` transport and
3
- * the Remote gateway that exposes one snapshot to trusted clients. The fetch
4
- * takes an already-resolved endpoint and bearer token so the registering
5
- * plugin stays the one owner of credential policy; the gateway carries only a
6
- * `fetchBalance` thunk for the same reason.
7
- * @module @rayadesu/dsh-llm-billing/balance
8
- */
9
- var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
10
- var useValue = arguments.length > 2;
11
- for (var i = 0; i < initializers.length; i++) {
12
- value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
13
- }
14
- return useValue ? value : void 0;
15
- };
16
- var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
17
- function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
18
- var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
19
- var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
20
- var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
21
- var _, done = false;
22
- for (var i = decorators.length - 1; i >= 0; i--) {
23
- var context = {};
24
- for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
25
- for (var p in contextIn.access) context.access[p] = contextIn.access[p];
26
- context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
27
- var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
28
- if (kind === "accessor") {
29
- if (result === void 0) continue;
30
- if (result === null || typeof result !== "object") throw new TypeError("Object expected");
31
- if (_ = accept(result.get)) descriptor.get = _;
32
- if (_ = accept(result.set)) descriptor.set = _;
33
- if (_ = accept(result.init)) initializers.unshift(_);
34
- }
35
- else if (_ = accept(result)) {
36
- if (kind === "field") initializers.unshift(_);
37
- else descriptor[key] = _;
38
- }
39
- }
40
- if (target) Object.defineProperty(target, contextIn.name, descriptor);
41
- done = true;
42
- };
43
- import { LlmError } from '@deepseek-ai/dsh-llm';
44
- import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
45
- /** Map a balance HTTP status to a stable LlmError code. */
46
- function httpErrorCode(status) {
47
- if (status === 401 || status === 403)
48
- return 'AUTH';
49
- if (status === 429)
50
- return 'RATE_LIMIT';
51
- if (status >= 500)
52
- return 'SERVER';
53
- return `HTTP_${status}`;
54
- }
55
- /**
56
- * Parse and validate the DeepSeek balance response at the wire boundary.
57
- * @param body - decoded JSON response body.
58
- * @returns the validated, detached balance snapshot.
59
- * @throws {@link LlmError} with code `TRANSPORT` when the body is malformed.
60
- */
61
- export function parseDeepSeekBalance(body) {
62
- if (typeof body !== 'object' || body === null || Array.isArray(body)) {
63
- throw new LlmError('DeepSeek balance response was not a JSON object', 'TRANSPORT');
64
- }
65
- const response = body;
66
- const isAvailable = response['is_available'];
67
- const infos = response['balance_infos'];
68
- if (typeof isAvailable !== 'boolean' || !Array.isArray(infos)) {
69
- throw new LlmError('DeepSeek balance response is missing is_available or balance_infos', 'TRANSPORT');
70
- }
71
- const lines = infos.map((info, index) => {
72
- if (typeof info !== 'object' || info === null || Array.isArray(info)) {
73
- throw new LlmError(`DeepSeek balance line ${index} is malformed`, 'TRANSPORT');
74
- }
75
- const line = info;
76
- const currency = line['currency'];
77
- const total = line['total_balance'];
78
- const granted = line['granted_balance'];
79
- const toppedUp = line['topped_up_balance'];
80
- if (typeof currency !== 'string' || currency.length === 0
81
- || typeof total !== 'string'
82
- || typeof granted !== 'string'
83
- || typeof toppedUp !== 'string') {
84
- throw new LlmError(`DeepSeek balance line ${index} has missing or invalid fields`, 'TRANSPORT');
85
- }
86
- return { currency, total, granted, toppedUp };
87
- });
88
- return { isAvailable, lines };
89
- }
90
- /**
91
- * Fetch one account-balance snapshot from `{baseURL}/user/balance`.
92
- * @param baseURL - resolved endpoint base; `/user/balance` is appended.
93
- * @param apiKey - resolved bearer token for this endpoint.
94
- * @param signal - optional cancellation.
95
- * @returns the validated balance snapshot.
96
- * @throws {@link LlmError} for transport, HTTP, or malformed-response failures.
97
- */
98
- export async function fetchDeepSeekBalance(baseURL, apiKey, signal) {
99
- let response;
100
- try {
101
- response = await fetch(`${baseURL}/user/balance`, {
102
- method: 'GET',
103
- headers: {
104
- 'authorization': `Bearer ${apiKey}`,
105
- 'accept': 'application/json',
106
- },
107
- ...(signal === undefined ? {} : { signal }),
108
- });
109
- }
110
- catch (error) {
111
- if (signal?.aborted)
112
- throw new LlmError('DeepSeek balance request aborted by caller', 'ABORTED', { cause: error });
113
- throw new LlmError(`DeepSeek balance request to ${baseURL} failed`, 'TRANSPORT', { cause: error });
114
- }
115
- if (!response.ok) {
116
- throw new LlmError(`DeepSeek balance request failed (HTTP ${response.status})`, httpErrorCode(response.status), { status: response.status });
117
- }
118
- let body;
119
- try {
120
- body = await response.json();
121
- }
122
- catch (error) {
123
- throw new LlmError('DeepSeek balance response was not valid JSON', 'TRANSPORT', { cause: error });
124
- }
125
- return parseDeepSeekBalance(body);
126
- }
127
- /**
128
- * Remote-only service exposing the DeepSeek account balance and session spend.
129
- * The plugin that owns connection, credential, and session-history resolution
130
- * constructs it with the matching thunks, so the Remote boundary never sees an
131
- * endpoint, key, or the session store.
132
- */
133
- let DeepSeekBalanceGateway = (() => {
134
- let _classSuper = TypertRemoteService;
135
- let _instanceExtraInitializers = [];
136
- let _getBalance_decorators;
137
- let _getSessionSpend_decorators;
138
- let _getTodaySpend_decorators;
139
- return class DeepSeekBalanceGateway extends _classSuper {
140
- static {
141
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
142
- _getBalance_decorators = [Remote('getBalance')];
143
- _getSessionSpend_decorators = [Remote('getSessionSpend')];
144
- _getTodaySpend_decorators = [Remote('getTodaySpend')];
145
- __esDecorate(this, null, _getBalance_decorators, { kind: "method", name: "getBalance", static: false, private: false, access: { has: obj => "getBalance" in obj, get: obj => obj.getBalance }, metadata: _metadata }, null, _instanceExtraInitializers);
146
- __esDecorate(this, null, _getSessionSpend_decorators, { kind: "method", name: "getSessionSpend", static: false, private: false, access: { has: obj => "getSessionSpend" in obj, get: obj => obj.getSessionSpend }, metadata: _metadata }, null, _instanceExtraInitializers);
147
- __esDecorate(this, null, _getTodaySpend_decorators, { kind: "method", name: "getTodaySpend", static: false, private: false, access: { has: obj => "getTodaySpend" in obj, get: obj => obj.getTodaySpend }, metadata: _metadata }, null, _instanceExtraInitializers);
148
- if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
149
- }
150
- options = __runInitializers(this, _instanceExtraInitializers);
151
- /**
152
- * Register the balance Remote under the `billing` namespace.
153
- * @param ctx - owning plugin context.
154
- * @param options - balance and spend thunks bound to the plugin's facts.
155
- */
156
- constructor(ctx, options) {
157
- super(ctx, 'billing');
158
- this.options = options;
159
- }
160
- /**
161
- * Read the current DeepSeek account balance.
162
- * @returns the validated balance snapshot.
163
- */
164
- getBalance() {
165
- return this.options.fetchBalance();
166
- }
167
- /**
168
- * Read one session's billed spend, priced per event by its peak/off-peak hour.
169
- * @param sessionId - the session whose spend to compute.
170
- * @returns the session's total cost plus one row per priced model.
171
- */
172
- getSessionSpend(sessionId) {
173
- return this.options.fetchSessionSpend(sessionId);
174
- }
175
- /**
176
- * Read today's billed spend across every session, priced per event by its
177
- * Beijing-time calendar day and peak/off-peak hour.
178
- * @returns today's total cost plus one row per priced model.
179
- */
180
- getTodaySpend() {
181
- return this.options.fetchTodaySpend();
182
- }
183
- };
184
- })();
185
- export { DeepSeekBalanceGateway };
186
- export default DeepSeekBalanceGateway;
187
- //# sourceMappingURL=balance.js.map
@@ -1,106 +0,0 @@
1
- /**
2
- * DeepSeek billing: the peak/off-peak pricing table and the per-session spend
3
- * pricing. Pure functions over session events and the pricing table, so the
4
- * Remote gateway stays transport-free and the whole spend is testable without
5
- * a key.
6
- * @module @rayadesu/dsh-llm-billing/billing
7
- */
8
- import type { SessionEvent } from '@deepseek-ai/dsh-session';
9
- import type { DeepSeekSessionSpend, DeepSeekTodaySpend } from './types.ts';
10
- /** One token price point, in CNY per 1M tokens. */
11
- export interface DeepSeekTokenPrice {
12
- /** 1M input cache-hit tokens. */
13
- cacheHitInput: number;
14
- /** 1M input cache-miss tokens (cache writes bill at this rate too). */
15
- cacheMissInput: number;
16
- /** 1M output tokens. */
17
- output: number;
18
- }
19
- /** Peak and off-peak price pair for one model. */
20
- export interface DeepSeekModelPricing {
21
- /** Price during peak hours. */
22
- peak: DeepSeekTokenPrice;
23
- /** Price during off-peak hours. */
24
- offPeak: DeepSeekTokenPrice;
25
- }
26
- /** One model's pricing-table row in configuration form. */
27
- export interface BillingConfigModel {
28
- /** Wire model id. */
29
- model: string;
30
- /** Peak-hour price. */
31
- peak: DeepSeekTokenPrice;
32
- /** Off-peak price. */
33
- offPeak: DeepSeekTokenPrice;
34
- }
35
- /** One peak-hour window on a 24h Beijing-time clock. */
36
- export interface PeakHourWindow {
37
- /** Inclusive start hour, `0`–`23`. */
38
- start: number;
39
- /** Exclusive end hour, `1`–`24`. */
40
- end: number;
41
- }
42
- /** Optional billing configuration; omission uses the published defaults. */
43
- export interface BillingConfig {
44
- /** Peak-hour windows in Beijing time. */
45
- peakHours?: PeakHourWindow[];
46
- /** Per-model pricing rows; omission uses the V4 Flash, V4 Pro, and V4 Flash Vision defaults. */
47
- models?: BillingConfigModel[];
48
- }
49
- /** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
50
- export declare const DEFAULT_PEAK_HOURS: {
51
- start: number;
52
- end: number;
53
- }[];
54
- /** Official peak/off-peak rates (CNY per 1M tokens), effective 2026-08-17. */
55
- export declare const DEFAULT_MODEL_PRICING: BillingConfigModel[];
56
- /** Resolved billing configuration: a pricing table plus peak-hour windows. */
57
- export interface ResolvedBilling {
58
- peakHours: readonly {
59
- start: number;
60
- end: number;
61
- }[];
62
- models: ReadonlyMap<string, DeepSeekModelPricing>;
63
- }
64
- /**
65
- * Resolve optional configuration to a pricing table, defaulting omitted or
66
- * empty rows to the published rates. Schemastery materializes an absent
67
- * `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
68
- * selects the defaults. Explicit non-empty rows override the same model; a
69
- * supplied non-empty `models` list is authoritative.
70
- * @param config - optional raw billing configuration.
71
- * @returns the resolved table and peak-hour windows.
72
- */
73
- export declare function resolveBilling(config: BillingConfig | undefined): ResolvedBilling;
74
- /**
75
- * Whether a timestamp falls inside any peak-hour window (Beijing time).
76
- * @param billing - resolved pricing with peak-hour windows.
77
- * @param now - the moment to classify.
78
- * @returns true during peak hours.
79
- */
80
- export declare function isPeak(billing: ResolvedBilling, now: Date): boolean;
81
- /**
82
- * Price one session's complete event log at the official per-model rates.
83
- * @param events - one session's complete event log.
84
- * @param billing - resolved pricing with peak-hour windows.
85
- * @param catalog - model display rows, in presentation order.
86
- * @returns the session's total cost plus one row per priced model.
87
- */
88
- export declare function computeSessionSpend(events: readonly SessionEvent[], billing: ResolvedBilling, catalog: readonly {
89
- id: string;
90
- name: string;
91
- }[]): DeepSeekSessionSpend;
92
- /**
93
- * Price every event whose Beijing-time calendar day is the day of `now`,
94
- * aggregating across every session's event log. Events from other Beijing
95
- * days are ignored, so a caller passes the concatenated logs of all sessions.
96
- * @param events - every session's complete event log, concatenated.
97
- * @param billing - resolved pricing with peak-hour windows.
98
- * @param catalog - model display rows, in presentation order.
99
- * @param now - the reference moment whose Beijing-time calendar day is "today".
100
- * @returns today's total cost plus one row per priced model.
101
- */
102
- export declare function computeTodaySpend(events: readonly SessionEvent[], billing: ResolvedBilling, catalog: readonly {
103
- id: string;
104
- name: string;
105
- }[], now?: Date): DeepSeekTodaySpend;
106
- //# sourceMappingURL=billing.d.ts.map
@@ -1,170 +0,0 @@
1
- /**
2
- * DeepSeek billing: the peak/off-peak pricing table and the per-session spend
3
- * pricing. Pure functions over session events and the pricing table, so the
4
- * Remote gateway stays transport-free and the whole spend is testable without
5
- * a key.
6
- * @module @rayadesu/dsh-llm-billing/billing
7
- */
8
- /** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
9
- export const DEFAULT_PEAK_HOURS = [
10
- { start: 9, end: 12 },
11
- { start: 14, end: 18 },
12
- ];
13
- /** Official peak/off-peak rates (CNY per 1M tokens), effective 2026-08-17. */
14
- export const DEFAULT_MODEL_PRICING = [
15
- {
16
- model: 'deepseek-v4-flash',
17
- peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
18
- offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
19
- },
20
- {
21
- model: 'deepseek-v4-pro',
22
- peak: { cacheHitInput: 0.30, cacheMissInput: 9.0, output: 27.0 },
23
- offPeak: { cacheHitInput: 0.15, cacheMissInput: 4.5, output: 13.5 },
24
- },
25
- // deepseek-v4-flash-vision-exp bills at the same rates as deepseek-v4-flash;
26
- // images are converted to tokens at the same per-token price.
27
- {
28
- model: 'deepseek-v4-flash-vision-exp',
29
- peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
30
- offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
31
- },
32
- ];
33
- /**
34
- * Resolve optional configuration to a pricing table, defaulting omitted or
35
- * empty rows to the published rates. Schemastery materializes an absent
36
- * `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
37
- * selects the defaults. Explicit non-empty rows override the same model; a
38
- * supplied non-empty `models` list is authoritative.
39
- * @param config - optional raw billing configuration.
40
- * @returns the resolved table and peak-hour windows.
41
- */
42
- export function resolveBilling(config) {
43
- const peakHours = config?.peakHours !== undefined && config.peakHours.length > 0
44
- ? config.peakHours
45
- : DEFAULT_PEAK_HOURS;
46
- const rows = config?.models !== undefined && config.models.length > 0
47
- ? config.models
48
- : DEFAULT_MODEL_PRICING;
49
- const models = new Map();
50
- for (const row of rows)
51
- models.set(row.model, { peak: row.peak, offPeak: row.offPeak });
52
- return { peakHours, models };
53
- }
54
- /** The Beijing (Asia/Shanghai, UTC+8, no DST) hour of a timestamp. */
55
- function beijingHour(now) {
56
- return new Date(now.getTime() + 8 * 3_600_000).getUTCHours();
57
- }
58
- /** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
59
- function beijingDayKey(now) {
60
- return new Date(now.getTime() + 8 * 3_600_000).toISOString().slice(0, 10);
61
- }
62
- /**
63
- * Whether a timestamp falls inside any peak-hour window (Beijing time).
64
- * @param billing - resolved pricing with peak-hour windows.
65
- * @param now - the moment to classify.
66
- * @returns true during peak hours.
67
- */
68
- export function isPeak(billing, now) {
69
- const hour = beijingHour(now);
70
- return billing.peakHours.some(({ start, end }) => hour >= start && hour < end);
71
- }
72
- /**
73
- * Price a set of billed events at the official per-model rates, applying the
74
- * peak/off-peak table per event by its Beijing-time hour. Each
75
- * `assistant/message` event with usage contributes cache-hit input, cache-miss
76
- * input (uncached input plus cache writes), and output (reasoning included)
77
- * tokens at the rate of its own timestamp, with the three component costs
78
- * carried separately; a model with usage but no pricing row is omitted (the
79
- * published table prices only the two V4 rows).
80
- * @param events - the events to price.
81
- * @param billing - resolved pricing with peak-hour windows.
82
- * @param catalog - model display rows, in presentation order.
83
- * @returns the total cost plus one row per priced model.
84
- */
85
- function priceEvents(events, billing, catalog) {
86
- const names = new Map(catalog.map(model => [model.id, model.name]));
87
- const rows = new Map();
88
- for (const event of events) {
89
- if (event.type !== 'assistant/message')
90
- continue;
91
- const reported = event.data.usage;
92
- if (reported === undefined)
93
- continue;
94
- const model = event.data.message.source.model;
95
- const pricing = billing.models.get(model);
96
- if (pricing === undefined)
97
- continue;
98
- const peak = isPeak(billing, new Date(event.time));
99
- const price = peak ? pricing.peak : pricing.offPeak;
100
- const hit = reported.cacheReadTokens ?? 0;
101
- const miss = reported.inputTokens + (reported.cacheWriteTokens ?? 0);
102
- const output = reported.outputTokens;
103
- const hitCost = (hit * price.cacheHitInput) / 1_000_000;
104
- const missCost = (miss * price.cacheMissInput) / 1_000_000;
105
- const outputCost = (output * price.output) / 1_000_000;
106
- const cost = hitCost + missCost + outputCost;
107
- let row = rows.get(model);
108
- if (row === undefined) {
109
- row = {
110
- cacheHitInputTokens: 0, cacheMissInputTokens: 0, outputTokens: 0,
111
- cost: 0, peakCost: 0, offPeakCost: 0,
112
- cacheHitInputCost: 0, cacheMissInputCost: 0, outputCost: 0,
113
- };
114
- rows.set(model, row);
115
- }
116
- row.cacheHitInputTokens += hit;
117
- row.cacheMissInputTokens += miss;
118
- row.outputTokens += output;
119
- row.cost += cost;
120
- row.cacheHitInputCost += hitCost;
121
- row.cacheMissInputCost += missCost;
122
- row.outputCost += outputCost;
123
- if (peak)
124
- row.peakCost += cost;
125
- else
126
- row.offPeakCost += cost;
127
- }
128
- const models = [...rows.entries()].map(([model, row]) => ({
129
- model,
130
- displayName: names.get(model) ?? model,
131
- cost: row.cost,
132
- peakCost: row.peakCost,
133
- offPeakCost: row.offPeakCost,
134
- cacheHitInputTokens: row.cacheHitInputTokens,
135
- cacheMissInputTokens: row.cacheMissInputTokens,
136
- outputTokens: row.outputTokens,
137
- cacheHitInputCost: row.cacheHitInputCost,
138
- cacheMissInputCost: row.cacheMissInputCost,
139
- outputCost: row.outputCost,
140
- }));
141
- return {
142
- total: models.reduce((sum, model) => sum + model.cost, 0),
143
- models,
144
- };
145
- }
146
- /**
147
- * Price one session's complete event log at the official per-model rates.
148
- * @param events - one session's complete event log.
149
- * @param billing - resolved pricing with peak-hour windows.
150
- * @param catalog - model display rows, in presentation order.
151
- * @returns the session's total cost plus one row per priced model.
152
- */
153
- export function computeSessionSpend(events, billing, catalog) {
154
- return priceEvents(events, billing, catalog);
155
- }
156
- /**
157
- * Price every event whose Beijing-time calendar day is the day of `now`,
158
- * aggregating across every session's event log. Events from other Beijing
159
- * days are ignored, so a caller passes the concatenated logs of all sessions.
160
- * @param events - every session's complete event log, concatenated.
161
- * @param billing - resolved pricing with peak-hour windows.
162
- * @param catalog - model display rows, in presentation order.
163
- * @param now - the reference moment whose Beijing-time calendar day is "today".
164
- * @returns today's total cost plus one row per priced model.
165
- */
166
- export function computeTodaySpend(events, billing, catalog, now = new Date()) {
167
- const day = beijingDayKey(now);
168
- return priceEvents(events.filter(event => beijingDayKey(new Date(event.time)) === day), billing, catalog);
169
- }
170
- //# sourceMappingURL=billing.js.map
@@ -1,49 +0,0 @@
1
- /**
2
- * DeepSeek account balance and session-spend provider, as a standalone host
3
- * plugin. It resolves the DeepSeek endpoint and API key from its own config and
4
- * the credential/environment seams, prices each session's billed usage with the
5
- * peak/off-peak table, and exposes the `billing` Remote (`getBalance`, the
6
- * per-session `getSessionSpend`, and the all-sessions `getTodaySpend`).
7
- * @module @rayadesu/dsh-llm-billing
8
- */
9
- import type { Context } from '@deepseek-ai/cordis';
10
- import z from '@deepseek-ai/schemastery';
11
- import type { BillingConfig } from './billing.ts';
12
- export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from './balance.ts';
13
- export { computeSessionSpend, computeTodaySpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, isPeak, resolveBilling, } from './billing.ts';
14
- export type { BillingConfig, BillingConfigModel, DeepSeekModelPricing, DeepSeekTokenPrice, PeakHourWindow, ResolvedBilling, } from './billing.ts';
15
- export type * from './types.ts';
16
- export declare const name = "llm-billing";
17
- /** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
18
- export declare const PUBLIC_BASE_URL = "https://api.deepseek.com";
19
- /** One advisory display row; requests are never restricted to this list. */
20
- export interface BillingModel {
21
- /** Wire model id, e.g. `deepseek-v4-flash`. */
22
- id: string;
23
- /** Selector label; defaults to {@link id}. */
24
- name?: string;
25
- }
26
- /**
27
- * Plugin config. Every field is optional: the API key resolves per call from
28
- * {@link Config.apiKeyEnv} (credentials seam, then environment), the endpoint
29
- * falls back to `$DEEPSEEK_BASE_URL` then the public API, and the pricing
30
- * table and peak-hour windows fall back to the published DeepSeek rates.
31
- */
32
- export interface Config {
33
- /** Credential reference (environment-variable name); defaults to `DEEPSEEK_API_KEY`. */
34
- apiKeyEnv?: string;
35
- /** Endpoint base; defaults to `$DEEPSEEK_BASE_URL`, then `https://api.deepseek.com`. */
36
- baseURL?: string;
37
- /** Advisory display rows, in presentation order; defaults to V4 Flash, V4 Pro, and V4 Flash Vision Exp. */
38
- models?: BillingModel[];
39
- /** Pricing table and peak-hour windows; omission uses the published defaults. */
40
- billing?: BillingConfig;
41
- }
42
- export declare const Config: z<Config>;
43
- /**
44
- * Register the `billing` Remote under the `billing` namespace.
45
- * @param ctx - owning plugin context.
46
- * @param config - validated plugin config.
47
- */
48
- export declare function apply(ctx: Context, config: Config): void;
49
- //# sourceMappingURL=index.d.ts.map
@@ -1,156 +0,0 @@
1
- /**
2
- * DeepSeek account balance and session-spend provider, as a standalone host
3
- * plugin. It resolves the DeepSeek endpoint and API key from its own config and
4
- * the credential/environment seams, prices each session's billed usage with the
5
- * peak/off-peak table, and exposes the `billing` Remote (`getBalance`, the
6
- * per-session `getSessionSpend`, and the all-sessions `getTodaySpend`).
7
- * @module @rayadesu/dsh-llm-billing
8
- */
9
- import z from '@deepseek-ai/schemastery';
10
- import { assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm';
11
- import { credentialRef } from '@deepseek-ai/dsh-credentials';
12
- import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
13
- import { DeepSeekBalanceGateway, fetchDeepSeekBalance } from "./balance.js";
14
- import { computeSessionSpend, computeTodaySpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, resolveBilling, } from "./billing.js";
15
- export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from "./balance.js";
16
- export { computeSessionSpend, computeTodaySpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, isPeak, resolveBilling, } from "./billing.js";
17
- export const name = 'llm-billing';
18
- const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY';
19
- const BASE_URL_ENV = 'DEEPSEEK_BASE_URL';
20
- /** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
21
- export const PUBLIC_BASE_URL = 'https://api.deepseek.com';
22
- const DEFAULT_MODELS = [
23
- { id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash' },
24
- { id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
25
- { id: 'deepseek-v4-flash-vision-exp', name: 'DeepSeek-V4-Flash-Vision-Exp' },
26
- ];
27
- const billingModel = z.object({
28
- id: z.string().required(),
29
- name: z.string(),
30
- });
31
- const tokenPrice = z.object({
32
- cacheHitInput: z.number().min(0),
33
- cacheMissInput: z.number().min(0),
34
- output: z.number().min(0),
35
- });
36
- const billingConfig = z.object({
37
- peakHours: z.array(z.object({
38
- start: z.number().step(1).min(0).max(23),
39
- end: z.number().step(1).min(0).max(24),
40
- })).default(DEFAULT_PEAK_HOURS),
41
- models: z.array(z.object({
42
- model: z.string().required(),
43
- peak: tokenPrice,
44
- offPeak: tokenPrice,
45
- })).default(DEFAULT_MODEL_PRICING),
46
- });
47
- export const Config = z.object({
48
- apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),
49
- baseURL: z.string(),
50
- models: z.array(billingModel).default(DEFAULT_MODELS),
51
- billing: billingConfig,
52
- });
53
- /**
54
- * Read one session's event log: the live SessionStore first, then the
55
- * persistence backend for a flushed session.
56
- * @param ctx - plugin context carrying the SessionStore and optional persistence.
57
- * @param sessionId - the session to read.
58
- * @returns the session's complete event log.
59
- * @throws {@link LlmError} with code `NOT_FOUND` when the session is unknown.
60
- */
61
- async function sessionEvents(ctx, sessionId) {
62
- const sessions = ctx.get('sessions');
63
- const live = sessions?.get(sessionId);
64
- if (live !== undefined)
65
- return live.events;
66
- const persistence = ctx.get('sessionPersistence');
67
- if (persistence !== undefined) {
68
- for (const header of await persistence.list()) {
69
- if (header.id !== sessionId)
70
- continue;
71
- const inspection = await persistence.inspect(sessionId);
72
- return inspection.events;
73
- }
74
- }
75
- throw new LlmError(`llm-billing: session ${sessionId} not found`, 'NOT_FOUND');
76
- }
77
- /**
78
- * Read every session's event log, concatenated: each live SessionStore
79
- * session first (its log may hold events not yet flushed), then each persisted
80
- * session that is not live, so no event is counted twice. Events are appended
81
- * one at a time: spreading a very large log into `push(...)` exceeds the
82
- * engine's argument limit and throws a stack RangeError.
83
- * @param ctx - plugin context carrying the SessionStore and optional persistence.
84
- * @returns every session's complete event log, concatenated.
85
- */
86
- async function allSessionEvents(ctx) {
87
- const events = [];
88
- const sessions = ctx.get('sessions');
89
- const liveIds = new Set();
90
- if (sessions !== undefined) {
91
- for (const session of sessions.list()) {
92
- liveIds.add(session.id);
93
- for (const event of session.events)
94
- events.push(event);
95
- }
96
- }
97
- const persistence = ctx.get('sessionPersistence');
98
- if (persistence !== undefined) {
99
- for (const header of await persistence.list()) {
100
- if (liveIds.has(header.id))
101
- continue;
102
- try {
103
- const inspection = await persistence.inspect(header.id);
104
- for (const event of inspection.events)
105
- events.push(event);
106
- }
107
- catch (error) {
108
- // One unreadable session must not blank the whole-day aggregate.
109
- ctx.logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
110
- }
111
- }
112
- }
113
- return events;
114
- }
115
- /**
116
- * Register the `billing` Remote under the `billing` namespace.
117
- * @param ctx - owning plugin context.
118
- * @param config - validated plugin config.
119
- */
120
- export function apply(ctx, config) {
121
- const baseURL = () => config.baseURL
122
- ?? launchEnvironmentOf(ctx).get(BASE_URL_ENV)?.value
123
- ?? PUBLIC_BASE_URL;
124
- const apiKeyRef = credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV);
125
- const resolveApiKey = async () => {
126
- const credentials = ctx.get('credentials');
127
- if (credentials !== undefined) {
128
- const hit = await credentials.resolve(apiKeyRef);
129
- if (hit !== undefined)
130
- return assertUsableApiKey(hit.value, 'llm-billing', apiKeyRef);
131
- }
132
- else {
133
- const ambient = launchEnvironmentOf(ctx).get(apiKeyRef);
134
- if (ambient !== undefined && ambient.value.length > 0) {
135
- return assertUsableApiKey(ambient.value, 'llm-billing', apiKeyRef);
136
- }
137
- }
138
- throw new LlmError(`llm-billing: no API key; store ${apiKeyRef} through the credentials service or export it`, 'MISSING_CREDENTIAL');
139
- };
140
- const fetchBalance = async () => {
141
- const apiKey = await resolveApiKey();
142
- return fetchDeepSeekBalance(baseURL(), apiKey);
143
- };
144
- const fetchSessionSpend = async (sessionId) => {
145
- const billing = resolveBilling(config.billing);
146
- const catalog = (config.models ?? DEFAULT_MODELS).map(model => ({ id: model.id, name: model.name ?? model.id }));
147
- return computeSessionSpend(await sessionEvents(ctx, sessionId), billing, catalog);
148
- };
149
- const fetchTodaySpend = async () => {
150
- const billing = resolveBilling(config.billing);
151
- const catalog = (config.models ?? DEFAULT_MODELS).map(model => ({ id: model.id, name: model.name ?? model.id }));
152
- return computeTodaySpend(await allSessionEvents(ctx), billing, catalog);
153
- };
154
- new DeepSeekBalanceGateway(ctx, { fetchBalance, fetchSessionSpend, fetchTodaySpend });
155
- }
156
- //# sourceMappingURL=index.js.map
@@ -1,16 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@rayadesu/dsh-llm-billing`.
3
- * @module @rayadesu/dsh-llm-billing/invariant
4
- */
5
- import type { Context } from '@deepseek-ai/cordis';
6
- /** Cordis companion plugin name. */
7
- export declare const name = "llm-billing-invariant";
8
- /** Service required before the companion can reserve package ownership. */
9
- export declare const inject: string[];
10
- /**
11
- * Register this package's invariant companion.
12
- * @param ctx - Cordis context carrying the invariant service.
13
- * @returns the installed registration's disposer after setup succeeds.
14
- */
15
- export declare const apply: (ctx: Context) => Promise<() => void>;
16
- //# sourceMappingURL=invariant.d.ts.map
@@ -1,22 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@rayadesu/dsh-llm-billing`.
3
- * @module @rayadesu/dsh-llm-billing/invariant
4
- */
5
- const PACKAGE_NAME = '@rayadesu/dsh-llm-billing';
6
- /** Cordis companion plugin name. */
7
- export const name = 'llm-billing-invariant';
8
- /** Service required before the companion can reserve package ownership. */
9
- export const inject = ['invariants'];
10
- /**
11
- * No runtime invariant: this package is a read-only Remote projection over the
12
- * provider balance and session logs, and owns no cross-plugin mutable state.
13
- */
14
- const install = () => { };
15
- /**
16
- * Register this package's invariant companion.
17
- * @param ctx - Cordis context carrying the invariant service.
18
- * @returns the installed registration's disposer after setup succeeds.
19
- */
20
- export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
21
- /* jscpd:ignore-end */
22
- //# sourceMappingURL=invariant.js.map
@@ -1,63 +0,0 @@
1
- /**
2
- * Client-safe balance and spend vocabulary shared by the `billing` Remote,
3
- * its generated artifacts, and the web UI.
4
- * @module @rayadesu/dsh-llm-billing/types
5
- */
6
- /** One currency line of the account balance returned by `GET /user/balance`. */
7
- export interface DeepSeekBalanceLine {
8
- /** Currency code, e.g. `CNY` or `USD`. */
9
- currency: string;
10
- /** Total available balance (granted plus topped up). */
11
- total: string;
12
- /** Non-expired granted (free) balance. */
13
- granted: string;
14
- /** Topped-up (paid) balance. */
15
- toppedUp: string;
16
- }
17
- /** DeepSeek account balance returned by `GET /user/balance`. */
18
- export interface DeepSeekBalance {
19
- /** Whether the account has any balance available for API calls. */
20
- isAvailable: boolean;
21
- /** Balance lines, one per currency; empty when the provider reports none. */
22
- lines: readonly DeepSeekBalanceLine[];
23
- }
24
- /** One model's billed spend within one session, priced by the peak/off-peak table. */
25
- export interface DeepSeekSessionSpendModel {
26
- /** Wire model id, e.g. `deepseek-v4-flash`. */
27
- model: string;
28
- /** Selector label, e.g. `DeepSeek-V4-Flash`. */
29
- displayName: string;
30
- /** Billed cost in CNY (peak plus off-peak portions). */
31
- cost: number;
32
- /** Cost portion billed at peak rates. */
33
- peakCost: number;
34
- /** Cost portion billed at off-peak rates. */
35
- offPeakCost: number;
36
- /** Cache-hit input tokens billed at the hit rate. */
37
- cacheHitInputTokens: number;
38
- /** Cache-miss input tokens (uncached input plus cache writes), billed at the miss rate. */
39
- cacheMissInputTokens: number;
40
- /** Output tokens (reasoning included), billed at the output rate. */
41
- outputTokens: number;
42
- /** Billed cost of cache-hit input tokens in CNY. */
43
- cacheHitInputCost: number;
44
- /** Billed cost of cache-miss input tokens (uncached input plus cache writes) in CNY. */
45
- cacheMissInputCost: number;
46
- /** Billed cost of output tokens (reasoning included) in CNY. */
47
- outputCost: number;
48
- }
49
- /** The billed spend of one session, priced per event by its Beijing-time peak/off-peak hour. */
50
- export interface DeepSeekSessionSpend {
51
- /** Total billed cost in CNY across every priced model. */
52
- total: number;
53
- /** One row per model that reported usage AND has a pricing row; empty when the session has no priced usage. */
54
- models: readonly DeepSeekSessionSpendModel[];
55
- }
56
- /** The billed spend of every session on one Beijing-time calendar day, priced per event by its peak/off-peak hour. */
57
- export interface DeepSeekTodaySpend {
58
- /** Total billed cost in CNY across every priced model and every session. */
59
- total: number;
60
- /** One row per model that reported usage AND has a pricing row; empty when today has no priced usage. */
61
- models: readonly DeepSeekSessionSpendModel[];
62
- }
63
- //# sourceMappingURL=types.d.ts.map
@@ -1,7 +0,0 @@
1
- /**
2
- * Client-safe balance and spend vocabulary shared by the `billing` Remote,
3
- * its generated artifacts, and the web UI.
4
- * @module @rayadesu/dsh-llm-billing/types
5
- */
6
- export {};
7
- //# sourceMappingURL=types.js.map