@rayadesu/dsh-llm-billing 0.3.9 → 0.3.11
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 +2 -2
- package/README.md +13 -7
- package/README.zh.md +13 -7
- package/lib/index.js +178 -54
- package/lib/types/billing.d.ts +73 -13
- package/lib/types/billing.js +118 -26
- package/lib/types/index.d.ts +8 -3
- package/lib/types/index.js +19 -6
- package/lib/types/projection.d.ts +9 -7
- package/lib/types/projection.js +16 -12
- package/lib/types/types.d.ts +6 -2
- package/lib/types/types.js +6 -2
- package/package.json +1 -1
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 2c416fb7f45464abd06484bffb3db61ccea3a47b
|
|
6
|
+
README.zh.md: 2cec64178289c1fb2e6081e3fd7af59dfb0e0613
|
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 six methods: `getBalance(force?)` (the parsed `/user/balance` snapshot; a snapshot younger than the 15-second host TTL is reused, `force` bypasses it, and each request aborts after 5 seconds), `getSessionSpend(sessionId)` (one session's billed cost), `getTodaySpend(force?)` (every session's billed cost on the current Beijing-time calendar day; `force` bypasses the host-side cache, for the badge's manual refresh), `getTodaySessionsSpend(force?)` (today's billed cost per session, sorted by cost descending, each row carrying the session's durable title), `getTurnSpend(sessionId, messageId)` (one completed turn's billed cost, located by its closing assistant message id), and `getSessionTurnSpends(sessionId)` (every completed turn's cost as a `messageId → total` map, folded in one pass — the transcript renders one row per message, so the client fetches this once per session instead of calling `getTurnSpend` per row). The spend prices each provider-reported usage sample — an `assistant/message`'s usage, or a failed/retried `assistant/attempt`'s stream usage, priced with the model of the latest `request/header` — at the official rate
|
|
19
|
+
The plugin registers the `billing` Remote with six methods: `getBalance(force?)` (the parsed `/user/balance` snapshot; a snapshot younger than the 15-second host TTL is reused, `force` bypasses it, and each request aborts after 5 seconds), `getSessionSpend(sessionId)` (one session's billed cost), `getTodaySpend(force?)` (every session's billed cost on the current Beijing-time calendar day; `force` bypasses the host-side cache, for the badge's manual refresh), `getTodaySessionsSpend(force?)` (today's billed cost per session, sorted by cost descending, each row carrying the session's durable title), `getTurnSpend(sessionId, messageId)` (one completed turn's billed cost, located by its closing assistant message id), and `getSessionTurnSpends(sessionId)` (every completed turn's cost as a `messageId → total` map, folded in one pass — the transcript renders one row per message, so the client fetches this once per session instead of calling `getTurnSpend` per row). The spend prices each provider-reported usage sample — an `assistant/message`'s usage, or a failed/retried `assistant/attempt`'s stream usage, priced with the model of the latest `request/header` — at the official rate revision in effect at the sample's own timestamp, classified peak/off-peak by its own Beijing-time hour. Peak windows apply weekdays (Monday–Friday) only, and weekends are always off-peak. A sample for the same `(turn, step)` replaces the earlier one and `llm/retry-started` makes the retried attempt add, matching DSH's own turn-usage disclosure; costs then sum per model. A turn is the `turn/start`..`turn/end` range holding the closing message; the ranking folds each session's title from its latest `session/title` event (last-wins, so a rename is reflected as soon as its event commits and the session is re-read).
|
|
20
20
|
|
|
21
21
|
### Today-spend read path (no full scans per message)
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ The plugin registers the `billing` Remote with six methods: `getBalance(force?)`
|
|
|
27
27
|
|
|
28
28
|
After the first resolution per process, steady-state reads cost only the sessions whose logs actually changed. A session whose log cannot be read is skipped with a warning (and remembered) instead of failing the whole day's total.
|
|
29
29
|
|
|
30
|
-
Note: the projection path prices a session's history once, at the rates in effect when its events were folded
|
|
30
|
+
Note: the projection path prices a session's history once, at the rates in effect when its events were folded. Published rate revisions travel inside the pricing closure and are resolved per sample timestamp, so a re-priced series bills its own history correctly however late a log is folded; only a configuration change (editing `billing.models`) re-prices just the events folded afterwards (the events path re-prices the whole log), and the unit's `stateVersion` is bumped whenever that resolution changes so checkpointed rows are refolded instead of kept.
|
|
31
31
|
|
|
32
32
|
## Forked sessions
|
|
33
33
|
|
|
@@ -47,11 +47,17 @@ The projection-cache reader likewise targets the current seam: `cachedSnapshot(h
|
|
|
47
47
|
| --- | --- | --- |
|
|
48
48
|
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential-reference (environment-variable) name resolved per call. |
|
|
49
49
|
| `baseURL` | `$DEEPSEEK_BASE_URL` then `https://api.deepseek.com` | Endpoint base; `/user/balance` is appended. |
|
|
50
|
-
| `models` | V4 Flash + V4
|
|
50
|
+
| `models` | V4.1 Flash (`deepseek-flash`) + V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 series | Advisory display rows, in presentation order; they mirror DSH's `llm-deepseek` catalog (plus the retired `deepseek-v4.1-flash-expires-on-0910` preview id, kept for readable historical labels). |
|
|
51
51
|
| `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. |
|
|
52
|
-
| `billing.models` | Published V4 + MiMo rates | Per-model
|
|
52
|
+
| `billing.models` | Published V4 + MiMo rates | Per-model price rows (`cacheHitInput`, `cacheMissInput`, `output`, in CNY per 1M tokens) with an optional inclusive `effectiveFrom` (epoch ms). |
|
|
53
53
|
|
|
54
|
-
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.
|
|
54
|
+
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. Several rows may share one model: each row is a rate revision, and a usage sample is priced at the peak/off-peak pair of the revision in effect at the sample's own timestamp (a row without `effectiveFrom` is that model's base revision and also covers every earlier instant).
|
|
55
|
+
|
|
56
|
+
The published table already ships DeepSeek's adjustments, so a session or a day spanning a change is priced exactly:
|
|
57
|
+
|
|
58
|
+
- **2026-09-10 12:00 Beijing** (`FLASH_SERIES_RATE_CHANGE_AT`): the whole flash series — the V4.1 Flash route `deepseek-flash` (released that day and now DSH's default), V4 Flash, V4 Flash Vision Exp, and the retired preview id — drops to off-peak 0.02 / 1.0 / 4.0 with peak at twice those prices. Earlier samples, including this route's own usage from before that instant, keep the superseded rates.
|
|
59
|
+
- **2026-09-14 12:00 Beijing** (`V4_PRO_ROUTE_SWITCH_AT`): the V4 Pro route is announced to be served by V4.1 Flash and billed at the V4.1 Flash rates; its row carries that second revision.
|
|
60
|
+
- MiMo-V2.5 series: untouched by either adjustment (flat rate).
|
|
55
61
|
|
|
56
62
|
## Model Experience
|
|
57
63
|
|
|
@@ -64,7 +70,7 @@ None; its only provider call is a credential-authenticated `/user/balance` read,
|
|
|
64
70
|
## Known Limitations and Deferred Work
|
|
65
71
|
|
|
66
72
|
- **Priced rows only** — the session and today spends only price models that have a `billing.models` row; a model without a rate row is omitted. An `assistant/attempt` is priced with the model of the latest `request/header`, so an attempt before any header contributes nothing.
|
|
67
|
-
- **Up-to-60s staleness** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds; only the manual refresh (`force`) recomputes immediately (still revision-gated, so an unchanged log costs nothing). The browser reads the live per-session value from the pushed projection instead, so the session line is never stale.
|
|
73
|
+
- **Up-to-60s staleness** — `getTodaySpend()` is served from the host-side cache for up to 60 seconds; only the manual refresh (`force`) recomputes immediately (still revision-gated, so an unchanged log costs nothing). The browser reads the live per-session value from the pushed projection instead, so the session line is never stale; the parenthesized today share on that same line comes from the already-fetched today-session ranking (`getTodaySessionsSpend()`, under the same 60-second cache and revision gate), so that one number can trail by up to 60 seconds.
|
|
68
74
|
- **Balance is TTL-cached** — one `/user/balance` snapshot is reused for up to 15 seconds and each request aborts after 5 seconds; `force` (the manual refresh) bypasses the TTL.
|
|
69
|
-
- **Projection pricing
|
|
75
|
+
- **Projection pricing follows the published revisions** — the projection fold resolves the rate revision per sample timestamp, so published re-pricing needs no refold; a hand-edited `billing.models` change prices only events folded after the change until the state version or the process is reset (the events fallback re-prices the full log).
|
|
70
76
|
- **A cached cold row may trail its log** — a cold session whose cached row covers the queried day is re-read from the log for exactness; a row whose own day is not the queried day is trusted without a read, so a session that crashed between its last checkpoint and its last event can under-report that tail until it is next read.
|
package/README.zh.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# baseURL: https://api.deepseek.com
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
插件注册 `billing` Remote,含六个方法:`getBalance(force?)`(解析后的 `/user/balance` 快照;15 秒宿主 TTL 内复用,`force` 绕过,单次请求 5 秒超时)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(force?)`(今日按会话的计费花费,按花费从高到低排序,每行带会话的持久标题)、`getTurnSpend(sessionId, messageId)`(单个已完成回合的计费花费,按收尾助手消息 id 定位)与 `getSessionTurnSpends(sessionId)`(该会话所有已完成回合的 `messageId → 金额` 映射,一趟折叠——对话每个消息行都要显示金额,客户端因此每会话只拉一次,而不是逐行调用 `getTurnSpend`)。计价的样本来源有两处:`assistant/message` 自身的 usage,以及失败/重试的 `assistant/attempt` 内嵌 stream 里的 usage(后者用最近一条 `request/header`
|
|
19
|
+
插件注册 `billing` Remote,含六个方法:`getBalance(force?)`(解析后的 `/user/balance` 快照;15 秒宿主 TTL 内复用,`force` 绕过,单次请求 5 秒超时)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(force?)`(今日按会话的计费花费,按花费从高到低排序,每行带会话的持久标题)、`getTurnSpend(sessionId, messageId)`(单个已完成回合的计费花费,按收尾助手消息 id 定位)与 `getSessionTurnSpends(sessionId)`(该会话所有已完成回合的 `messageId → 金额` 映射,一趟折叠——对话每个消息行都要显示金额,客户端因此每会话只拉一次,而不是逐行调用 `getTurnSpend`)。计价的样本来源有两处:`assistant/message` 自身的 usage,以及失败/重试的 `assistant/attempt` 内嵌 stream 里的 usage(后者用最近一条 `request/header` 的模型),各按样本自身发生时刻(北京时间)所在的峰/谷单价、以及该时刻生效的官方费率版本计价——高峰窗口仅周一至周五适用,周末全天按低谷价。同一 `(turn, step)` 的后一份样本替换前一份,`llm/retry-started` 之后重试的那次累加,与 DSH 自己的回合用量口径一致;随后按模型汇总。一个回合即收尾消息所在的 `turn/start`..`turn/end` 区间;排行从每个会话日志里最后一条 `session/title` 事件折叠标题(last-wins,重命名事件一旦提交、会话被重新读取即反映新名字)。
|
|
20
20
|
|
|
21
21
|
### 今日花费读取路径(消息触发不再全量扫描)
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
进程内首次解析之后,稳态读取只花在日志确实变化过的会话上。日志无法读取的会话带警告跳过(并被记住),而不是让整日合计失败。
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
注意:投影路径对每个会话的历史只计价一次,按事件被折叠时的费率。官方费率版本随定价闭包一起进入折叠,并按样本自身时刻解析,因此被调价的系列无论日志多晚折叠都能正确计价自身历史;只有**手工修改配置**(`billing.models`)才只影响变更后折叠的事件(事件路径会重算整个日志),而该解析口径变化时单元 `stateVersion` 会一并提升,使已落检查点被丢弃重折而不是沿用旧值。
|
|
31
31
|
|
|
32
32
|
## 分叉会话
|
|
33
33
|
|
|
@@ -47,11 +47,17 @@
|
|
|
47
47
|
| --- | --- | --- |
|
|
48
48
|
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次调用时解析的凭据引用(环境变量)名。 |
|
|
49
49
|
| `baseURL` | `$DEEPSEEK_BASE_URL`,其次 `https://api.deepseek.com` | 端点基础地址;会追加 `/user/balance`。 |
|
|
50
|
-
| `models` | V4 Flash
|
|
50
|
+
| `models` | V4.1 Flash(`deepseek-flash`)+ V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 系列 | 展示用的模型行,按展示顺序;与 DSH `llm-deepseek` 目录对齐(另保留已退役的 `deepseek-v4.1-flash-expires-on-0910` 预览 id,让历史日志仍有可读标签)。 |
|
|
51
51
|
| `billing.peakHours` | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 |
|
|
52
|
-
| `billing.models` | 官方 V4 + MiMo 费率 |
|
|
52
|
+
| `billing.models` | 官方 V4 + MiMo 费率 | 每个模型的单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token),可带生效时刻 `effectiveFrom`(epoch 毫秒,含该时刻)。 |
|
|
53
53
|
|
|
54
|
-
只想覆盖某个模型而不丢其它,就提供一个非空的 `billing.models`
|
|
54
|
+
只想覆盖某个模型而不丢其它,就提供一个非空的 `billing.models` 列表;空或省略则回退到官方默认费率。同一个模型可以有多行:每行是一个费率版本,用量样本按**样本自身时刻**生效的那一版取峰/谷单价(不带 `effectiveFrom` 的行是该模型的基础版本,同时覆盖更早的一切时刻)。
|
|
55
|
+
|
|
56
|
+
内置价目表已包含 DeepSeek 的两轮调整,跨越任一变更点的会话或自然日都能精确计价:
|
|
57
|
+
|
|
58
|
+
- **2026-09-10 12:00(北京时间)**(`FLASH_SERIES_RATE_CHANGE_AT`):整个 flash 系列——V4.1 Flash 路由 `deepseek-flash`(当日发布,现为 DSH 默认模型)、V4 Flash、V4 Flash Vision Exp 以及已退役的预览 id——降为谷时 0.02 / 1.0 / 4.0,峰时为其两倍;更早的样本(含该路由 12:00 之前的自身用量)沿用被取代的旧价。
|
|
59
|
+
- **2026-09-14 12:00(北京时间)**(`V4_PRO_ROUTE_SWITCH_AT`):V4 Pro 路由按公告改由 V4.1 Flash 服务并按 V4.1 Flash 计费,该行因此带有第二个费率版本。
|
|
60
|
+
- MiMo-V2.5 系列不受两轮调整影响(统一费率)。
|
|
55
61
|
|
|
56
62
|
## 模型体验
|
|
57
63
|
|
|
@@ -64,7 +70,7 @@
|
|
|
64
70
|
## 已知限制与暂缓事项
|
|
65
71
|
|
|
66
72
|
- **有费率行才计价** —— 会话花费与今日花费只统计价目表(`billing.models`)里有的模型;没有费率行的模型不计入。`assistant/attempt` 用最近一条 `request/header` 的模型计价,因此首条 header 之前的 attempt 不计入。
|
|
67
|
-
- **最多 60 秒延迟** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;只有手动刷新(`force`)立即重算(仍受 revision
|
|
73
|
+
- **最多 60 秒延迟** —— `getTodaySpend()` 由宿主侧缓存服务最多 60 秒;只有手动刷新(`force`)立即重算(仍受 revision 门控,日志未变则零成本)。浏览器端「本会话花费」读的是推送的投影值,因此不会滞后;它括号里的今日份金额取自已拉取的今日会话排行(`getTodaySessionsSpend()`,同样受 60 秒缓存与 revision 门控),所以那一项最多滞后 60 秒。
|
|
68
74
|
- **额度带 TTL 缓存** —— 一份 `/user/balance` 快照最多复用 15 秒,单次请求 5 秒超时;`force`(手动刷新)绕过 TTL。
|
|
69
|
-
-
|
|
75
|
+
- **投影计价跟随官方费率版本** —— 投影折叠按样本时刻解析费率版本,官方调价因此无需重折;手工改 `billing.models` 则只影响变更后折叠的事件,直到状态版本或进程重置(事件路径回退会重算整个日志)。
|
|
70
76
|
- **冷缓存行可能滞后于日志** —— 冷会话若缓存行覆盖查询日,会重读日志以求精确;若缓存行自身的日期不是查询日则直接采信、不读日志,因此进程在最后一次检查点之后、最后一条事件之前崩溃的会话,其尾部可能暂时少算,直到该会话被重新读取。
|
package/lib/index.js
CHANGED
|
@@ -322,33 +322,93 @@ const DEFAULT_PEAK_HOURS = [{
|
|
|
322
322
|
start: 14,
|
|
323
323
|
end: 18
|
|
324
324
|
}];
|
|
325
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* Inclusive epoch ms of the published V4 Flash series re-pricing:
|
|
327
|
+
* 2026-09-10 12:00 Beijing time (UTC+8, no DST) = 04:00 UTC. Samples before
|
|
328
|
+
* this instant keep the base rates; samples at or after it bill at the second
|
|
329
|
+
* revision.
|
|
330
|
+
*/
|
|
331
|
+
const FLASH_SERIES_RATE_CHANGE_AT = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
332
|
+
/**
|
|
333
|
+
* Inclusive epoch ms of the announced V4 Pro route switch: 2026-09-14 12:00
|
|
334
|
+
* Beijing time (UTC+8, no DST) = 04:00 UTC. From that instant the V4 Pro route
|
|
335
|
+
* is served by V4.1 Flash and billed at the V4.1 Flash rates.
|
|
336
|
+
*/
|
|
337
|
+
const V4_PRO_ROUTE_SWITCH_AT = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
338
|
+
/** The V4 Flash series' base rates (effective 2026-08-17), CNY per 1M tokens. */
|
|
339
|
+
const FLASH_BASE_RATES = {
|
|
340
|
+
peak: {
|
|
341
|
+
cacheHitInput: .1,
|
|
342
|
+
cacheMissInput: 3,
|
|
343
|
+
output: 9
|
|
344
|
+
},
|
|
345
|
+
offPeak: {
|
|
346
|
+
cacheHitInput: .05,
|
|
347
|
+
cacheMissInput: 1.5,
|
|
348
|
+
output: 4.5
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* The V4 Flash series' second revision (effective
|
|
353
|
+
* {@link FLASH_SERIES_RATE_CHANGE_AT}): off-peak 0.02 / 1.0 / 4.0, peak at
|
|
354
|
+
* twice those prices.
|
|
355
|
+
*/
|
|
356
|
+
const FLASH_REPRICED_RATES = {
|
|
357
|
+
effectiveFrom: FLASH_SERIES_RATE_CHANGE_AT,
|
|
358
|
+
peak: {
|
|
359
|
+
cacheHitInput: .04,
|
|
360
|
+
cacheMissInput: 2,
|
|
361
|
+
output: 8
|
|
362
|
+
},
|
|
363
|
+
offPeak: {
|
|
364
|
+
cacheHitInput: .02,
|
|
365
|
+
cacheMissInput: 1,
|
|
366
|
+
output: 4
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* The V4.1 Flash rates as they reach the retired V4 Pro route from
|
|
371
|
+
* {@link V4_PRO_ROUTE_SWITCH_AT}: the same price pair as the flash series'
|
|
372
|
+
* second revision, carried at its own effective instant.
|
|
373
|
+
*/
|
|
374
|
+
const V4_PRO_SWITCHED_RATES = {
|
|
375
|
+
effectiveFrom: V4_PRO_ROUTE_SWITCH_AT,
|
|
376
|
+
peak: FLASH_REPRICED_RATES.peak,
|
|
377
|
+
offPeak: FLASH_REPRICED_RATES.offPeak
|
|
378
|
+
};
|
|
379
|
+
/**
|
|
380
|
+
* Official peak/off-peak rates (CNY per 1M tokens) per model, as dated
|
|
381
|
+
* revisions. Base rows are the schedule effective 2026-08-17; the V4 Flash
|
|
382
|
+
* series (V4.1 Flash, V4 Flash, V4 Flash Vision Exp) carries the second
|
|
383
|
+
* revision effective 2026-09-10 12:00 Beijing, and the V4 Pro row the V4.1
|
|
384
|
+
* Flash rates from its announced route switch (2026-09-14 12:00 Beijing) —
|
|
385
|
+
* the MiMo-V2.5 series is untouched by either adjustment. Rows sharing a model
|
|
386
|
+
* are that model's rate history.
|
|
387
|
+
*/
|
|
326
388
|
const DEFAULT_MODEL_PRICING = [
|
|
389
|
+
{
|
|
390
|
+
model: "deepseek-flash",
|
|
391
|
+
...FLASH_BASE_RATES
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
model: "deepseek-flash",
|
|
395
|
+
...FLASH_REPRICED_RATES
|
|
396
|
+
},
|
|
327
397
|
{
|
|
328
398
|
model: "deepseek-v4-flash",
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
offPeak: {
|
|
335
|
-
cacheHitInput: .05,
|
|
336
|
-
cacheMissInput: 1.5,
|
|
337
|
-
output: 4.5
|
|
338
|
-
}
|
|
399
|
+
...FLASH_BASE_RATES
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
model: "deepseek-v4-flash",
|
|
403
|
+
...FLASH_REPRICED_RATES
|
|
339
404
|
},
|
|
340
405
|
{
|
|
341
406
|
model: "deepseek-v4.1-flash-expires-on-0910",
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
offPeak: {
|
|
348
|
-
cacheHitInput: .05,
|
|
349
|
-
cacheMissInput: 1.5,
|
|
350
|
-
output: 4.5
|
|
351
|
-
}
|
|
407
|
+
...FLASH_BASE_RATES
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
model: "deepseek-v4.1-flash-expires-on-0910",
|
|
411
|
+
...FLASH_REPRICED_RATES
|
|
352
412
|
},
|
|
353
413
|
{
|
|
354
414
|
model: "deepseek-v4-pro",
|
|
@@ -363,18 +423,17 @@ const DEFAULT_MODEL_PRICING = [
|
|
|
363
423
|
output: 13.5
|
|
364
424
|
}
|
|
365
425
|
},
|
|
426
|
+
{
|
|
427
|
+
model: "deepseek-v4-pro",
|
|
428
|
+
...V4_PRO_SWITCHED_RATES
|
|
429
|
+
},
|
|
366
430
|
{
|
|
367
431
|
model: "deepseek-v4-flash-vision-exp",
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
offPeak: {
|
|
374
|
-
cacheHitInput: .05,
|
|
375
|
-
cacheMissInput: 1.5,
|
|
376
|
-
output: 4.5
|
|
377
|
-
}
|
|
432
|
+
...FLASH_BASE_RATES
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
model: "deepseek-v4-flash-vision-exp",
|
|
436
|
+
...FLASH_REPRICED_RATES
|
|
378
437
|
},
|
|
379
438
|
{
|
|
380
439
|
model: "mimo-v2.5-pro",
|
|
@@ -409,22 +468,67 @@ const DEFAULT_MODEL_PRICING = [
|
|
|
409
468
|
* `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
|
|
410
469
|
* selects the defaults. Explicit non-empty rows override the same model; a
|
|
411
470
|
* supplied non-empty `models` list is authoritative.
|
|
471
|
+
*
|
|
472
|
+
* Rows sharing a model are that model's rate revisions, kept in ascending
|
|
473
|
+
* `effectiveFrom` order (an undated base revision first). Two rows declaring
|
|
474
|
+
* the same effective instant are one revision and the later row wins — the
|
|
475
|
+
* historical override rule — so re-declaring a model can neither duplicate a
|
|
476
|
+
* revision nor install a second undated base.
|
|
412
477
|
* @param config - optional raw billing configuration.
|
|
413
|
-
* @returns the resolved table and peak-hour windows.
|
|
478
|
+
* @returns the resolved table (per model: its revisions plus the newest rates) and peak-hour windows.
|
|
414
479
|
*/
|
|
415
480
|
function resolveBilling(config) {
|
|
416
481
|
const peakHours = config?.peakHours !== void 0 && config.peakHours.length > 0 ? config.peakHours : DEFAULT_PEAK_HOURS;
|
|
417
482
|
const rows = config?.models !== void 0 && config.models.length > 0 ? config.models : DEFAULT_MODEL_PRICING;
|
|
483
|
+
const schedules = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const row of rows) {
|
|
485
|
+
const revision = row.effectiveFrom === void 0 ? {
|
|
486
|
+
peak: row.peak,
|
|
487
|
+
offPeak: row.offPeak
|
|
488
|
+
} : {
|
|
489
|
+
effectiveFrom: row.effectiveFrom,
|
|
490
|
+
peak: row.peak,
|
|
491
|
+
offPeak: row.offPeak
|
|
492
|
+
};
|
|
493
|
+
const revisions = schedules.get(row.model);
|
|
494
|
+
if (revisions === void 0) {
|
|
495
|
+
schedules.set(row.model, [revision]);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const duplicate = revisions.findIndex((candidate) => candidate.effectiveFrom === revision.effectiveFrom);
|
|
499
|
+
if (duplicate >= 0) revisions[duplicate] = revision;
|
|
500
|
+
else revisions.push(revision);
|
|
501
|
+
}
|
|
418
502
|
const models = /* @__PURE__ */ new Map();
|
|
419
|
-
for (const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
503
|
+
for (const [model, revisions] of schedules) {
|
|
504
|
+
revisions.sort((left, right) => (left.effectiveFrom ?? Number.NEGATIVE_INFINITY) - (right.effectiveFrom ?? Number.NEGATIVE_INFINITY));
|
|
505
|
+
const newest = revisions[revisions.length - 1];
|
|
506
|
+
models.set(model, {
|
|
507
|
+
peak: newest.peak,
|
|
508
|
+
offPeak: newest.offPeak,
|
|
509
|
+
revisions
|
|
510
|
+
});
|
|
511
|
+
}
|
|
423
512
|
return {
|
|
424
513
|
peakHours,
|
|
425
514
|
models
|
|
426
515
|
};
|
|
427
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* The rate revision in effect at one instant: the newest revision that took
|
|
519
|
+
* effect at or before it. Revisions are ascending, so the scan stops at the
|
|
520
|
+
* first future one. An instant before the earliest dated revision bills at that
|
|
521
|
+
* earliest revision — a model with only dated rows is never left unpriced.
|
|
522
|
+
*/
|
|
523
|
+
function ratesAt(revisions, time) {
|
|
524
|
+
let chosen = revisions[0];
|
|
525
|
+
for (let index = 1; index < revisions.length; index += 1) {
|
|
526
|
+
const revision = revisions[index];
|
|
527
|
+
if (revision.effectiveFrom === void 0 || revision.effectiveFrom > time) break;
|
|
528
|
+
chosen = revision;
|
|
529
|
+
}
|
|
530
|
+
return chosen;
|
|
531
|
+
}
|
|
428
532
|
/** Beijing is a fixed UTC+8 offset with no DST. */
|
|
429
533
|
const BEIJING_OFFSET_MS = 288e5;
|
|
430
534
|
/** Milliseconds in one day. */
|
|
@@ -472,6 +576,7 @@ function beijingPartsOf(time) {
|
|
|
472
576
|
const msOfDay = shifted - epochDay * DAY_MS;
|
|
473
577
|
const civil = civilDateOf(epochDay);
|
|
474
578
|
return {
|
|
579
|
+
time,
|
|
475
580
|
hour: Math.floor(msOfDay / 36e5),
|
|
476
581
|
weekday: ((epochDay + 4) % 7 + 7) % 7,
|
|
477
582
|
dayKey: `${civil.year}-${pad2(civil.month)}-${pad2(civil.day)}`
|
|
@@ -525,7 +630,8 @@ function isPeak(billing, now) {
|
|
|
525
630
|
/**
|
|
526
631
|
* Price one event at the official per-model rates, applying the peak/off-peak
|
|
527
632
|
* table by its Beijing-time hour and weekday (peak windows apply Monday–Friday
|
|
528
|
-
* only; weekends are off-peak)
|
|
633
|
+
* only; weekends are off-peak) and the rate revision in effect at its own
|
|
634
|
+
* timestamp. Each `assistant/message` event with usage
|
|
529
635
|
* contributes cache-hit input, cache-miss input (uncached input plus cache
|
|
530
636
|
* writes), and output (reasoning included) tokens at the rate of its own
|
|
531
637
|
* timestamp; a model with usage but no pricing row contributes nothing.
|
|
@@ -556,8 +662,10 @@ function priceEventAt(parts, event, billing, names) {
|
|
|
556
662
|
}
|
|
557
663
|
/**
|
|
558
664
|
* Price one provider-reported usage sample for one model at the rates of the
|
|
559
|
-
* sample's own Beijing-time hour and weekday
|
|
560
|
-
*
|
|
665
|
+
* sample's own Beijing-time hour and weekday — the peak or off-peak price of
|
|
666
|
+
* the rate revision in effect at the sample's own timestamp (a re-priced series
|
|
667
|
+
* bills its history at the rates that applied then). `undefined` when the model
|
|
668
|
+
* has no pricing row.
|
|
561
669
|
* @param parts - the sample's Beijing-time view.
|
|
562
670
|
* @param usage - the reported token buckets.
|
|
563
671
|
* @param model - the wire model id the sample belongs to.
|
|
@@ -569,7 +677,8 @@ function priceUsage(parts, usage, model, billing, names) {
|
|
|
569
677
|
const pricing = billing.models.get(model);
|
|
570
678
|
if (pricing === void 0) return void 0;
|
|
571
679
|
const peak = isPeakParts(billing, parts.hour, parts.weekday);
|
|
572
|
-
const
|
|
680
|
+
const revision = ratesAt(pricing.revisions, parts.time);
|
|
681
|
+
const price = peak ? revision.peak : revision.offPeak;
|
|
573
682
|
const hit = usage.cacheReadTokens ?? 0;
|
|
574
683
|
const miss = usage.inputTokens + (usage.cacheWriteTokens ?? 0);
|
|
575
684
|
const output = usage.outputTokens;
|
|
@@ -1119,13 +1228,15 @@ const billingUnitSchema = z$1.object({
|
|
|
1119
1228
|
}).strict().nullable()
|
|
1120
1229
|
}).strict();
|
|
1121
1230
|
/**
|
|
1122
|
-
* Build the `billingTodaySpend` unit for one resolved pricing table.
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1231
|
+
* Build the `billingTodaySpend` unit for one resolved pricing table. Published
|
|
1232
|
+
* rate revisions travel inside the closure and are resolved per sample
|
|
1233
|
+
* timestamp, so a re-priced series bills its own history correctly however late
|
|
1234
|
+
* a log is folded; only a configuration change (editing `billing.models`) is
|
|
1235
|
+
* fixed at registration, and it re-prices just the events folded afterwards
|
|
1236
|
+
* (the events-scan paths re-price the whole log). Bump
|
|
1237
|
+
* {@link ProjectionDefinition.stateVersion} whenever the state shape or fold
|
|
1238
|
+
* semantics change, so persisted checkpoint rows are discarded instead of
|
|
1239
|
+
* folded forward.
|
|
1129
1240
|
* @param billing - resolved pricing with peak-hour windows.
|
|
1130
1241
|
* @param catalog - model display rows, in presentation order.
|
|
1131
1242
|
* @returns the unit definition to register on `ctx.sessionProjections`.
|
|
@@ -1134,7 +1245,7 @@ function billingTodaySpendDefinition(billing, catalog) {
|
|
|
1134
1245
|
const names = new Map(catalog.map((model) => [model.id, model.name]));
|
|
1135
1246
|
return {
|
|
1136
1247
|
key: BILLING_UNIT_KEY,
|
|
1137
|
-
stateVersion:
|
|
1248
|
+
stateVersion: 4,
|
|
1138
1249
|
stateSchema: billingUnitSchema,
|
|
1139
1250
|
init: (_header, inheritedEventCount) => emptyBillingFoldState(Number(inheritedEventCount ?? 0)),
|
|
1140
1251
|
apply: (state, event) => applyBillingEvent(state, event, billing, names),
|
|
@@ -1698,7 +1809,18 @@ const DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
|
1698
1809
|
const BASE_URL_ENV = "DEEPSEEK_BASE_URL";
|
|
1699
1810
|
/** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
|
|
1700
1811
|
const PUBLIC_BASE_URL = "https://api.deepseek.com";
|
|
1812
|
+
/**
|
|
1813
|
+
* Advisory display rows mirroring the DSH `llm-deepseek` catalog (V4.1 Flash
|
|
1814
|
+
* first, its current default route), plus the MiMo-V2.5 series. The retired
|
|
1815
|
+
* preview id `deepseek-v4.1-flash-expires-on-0910` stays so the logs that used
|
|
1816
|
+
* it keep a readable label; rows never restrict which models are priced — the
|
|
1817
|
+
* pricing table does.
|
|
1818
|
+
*/
|
|
1701
1819
|
const DEFAULT_MODELS = [
|
|
1820
|
+
{
|
|
1821
|
+
id: "deepseek-flash",
|
|
1822
|
+
name: "DeepSeek-V41-Flash"
|
|
1823
|
+
},
|
|
1702
1824
|
{
|
|
1703
1825
|
id: "deepseek-v4-flash",
|
|
1704
1826
|
name: "DeepSeek-V4-Flash"
|
|
@@ -1733,16 +1855,18 @@ const tokenPrice = z.object({
|
|
|
1733
1855
|
cacheMissInput: z.number().min(0),
|
|
1734
1856
|
output: z.number().min(0)
|
|
1735
1857
|
});
|
|
1858
|
+
const billingRateRow = z.object({
|
|
1859
|
+
model: z.string().required(),
|
|
1860
|
+
peak: tokenPrice,
|
|
1861
|
+
offPeak: tokenPrice,
|
|
1862
|
+
effectiveFrom: z.number().min(0)
|
|
1863
|
+
});
|
|
1736
1864
|
const billingConfig = z.object({
|
|
1737
1865
|
peakHours: z.array(z.object({
|
|
1738
1866
|
start: z.number().step(1).min(0).max(23),
|
|
1739
1867
|
end: z.number().step(1).min(0).max(24)
|
|
1740
1868
|
})).default([...DEFAULT_PEAK_HOURS]),
|
|
1741
|
-
models: z.array(
|
|
1742
|
-
model: z.string().required(),
|
|
1743
|
-
peak: tokenPrice,
|
|
1744
|
-
offPeak: tokenPrice
|
|
1745
|
-
})).default([...DEFAULT_MODEL_PRICING])
|
|
1869
|
+
models: z.array(billingRateRow).default([...DEFAULT_MODEL_PRICING])
|
|
1746
1870
|
});
|
|
1747
1871
|
const Config = z.object({
|
|
1748
1872
|
apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
|
|
@@ -2017,4 +2141,4 @@ function apply(ctx, config) {
|
|
|
2017
2141
|
});
|
|
2018
2142
|
}
|
|
2019
2143
|
//#endregion
|
|
2020
|
-
export { BALANCE_CACHE_MS, BALANCE_TIMEOUT_MS, BILLING_UNIT_KEY, BillingFolder, Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, PUBLIC_BASE_URL, SESSION_SPEND_CACHE_LIMIT, SESSION_TURN_SPEND_CACHE_LIMIT, SessionTurnSpendFolder, SpendAccumulator, TODAY_SPEND_CACHE_MS, TODAY_SPEND_MAX_EVENTS, TodaySpendCache, TodaySpendScanner, addEventContribution, apply, applyBillingEvent, beijingDayKey, billingTodaySpendDefinition, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, emptyBillingFoldState, emptyTodaySpend, fetchDeepSeekBalance, foldBillingUnit, foldOwnBilling, foldSessionTitle, forkBoundaryOf, isPeak, isSeededSession, liveSessionEvents, mergeTodaySpend, name, negateSpend, parseDeepSeekBalance, persistenceInspect, persistenceListSnapshots, priceEvent, priceUsage, resolveBilling, subtractSpend };
|
|
2144
|
+
export { BALANCE_CACHE_MS, BALANCE_TIMEOUT_MS, BILLING_UNIT_KEY, BillingFolder, Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, FLASH_SERIES_RATE_CHANGE_AT, PUBLIC_BASE_URL, SESSION_SPEND_CACHE_LIMIT, SESSION_TURN_SPEND_CACHE_LIMIT, SessionTurnSpendFolder, SpendAccumulator, TODAY_SPEND_CACHE_MS, TODAY_SPEND_MAX_EVENTS, TodaySpendCache, TodaySpendScanner, V4_PRO_ROUTE_SWITCH_AT, addEventContribution, apply, applyBillingEvent, beijingDayKey, billingTodaySpendDefinition, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, emptyBillingFoldState, emptyTodaySpend, fetchDeepSeekBalance, foldBillingUnit, foldOwnBilling, foldSessionTitle, forkBoundaryOf, isPeak, isSeededSession, liveSessionEvents, mergeTodaySpend, name, negateSpend, parseDeepSeekBalance, persistenceInspect, persistenceListSnapshots, priceEvent, priceUsage, resolveBilling, subtractSpend };
|
package/lib/types/billing.d.ts
CHANGED
|
@@ -24,21 +24,41 @@ export interface DeepSeekTokenPrice {
|
|
|
24
24
|
/** 1M output tokens. */
|
|
25
25
|
output: number;
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* One published peak/off-peak rate revision of a model: the price pair plus the
|
|
29
|
+
* instant it took effect. A provider re-prices a series without repricing its
|
|
30
|
+
* history, so the table keeps every revision and prices each sample at the
|
|
31
|
+
* rates of the sample's own timestamp.
|
|
32
|
+
*/
|
|
33
|
+
export interface DeepSeekRateRevision {
|
|
29
34
|
/** Price during peak hours. */
|
|
30
35
|
peak: DeepSeekTokenPrice;
|
|
31
36
|
/** Price during off-peak hours. */
|
|
32
37
|
offPeak: DeepSeekTokenPrice;
|
|
38
|
+
/**
|
|
39
|
+
* Inclusive epoch ms this revision takes effect: it prices every sample at or
|
|
40
|
+
* after that instant. `undefined` on a model's base revision, which also
|
|
41
|
+
* covers every earlier instant.
|
|
42
|
+
*/
|
|
43
|
+
effectiveFrom?: number;
|
|
44
|
+
}
|
|
45
|
+
/** Resolved pricing for one model: its published rate revisions, oldest first. */
|
|
46
|
+
export interface DeepSeekModelPricing {
|
|
47
|
+
/** Peak-hour price of the newest revision (the rates in effect now). */
|
|
48
|
+
peak: DeepSeekTokenPrice;
|
|
49
|
+
/** Off-peak price of the newest revision (the rates in effect now). */
|
|
50
|
+
offPeak: DeepSeekTokenPrice;
|
|
51
|
+
/**
|
|
52
|
+
* Every published revision of this model, ascending by `effectiveFrom` (an
|
|
53
|
+
* undated base revision first); always at least one. {@link priceUsage} picks
|
|
54
|
+
* the revision in effect at the priced sample's own timestamp.
|
|
55
|
+
*/
|
|
56
|
+
revisions: readonly DeepSeekRateRevision[];
|
|
33
57
|
}
|
|
34
58
|
/** One model's pricing-table row in configuration form. */
|
|
35
|
-
export interface BillingConfigModel {
|
|
59
|
+
export interface BillingConfigModel extends DeepSeekRateRevision {
|
|
36
60
|
/** Wire model id. */
|
|
37
61
|
model: string;
|
|
38
|
-
/** Peak-hour price. */
|
|
39
|
-
peak: DeepSeekTokenPrice;
|
|
40
|
-
/** Off-peak price. */
|
|
41
|
-
offPeak: DeepSeekTokenPrice;
|
|
42
62
|
}
|
|
43
63
|
/** One peak-hour window on a 24h Beijing-time clock, applied weekdays only. */
|
|
44
64
|
export interface PeakHourWindow {
|
|
@@ -54,7 +74,11 @@ export interface BillingConfig {
|
|
|
54
74
|
* only; weekends (Saturday and Sunday) are always off-peak.
|
|
55
75
|
*/
|
|
56
76
|
peakHours?: PeakHourWindow[];
|
|
57
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Per-model pricing rows; omission uses the published V4 and MiMo rates.
|
|
79
|
+
* Several rows for one model declare that model's rate history, priced per
|
|
80
|
+
* sample by `effectiveFrom` (see {@link BillingConfigModel}).
|
|
81
|
+
*/
|
|
58
82
|
models?: BillingConfigModel[];
|
|
59
83
|
}
|
|
60
84
|
/**
|
|
@@ -63,7 +87,28 @@ export interface BillingConfig {
|
|
|
63
87
|
* (effective 2026-08-23).
|
|
64
88
|
*/
|
|
65
89
|
export declare const DEFAULT_PEAK_HOURS: readonly PeakHourWindow[];
|
|
66
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Inclusive epoch ms of the published V4 Flash series re-pricing:
|
|
92
|
+
* 2026-09-10 12:00 Beijing time (UTC+8, no DST) = 04:00 UTC. Samples before
|
|
93
|
+
* this instant keep the base rates; samples at or after it bill at the second
|
|
94
|
+
* revision.
|
|
95
|
+
*/
|
|
96
|
+
export declare const FLASH_SERIES_RATE_CHANGE_AT: number;
|
|
97
|
+
/**
|
|
98
|
+
* Inclusive epoch ms of the announced V4 Pro route switch: 2026-09-14 12:00
|
|
99
|
+
* Beijing time (UTC+8, no DST) = 04:00 UTC. From that instant the V4 Pro route
|
|
100
|
+
* is served by V4.1 Flash and billed at the V4.1 Flash rates.
|
|
101
|
+
*/
|
|
102
|
+
export declare const V4_PRO_ROUTE_SWITCH_AT: number;
|
|
103
|
+
/**
|
|
104
|
+
* Official peak/off-peak rates (CNY per 1M tokens) per model, as dated
|
|
105
|
+
* revisions. Base rows are the schedule effective 2026-08-17; the V4 Flash
|
|
106
|
+
* series (V4.1 Flash, V4 Flash, V4 Flash Vision Exp) carries the second
|
|
107
|
+
* revision effective 2026-09-10 12:00 Beijing, and the V4 Pro row the V4.1
|
|
108
|
+
* Flash rates from its announced route switch (2026-09-14 12:00 Beijing) —
|
|
109
|
+
* the MiMo-V2.5 series is untouched by either adjustment. Rows sharing a model
|
|
110
|
+
* are that model's rate history.
|
|
111
|
+
*/
|
|
67
112
|
export declare const DEFAULT_MODEL_PRICING: readonly BillingConfigModel[];
|
|
68
113
|
/** Resolved billing configuration: a pricing table plus peak-hour windows. */
|
|
69
114
|
export interface ResolvedBilling {
|
|
@@ -79,12 +124,24 @@ export interface ResolvedBilling {
|
|
|
79
124
|
* `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
|
|
80
125
|
* selects the defaults. Explicit non-empty rows override the same model; a
|
|
81
126
|
* supplied non-empty `models` list is authoritative.
|
|
127
|
+
*
|
|
128
|
+
* Rows sharing a model are that model's rate revisions, kept in ascending
|
|
129
|
+
* `effectiveFrom` order (an undated base revision first). Two rows declaring
|
|
130
|
+
* the same effective instant are one revision and the later row wins — the
|
|
131
|
+
* historical override rule — so re-declaring a model can neither duplicate a
|
|
132
|
+
* revision nor install a second undated base.
|
|
82
133
|
* @param config - optional raw billing configuration.
|
|
83
|
-
* @returns the resolved table and peak-hour windows.
|
|
134
|
+
* @returns the resolved table (per model: its revisions plus the newest rates) and peak-hour windows.
|
|
84
135
|
*/
|
|
85
136
|
export declare function resolveBilling(config: BillingConfig | undefined): ResolvedBilling;
|
|
86
137
|
/** One shifted-timestamp view of a Beijing (UTC+8, no DST) instant. */
|
|
87
138
|
export interface BeijingParts {
|
|
139
|
+
/**
|
|
140
|
+
* The instant in epoch milliseconds. Pricing needs it back to resolve the
|
|
141
|
+
* rate revision in effect at the sample's own timestamp (see
|
|
142
|
+
* {@link ratesAt}), so the view carries it instead of a second parse.
|
|
143
|
+
*/
|
|
144
|
+
time: number;
|
|
88
145
|
/** Beijing hour, `0`–`23`. */
|
|
89
146
|
hour: number;
|
|
90
147
|
/** Beijing weekday as `getUTCDay()`: `0` is Sunday, `6` is Saturday. */
|
|
@@ -199,7 +256,8 @@ export interface BillingEventContribution {
|
|
|
199
256
|
/**
|
|
200
257
|
* Price one event at the official per-model rates, applying the peak/off-peak
|
|
201
258
|
* table by its Beijing-time hour and weekday (peak windows apply Monday–Friday
|
|
202
|
-
* only; weekends are off-peak)
|
|
259
|
+
* only; weekends are off-peak) and the rate revision in effect at its own
|
|
260
|
+
* timestamp. Each `assistant/message` event with usage
|
|
203
261
|
* contributes cache-hit input, cache-miss input (uncached input plus cache
|
|
204
262
|
* writes), and output (reasoning included) tokens at the rate of its own
|
|
205
263
|
* timestamp; a model with usage but no pricing row contributes nothing.
|
|
@@ -223,8 +281,10 @@ export declare function priceEvent(event: SessionEvent, billing: ResolvedBilling
|
|
|
223
281
|
export declare function priceEventAt(parts: BeijingParts, event: SessionEvent, billing: ResolvedBilling, names: ReadonlyMap<string, string>): BillingEventContribution | undefined;
|
|
224
282
|
/**
|
|
225
283
|
* Price one provider-reported usage sample for one model at the rates of the
|
|
226
|
-
* sample's own Beijing-time hour and weekday
|
|
227
|
-
*
|
|
284
|
+
* sample's own Beijing-time hour and weekday — the peak or off-peak price of
|
|
285
|
+
* the rate revision in effect at the sample's own timestamp (a re-priced series
|
|
286
|
+
* bills its history at the rates that applied then). `undefined` when the model
|
|
287
|
+
* has no pricing row.
|
|
228
288
|
* @param parts - the sample's Beijing-time view.
|
|
229
289
|
* @param usage - the reported token buckets.
|
|
230
290
|
* @param model - the wire model id the sample belongs to.
|
package/lib/types/billing.js
CHANGED
|
@@ -21,33 +21,77 @@ export const DEFAULT_PEAK_HOURS = [
|
|
|
21
21
|
{ start: 9, end: 12 },
|
|
22
22
|
{ start: 14, end: 18 },
|
|
23
23
|
];
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Inclusive epoch ms of the published V4 Flash series re-pricing:
|
|
26
|
+
* 2026-09-10 12:00 Beijing time (UTC+8, no DST) = 04:00 UTC. Samples before
|
|
27
|
+
* this instant keep the base rates; samples at or after it bill at the second
|
|
28
|
+
* revision.
|
|
29
|
+
*/
|
|
30
|
+
export const FLASH_SERIES_RATE_CHANGE_AT = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
31
|
+
/**
|
|
32
|
+
* Inclusive epoch ms of the announced V4 Pro route switch: 2026-09-14 12:00
|
|
33
|
+
* Beijing time (UTC+8, no DST) = 04:00 UTC. From that instant the V4 Pro route
|
|
34
|
+
* is served by V4.1 Flash and billed at the V4.1 Flash rates.
|
|
35
|
+
*/
|
|
36
|
+
export const V4_PRO_ROUTE_SWITCH_AT = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
37
|
+
/** The V4 Flash series' base rates (effective 2026-08-17), CNY per 1M tokens. */
|
|
38
|
+
const FLASH_BASE_RATES = {
|
|
39
|
+
peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
|
|
40
|
+
offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The V4 Flash series' second revision (effective
|
|
44
|
+
* {@link FLASH_SERIES_RATE_CHANGE_AT}): off-peak 0.02 / 1.0 / 4.0, peak at
|
|
45
|
+
* twice those prices.
|
|
46
|
+
*/
|
|
47
|
+
const FLASH_REPRICED_RATES = {
|
|
48
|
+
effectiveFrom: FLASH_SERIES_RATE_CHANGE_AT,
|
|
49
|
+
peak: { cacheHitInput: 0.04, cacheMissInput: 2.0, output: 8.0 },
|
|
50
|
+
offPeak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 4.0 },
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The V4.1 Flash rates as they reach the retired V4 Pro route from
|
|
54
|
+
* {@link V4_PRO_ROUTE_SWITCH_AT}: the same price pair as the flash series'
|
|
55
|
+
* second revision, carried at its own effective instant.
|
|
56
|
+
*/
|
|
57
|
+
const V4_PRO_SWITCHED_RATES = {
|
|
58
|
+
effectiveFrom: V4_PRO_ROUTE_SWITCH_AT,
|
|
59
|
+
peak: FLASH_REPRICED_RATES.peak,
|
|
60
|
+
offPeak: FLASH_REPRICED_RATES.offPeak,
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Official peak/off-peak rates (CNY per 1M tokens) per model, as dated
|
|
64
|
+
* revisions. Base rows are the schedule effective 2026-08-17; the V4 Flash
|
|
65
|
+
* series (V4.1 Flash, V4 Flash, V4 Flash Vision Exp) carries the second
|
|
66
|
+
* revision effective 2026-09-10 12:00 Beijing, and the V4 Pro row the V4.1
|
|
67
|
+
* Flash rates from its announced route switch (2026-09-14 12:00 Beijing) —
|
|
68
|
+
* the MiMo-V2.5 series is untouched by either adjustment. Rows sharing a model
|
|
69
|
+
* are that model's rate history.
|
|
70
|
+
*/
|
|
25
71
|
export const DEFAULT_MODEL_PRICING = [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
// deepseek-v4-flash
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
|
|
38
|
-
},
|
|
72
|
+
// deepseek-flash is the V4.1 Flash route, DSH's default catalog entry; image
|
|
73
|
+
// inputs are converted to tokens at the same per-token price.
|
|
74
|
+
{ model: 'deepseek-flash', ...FLASH_BASE_RATES },
|
|
75
|
+
{ model: 'deepseek-flash', ...FLASH_REPRICED_RATES },
|
|
76
|
+
{ model: 'deepseek-v4-flash', ...FLASH_BASE_RATES },
|
|
77
|
+
{ model: 'deepseek-v4-flash', ...FLASH_REPRICED_RATES },
|
|
78
|
+
// deepseek-v4.1-flash-expires-on-0910 was the V4.1 Flash preview route,
|
|
79
|
+
// retired when the model was released on 2026-09-10; its rows stay so the
|
|
80
|
+
// logs that used it keep pricing.
|
|
81
|
+
{ model: 'deepseek-v4.1-flash-expires-on-0910', ...FLASH_BASE_RATES },
|
|
82
|
+
{ model: 'deepseek-v4.1-flash-expires-on-0910', ...FLASH_REPRICED_RATES },
|
|
39
83
|
{
|
|
40
84
|
model: 'deepseek-v4-pro',
|
|
41
85
|
peak: { cacheHitInput: 0.30, cacheMissInput: 9.0, output: 27.0 },
|
|
42
86
|
offPeak: { cacheHitInput: 0.15, cacheMissInput: 4.5, output: 13.5 },
|
|
43
87
|
},
|
|
88
|
+
// From the announced route switch V4 Pro is served by V4.1 Flash and billed
|
|
89
|
+
// at the V4.1 Flash rates.
|
|
90
|
+
{ model: 'deepseek-v4-pro', ...V4_PRO_SWITCHED_RATES },
|
|
44
91
|
// deepseek-v4-flash-vision-exp bills at the same rates as deepseek-v4-flash;
|
|
45
92
|
// images are converted to tokens at the same per-token price.
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
|
|
49
|
-
offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
|
|
50
|
-
},
|
|
93
|
+
{ model: 'deepseek-v4-flash-vision-exp', ...FLASH_BASE_RATES },
|
|
94
|
+
{ model: 'deepseek-v4-flash-vision-exp', ...FLASH_REPRICED_RATES },
|
|
51
95
|
// MiMo-V2.5 series (Xiaomi): flat rate, no peak/off-peak distinction.
|
|
52
96
|
{
|
|
53
97
|
model: 'mimo-v2.5-pro',
|
|
@@ -66,8 +110,14 @@ export const DEFAULT_MODEL_PRICING = [
|
|
|
66
110
|
* `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
|
|
67
111
|
* selects the defaults. Explicit non-empty rows override the same model; a
|
|
68
112
|
* supplied non-empty `models` list is authoritative.
|
|
113
|
+
*
|
|
114
|
+
* Rows sharing a model are that model's rate revisions, kept in ascending
|
|
115
|
+
* `effectiveFrom` order (an undated base revision first). Two rows declaring
|
|
116
|
+
* the same effective instant are one revision and the later row wins — the
|
|
117
|
+
* historical override rule — so re-declaring a model can neither duplicate a
|
|
118
|
+
* revision nor install a second undated base.
|
|
69
119
|
* @param config - optional raw billing configuration.
|
|
70
|
-
* @returns the resolved table and peak-hour windows.
|
|
120
|
+
* @returns the resolved table (per model: its revisions plus the newest rates) and peak-hour windows.
|
|
71
121
|
*/
|
|
72
122
|
export function resolveBilling(config) {
|
|
73
123
|
const peakHours = config?.peakHours !== undefined && config.peakHours.length > 0
|
|
@@ -76,11 +126,48 @@ export function resolveBilling(config) {
|
|
|
76
126
|
const rows = config?.models !== undefined && config.models.length > 0
|
|
77
127
|
? config.models
|
|
78
128
|
: DEFAULT_MODEL_PRICING;
|
|
129
|
+
const schedules = new Map();
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
// Spelled out per branch: `exactOptionalPropertyTypes` forbids handing an
|
|
132
|
+
// explicit `undefined` to an optional field.
|
|
133
|
+
const revision = row.effectiveFrom === undefined
|
|
134
|
+
? { peak: row.peak, offPeak: row.offPeak }
|
|
135
|
+
: { effectiveFrom: row.effectiveFrom, peak: row.peak, offPeak: row.offPeak };
|
|
136
|
+
const revisions = schedules.get(row.model);
|
|
137
|
+
if (revisions === undefined) {
|
|
138
|
+
schedules.set(row.model, [revision]);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const duplicate = revisions.findIndex(candidate => candidate.effectiveFrom === revision.effectiveFrom);
|
|
142
|
+
if (duplicate >= 0)
|
|
143
|
+
revisions[duplicate] = revision;
|
|
144
|
+
else
|
|
145
|
+
revisions.push(revision);
|
|
146
|
+
}
|
|
79
147
|
const models = new Map();
|
|
80
|
-
for (const
|
|
81
|
-
|
|
148
|
+
for (const [model, revisions] of schedules) {
|
|
149
|
+
revisions.sort((left, right) => (left.effectiveFrom ?? Number.NEGATIVE_INFINITY) - (right.effectiveFrom ?? Number.NEGATIVE_INFINITY));
|
|
150
|
+
const newest = revisions[revisions.length - 1];
|
|
151
|
+
models.set(model, { peak: newest.peak, offPeak: newest.offPeak, revisions });
|
|
152
|
+
}
|
|
82
153
|
return { peakHours, models };
|
|
83
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The rate revision in effect at one instant: the newest revision that took
|
|
157
|
+
* effect at or before it. Revisions are ascending, so the scan stops at the
|
|
158
|
+
* first future one. An instant before the earliest dated revision bills at that
|
|
159
|
+
* earliest revision — a model with only dated rows is never left unpriced.
|
|
160
|
+
*/
|
|
161
|
+
function ratesAt(revisions, time) {
|
|
162
|
+
let chosen = revisions[0];
|
|
163
|
+
for (let index = 1; index < revisions.length; index += 1) {
|
|
164
|
+
const revision = revisions[index];
|
|
165
|
+
if (revision.effectiveFrom === undefined || revision.effectiveFrom > time)
|
|
166
|
+
break;
|
|
167
|
+
chosen = revision;
|
|
168
|
+
}
|
|
169
|
+
return chosen;
|
|
170
|
+
}
|
|
84
171
|
/** Beijing is a fixed UTC+8 offset with no DST. */
|
|
85
172
|
const BEIJING_OFFSET_MS = 8 * 3_600_000;
|
|
86
173
|
/** Milliseconds in one day. */
|
|
@@ -130,6 +217,7 @@ export function beijingPartsOf(time) {
|
|
|
130
217
|
const msOfDay = shifted - epochDay * DAY_MS;
|
|
131
218
|
const civil = civilDateOf(epochDay);
|
|
132
219
|
return {
|
|
220
|
+
time,
|
|
133
221
|
hour: Math.floor(msOfDay / 3_600_000),
|
|
134
222
|
// 1970-01-01 was a Thursday (4).
|
|
135
223
|
weekday: ((epochDay + 4) % 7 + 7) % 7,
|
|
@@ -189,7 +277,8 @@ export function isPeak(billing, now) {
|
|
|
189
277
|
/**
|
|
190
278
|
* Price one event at the official per-model rates, applying the peak/off-peak
|
|
191
279
|
* table by its Beijing-time hour and weekday (peak windows apply Monday–Friday
|
|
192
|
-
* only; weekends are off-peak)
|
|
280
|
+
* only; weekends are off-peak) and the rate revision in effect at its own
|
|
281
|
+
* timestamp. Each `assistant/message` event with usage
|
|
193
282
|
* contributes cache-hit input, cache-miss input (uncached input plus cache
|
|
194
283
|
* writes), and output (reasoning included) tokens at the rate of its own
|
|
195
284
|
* timestamp; a model with usage but no pricing row contributes nothing.
|
|
@@ -222,8 +311,10 @@ export function priceEventAt(parts, event, billing, names) {
|
|
|
222
311
|
}
|
|
223
312
|
/**
|
|
224
313
|
* Price one provider-reported usage sample for one model at the rates of the
|
|
225
|
-
* sample's own Beijing-time hour and weekday
|
|
226
|
-
*
|
|
314
|
+
* sample's own Beijing-time hour and weekday — the peak or off-peak price of
|
|
315
|
+
* the rate revision in effect at the sample's own timestamp (a re-priced series
|
|
316
|
+
* bills its history at the rates that applied then). `undefined` when the model
|
|
317
|
+
* has no pricing row.
|
|
227
318
|
* @param parts - the sample's Beijing-time view.
|
|
228
319
|
* @param usage - the reported token buckets.
|
|
229
320
|
* @param model - the wire model id the sample belongs to.
|
|
@@ -236,7 +327,8 @@ export function priceUsage(parts, usage, model, billing, names) {
|
|
|
236
327
|
if (pricing === undefined)
|
|
237
328
|
return undefined;
|
|
238
329
|
const peak = isPeakParts(billing, parts.hour, parts.weekday);
|
|
239
|
-
const
|
|
330
|
+
const revision = ratesAt(pricing.revisions, parts.time);
|
|
331
|
+
const price = peak ? revision.peak : revision.offPeak;
|
|
240
332
|
const hit = usage.cacheReadTokens ?? 0;
|
|
241
333
|
const miss = usage.inputTokens + (usage.cacheWriteTokens ?? 0);
|
|
242
334
|
const output = usage.outputTokens;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -24,8 +24,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
24
24
|
import z from '@deepseek-ai/schemastery';
|
|
25
25
|
import type { BillingConfig } from './billing.ts';
|
|
26
26
|
export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from './balance.ts';
|
|
27
|
-
export { addEventContribution, applyBillingEvent, beijingDayKey, BillingFolder, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyBillingFoldState, emptyTodaySpend, forkBoundaryOf, isPeak, isSeededSession, mergeTodaySpend, negateSpend, priceEvent, priceUsage, resolveBilling, SessionTurnSpendFolder, SpendAccumulator, subtractSpend, } from './billing.ts';
|
|
28
|
-
export type { BillingConfig, BillingConfigModel, BillingEventContribution, BillingFoldSample, BillingFoldState, DeepSeekModelPricing, DeepSeekTokenPrice, PeakHourWindow, ResolvedBilling, } from './billing.ts';
|
|
27
|
+
export { addEventContribution, applyBillingEvent, beijingDayKey, BillingFolder, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyBillingFoldState, emptyTodaySpend, FLASH_SERIES_RATE_CHANGE_AT, forkBoundaryOf, isPeak, isSeededSession, mergeTodaySpend, negateSpend, priceEvent, priceUsage, resolveBilling, SessionTurnSpendFolder, SpendAccumulator, subtractSpend, V4_PRO_ROUTE_SWITCH_AT, } from './billing.ts';
|
|
28
|
+
export type { BillingConfig, BillingConfigModel, BillingEventContribution, BillingFoldSample, BillingFoldState, DeepSeekModelPricing, DeepSeekRateRevision, DeepSeekTokenPrice, PeakHourWindow, ResolvedBilling, } from './billing.ts';
|
|
29
29
|
export type * from './types.ts';
|
|
30
30
|
export { BILLING_UNIT_KEY, billingTodaySpendDefinition, foldBillingUnit, foldOwnBilling } from './projection.ts';
|
|
31
31
|
export type { BillingUnitFold, BillingUnitState } from './projection.ts';
|
|
@@ -54,7 +54,12 @@ export interface Config {
|
|
|
54
54
|
baseURL?: string;
|
|
55
55
|
/** Advisory display rows, in presentation order; defaults to V4 Flash, V4.1 Flash, V4 Pro, and V4 Flash Vision Exp. */
|
|
56
56
|
models?: BillingModel[];
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Pricing table and peak-hour windows; omission uses the published defaults
|
|
59
|
+
* (including the V4 Flash series re-pricing effective 2026-09-10 12:00
|
|
60
|
+
* Beijing). Peak windows apply weekdays (Monday–Friday) only; weekends are
|
|
61
|
+
* always off-peak.
|
|
62
|
+
*/
|
|
58
63
|
billing?: BillingConfig;
|
|
59
64
|
}
|
|
60
65
|
export declare const Config: z<Config>;
|
package/lib/types/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import { computeSessionSpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_P
|
|
|
29
29
|
import { billingTodaySpendDefinition } from "./projection.js";
|
|
30
30
|
import { liveSessionEvents, persistenceInspect, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
|
|
31
31
|
export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from "./balance.js";
|
|
32
|
-
export { addEventContribution, applyBillingEvent, beijingDayKey, BillingFolder, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyBillingFoldState, emptyTodaySpend, forkBoundaryOf, isPeak, isSeededSession, mergeTodaySpend, negateSpend, priceEvent, priceUsage, resolveBilling, SessionTurnSpendFolder, SpendAccumulator, subtractSpend, } from "./billing.js";
|
|
32
|
+
export { addEventContribution, applyBillingEvent, beijingDayKey, BillingFolder, computeSessionSpend, computeSessionTurnSpends, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyBillingFoldState, emptyTodaySpend, FLASH_SERIES_RATE_CHANGE_AT, forkBoundaryOf, isPeak, isSeededSession, mergeTodaySpend, negateSpend, priceEvent, priceUsage, resolveBilling, SessionTurnSpendFolder, SpendAccumulator, subtractSpend, V4_PRO_ROUTE_SWITCH_AT, } from "./billing.js";
|
|
33
33
|
export { BILLING_UNIT_KEY, billingTodaySpendDefinition, foldBillingUnit, foldOwnBilling } from "./projection.js";
|
|
34
34
|
export { foldSessionTitle, liveSessionEvents, persistenceInspect, persistenceListSnapshots, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
|
|
35
35
|
export const name = 'llm-billing';
|
|
@@ -37,7 +37,15 @@ const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY';
|
|
|
37
37
|
const BASE_URL_ENV = 'DEEPSEEK_BASE_URL';
|
|
38
38
|
/** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
|
|
39
39
|
export const PUBLIC_BASE_URL = 'https://api.deepseek.com';
|
|
40
|
+
/**
|
|
41
|
+
* Advisory display rows mirroring the DSH `llm-deepseek` catalog (V4.1 Flash
|
|
42
|
+
* first, its current default route), plus the MiMo-V2.5 series. The retired
|
|
43
|
+
* preview id `deepseek-v4.1-flash-expires-on-0910` stays so the logs that used
|
|
44
|
+
* it keep a readable label; rows never restrict which models are priced — the
|
|
45
|
+
* pricing table does.
|
|
46
|
+
*/
|
|
40
47
|
const DEFAULT_MODELS = [
|
|
48
|
+
{ id: 'deepseek-flash', name: 'DeepSeek-V41-Flash' },
|
|
41
49
|
{ id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash' },
|
|
42
50
|
{ id: 'deepseek-v4.1-flash-expires-on-0910', name: 'DeepSeek-V4.1-Flash' },
|
|
43
51
|
{ id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
|
|
@@ -54,17 +62,22 @@ const tokenPrice = z.object({
|
|
|
54
62
|
cacheMissInput: z.number().min(0),
|
|
55
63
|
output: z.number().min(0),
|
|
56
64
|
});
|
|
65
|
+
const billingRateRow = z.object({
|
|
66
|
+
model: z.string().required(),
|
|
67
|
+
peak: tokenPrice,
|
|
68
|
+
offPeak: tokenPrice,
|
|
69
|
+
// Several rows may share one model: each is a rate revision, and samples are
|
|
70
|
+
// priced by the revision in effect at their own timestamp (epoch ms,
|
|
71
|
+
// inclusive).
|
|
72
|
+
effectiveFrom: z.number().min(0),
|
|
73
|
+
});
|
|
57
74
|
const billingConfig = z.object({
|
|
58
75
|
// Copies of the readonly published tables, taken once at module load.
|
|
59
76
|
peakHours: z.array(z.object({
|
|
60
77
|
start: z.number().step(1).min(0).max(23),
|
|
61
78
|
end: z.number().step(1).min(0).max(24),
|
|
62
79
|
})).default([...DEFAULT_PEAK_HOURS]),
|
|
63
|
-
models: z.array(
|
|
64
|
-
model: z.string().required(),
|
|
65
|
-
peak: tokenPrice,
|
|
66
|
-
offPeak: tokenPrice,
|
|
67
|
-
})).default([...DEFAULT_MODEL_PRICING]),
|
|
80
|
+
models: z.array(billingRateRow).default([...DEFAULT_MODEL_PRICING]),
|
|
68
81
|
});
|
|
69
82
|
export const Config = z.object({
|
|
70
83
|
apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),
|
|
@@ -48,13 +48,15 @@ export type BillingUnitDefinition = Omit<ProjectionDefinition<'billingTodaySpend
|
|
|
48
48
|
wire: NonNullable<ProjectionDefinition<'billingTodaySpend', BillingUnitState>['wire']>;
|
|
49
49
|
};
|
|
50
50
|
/**
|
|
51
|
-
* Build the `billingTodaySpend` unit for one resolved pricing table.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
51
|
+
* Build the `billingTodaySpend` unit for one resolved pricing table. Published
|
|
52
|
+
* rate revisions travel inside the closure and are resolved per sample
|
|
53
|
+
* timestamp, so a re-priced series bills its own history correctly however late
|
|
54
|
+
* a log is folded; only a configuration change (editing `billing.models`) is
|
|
55
|
+
* fixed at registration, and it re-prices just the events folded afterwards
|
|
56
|
+
* (the events-scan paths re-price the whole log). Bump
|
|
57
|
+
* {@link ProjectionDefinition.stateVersion} whenever the state shape or fold
|
|
58
|
+
* semantics change, so persisted checkpoint rows are discarded instead of
|
|
59
|
+
* folded forward.
|
|
58
60
|
* @param billing - resolved pricing with peak-hour windows.
|
|
59
61
|
* @param catalog - model display rows, in presentation order.
|
|
60
62
|
* @returns the unit definition to register on `ctx.sessionProjections`.
|
package/lib/types/projection.js
CHANGED
|
@@ -51,13 +51,15 @@ const billingUnitSchema = z.object({
|
|
|
51
51
|
}).strict().nullable(),
|
|
52
52
|
}).strict();
|
|
53
53
|
/**
|
|
54
|
-
* Build the `billingTodaySpend` unit for one resolved pricing table.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
54
|
+
* Build the `billingTodaySpend` unit for one resolved pricing table. Published
|
|
55
|
+
* rate revisions travel inside the closure and are resolved per sample
|
|
56
|
+
* timestamp, so a re-priced series bills its own history correctly however late
|
|
57
|
+
* a log is folded; only a configuration change (editing `billing.models`) is
|
|
58
|
+
* fixed at registration, and it re-prices just the events folded afterwards
|
|
59
|
+
* (the events-scan paths re-price the whole log). Bump
|
|
60
|
+
* {@link ProjectionDefinition.stateVersion} whenever the state shape or fold
|
|
61
|
+
* semantics change, so persisted checkpoint rows are discarded instead of
|
|
62
|
+
* folded forward.
|
|
61
63
|
* @param billing - resolved pricing with peak-hour windows.
|
|
62
64
|
* @param catalog - model display rows, in presentation order.
|
|
63
65
|
* @returns the unit definition to register on `ctx.sessionProjections`.
|
|
@@ -66,11 +68,13 @@ export function billingTodaySpendDefinition(billing, catalog) {
|
|
|
66
68
|
const names = new Map(catalog.map(model => [model.id, model.name]));
|
|
67
69
|
return {
|
|
68
70
|
key: BILLING_UNIT_KEY,
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// boundary-aware fold and whole-session total
|
|
72
|
-
//
|
|
73
|
-
|
|
71
|
+
// v4: rate revisions resolved per sample timestamp (the V4 Flash series is
|
|
72
|
+
// re-priced from 2026-09-10 12:00 Beijing), on top of v3's DSH-aligned
|
|
73
|
+
// attempt pricing, v2's boundary-aware fold, and the whole-session total.
|
|
74
|
+
// A row checkpointed by the previous version priced every sample at one
|
|
75
|
+
// flat pair of rates, so rows folded after the re-pricing instant would
|
|
76
|
+
// keep the superseded rates: bumping discards them and refolds.
|
|
77
|
+
stateVersion: 4,
|
|
74
78
|
stateSchema: billingUnitSchema,
|
|
75
79
|
init: (_header, inheritedEventCount) => emptyBillingFoldState(Number(inheritedEventCount ?? 0)),
|
|
76
80
|
apply: (state, event) => applyBillingEvent(state, event, billing, names),
|
package/lib/types/types.d.ts
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
* and weekday, with peak windows Monday–Friday 09:00–12:00 / 14:00–18:00 and
|
|
8
8
|
* weekends always off-peak; cache-hit input, cache-miss input (including
|
|
9
9
|
* cache writes), and output (including reasoning) are billed separately at
|
|
10
|
-
* per-1M-token rates
|
|
11
|
-
* the
|
|
10
|
+
* per-1M-token rates, taking the rate revision in effect at that same instant
|
|
11
|
+
* (the flash series was re-priced from 2026-09-10 12:00 Beijing, its earlier
|
|
12
|
+
* samples keeping the superseded rates, and V4 Pro follows the V4.1 Flash
|
|
13
|
+
* rates from its announced route switch on 2026-09-14 12:00 Beijing). The
|
|
14
|
+
* published table prices the DeepSeek V4 rows and the MiMo-V2.5 series (flat
|
|
15
|
+
* rate, no peak/off-peak distinction).
|
|
12
16
|
* @module @rayadesu/dsh-llm-billing/types
|
|
13
17
|
*/
|
|
14
18
|
import type { SessionId } from '@deepseek-ai/dsh-session';
|
package/lib/types/types.js
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
* and weekday, with peak windows Monday–Friday 09:00–12:00 / 14:00–18:00 and
|
|
8
8
|
* weekends always off-peak; cache-hit input, cache-miss input (including
|
|
9
9
|
* cache writes), and output (including reasoning) are billed separately at
|
|
10
|
-
* per-1M-token rates
|
|
11
|
-
* the
|
|
10
|
+
* per-1M-token rates, taking the rate revision in effect at that same instant
|
|
11
|
+
* (the flash series was re-priced from 2026-09-10 12:00 Beijing, its earlier
|
|
12
|
+
* samples keeping the superseded rates, and V4 Pro follows the V4.1 Flash
|
|
13
|
+
* rates from its announced route switch on 2026-09-14 12:00 Beijing). The
|
|
14
|
+
* published table prices the DeepSeek V4 rows and the MiMo-V2.5 series (flat
|
|
15
|
+
* rate, no peak/off-peak distinction).
|
|
12
16
|
* @module @rayadesu/dsh-llm-billing/types
|
|
13
17
|
*/
|
|
14
18
|
export {};
|
package/package.json
CHANGED