@rayadesu/dsh-llm-billing 0.2.3 → 0.3.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: 471bb8364f6229ce3a6c48287fe660a92ead0281
6
- README.zh.md: 9346896747a389b4aaf80400b19df8e0ff12961e
5
+ README.md: 9fc18328397e8824803ad7ed5335ee4b8a1063fe
6
+ README.zh.md: 330b3ae531177a9b934137b4c2d1e32f949c16e0
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(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). 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.
19
+ The plugin registers the `billing` Remote with five methods: `getBalance()` (the parsed `/user/balance` snapshot), `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), and `getTurnSpend(sessionId, messageId)` (one completed turn's billed cost, located by its closing assistant message id). 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. 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
 
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(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)。会话花费把每条 `assistant/message` 事件的计费 token(缓存命中输入、含缓存写入的未命中输入、含推理的输出)按事件自身发生时刻(北京时间)所在的峰/谷单价计价——高峰窗口仅周一至周五适用,周末全天按低谷价——再按模型汇总。
19
+ 插件注册 `billing` Remote,含五个方法:`getBalance()`(解析后的 `/user/balance` 快照)、`getSessionSpend(sessionId)`(单个会话的计费花费)、`getTodaySpend(force?)`(当前北京时间自然日内所有会话的计费花费合计;`force` 绕过宿主侧缓存,供徽标手动刷新使用)、`getTodaySessionsSpend(force?)`(今日按会话的计费花费,按花费从高到低排序,每行带会话的持久标题)与 `getTurnSpend(sessionId, messageId)`(单个已完成回合的计费花费,按收尾助手消息 id 定位)。会话花费把每条 `assistant/message` 事件的计费 token(缓存命中输入、含缓存写入的未命中输入、含推理的输出)按事件自身发生时刻(北京时间)所在的峰/谷单价计价——高峰窗口仅周一至周五适用,周末全天按低谷价——再按模型汇总。一个回合即收尾消息所在的 `turn/start`..`turn/end` 区间;排行从每个会话日志里最后一条 `session/title` 事件折叠标题(last-wins,重命名事件一旦提交、会话被重新读取即反映新名字)。
20
20
 
21
21
  ### 今日花费读取路径(消息触发不再全量扫描)
22
22
 
package/lib/index.js CHANGED
@@ -135,12 +135,16 @@ let DeepSeekBalanceGateway = (() => {
135
135
  let _getBalance_decorators;
136
136
  let _getSessionSpend_decorators;
137
137
  let _getTodaySpend_decorators;
138
+ let _getTodaySessionsSpend_decorators;
139
+ let _getTurnSpend_decorators;
138
140
  return class DeepSeekBalanceGateway extends _classSuper {
139
141
  static {
140
142
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
141
143
  _getBalance_decorators = [Remote("getBalance")];
142
144
  _getSessionSpend_decorators = [Remote("getSessionSpend")];
143
145
  _getTodaySpend_decorators = [Remote("getTodaySpend")];
146
+ _getTodaySessionsSpend_decorators = [Remote("getTodaySessionsSpend")];
147
+ _getTurnSpend_decorators = [Remote("getTurnSpend")];
144
148
  __esDecorate(this, null, _getBalance_decorators, {
145
149
  kind: "method",
146
150
  name: "getBalance",
@@ -174,6 +178,28 @@ let DeepSeekBalanceGateway = (() => {
174
178
  },
175
179
  metadata: _metadata
176
180
  }, null, _instanceExtraInitializers);
181
+ __esDecorate(this, null, _getTodaySessionsSpend_decorators, {
182
+ kind: "method",
183
+ name: "getTodaySessionsSpend",
184
+ static: false,
185
+ private: false,
186
+ access: {
187
+ has: (obj) => "getTodaySessionsSpend" in obj,
188
+ get: (obj) => obj.getTodaySessionsSpend
189
+ },
190
+ metadata: _metadata
191
+ }, null, _instanceExtraInitializers);
192
+ __esDecorate(this, null, _getTurnSpend_decorators, {
193
+ kind: "method",
194
+ name: "getTurnSpend",
195
+ static: false,
196
+ private: false,
197
+ access: {
198
+ has: (obj) => "getTurnSpend" in obj,
199
+ get: (obj) => obj.getTurnSpend
200
+ },
201
+ metadata: _metadata
202
+ }, null, _instanceExtraInitializers);
177
203
  if (_metadata) Object.defineProperty(this, Symbol.metadata, {
178
204
  enumerable: true,
179
205
  configurable: true,
@@ -218,6 +244,29 @@ let DeepSeekBalanceGateway = (() => {
218
244
  getTodaySpend(force) {
219
245
  return this.options.fetchTodaySpend(force ?? false);
220
246
  }
247
+ /**
248
+ * Read today's billed spend per session, priced per event by its
249
+ * Beijing-time calendar day, hour, and weekday (weekends are always
250
+ * off-peak). Rows carry the session's durable title and sort by cost
251
+ * descending; sessions with no priced usage on the day are omitted.
252
+ * @param force - bypass the host-side 60s cache (manual refresh); omitted
253
+ * means a cached read.
254
+ * @returns today's per-session rows, highest first.
255
+ */
256
+ getTodaySessionsSpend(force) {
257
+ return this.options.fetchTodaySessionsSpend(force ?? false);
258
+ }
259
+ /**
260
+ * Read one completed Turn's billed spend, priced per event by its
261
+ * Beijing-time hour and weekday (weekends are always off-peak).
262
+ * @param sessionId - the session owning the Turn.
263
+ * @param messageId - the closing assistant message's durable id, which
264
+ * locates the Turn in the session log.
265
+ * @returns the Turn's total cost in CNY.
266
+ */
267
+ getTurnSpend(sessionId, messageId) {
268
+ return this.options.fetchTurnSpend(sessionId, messageId);
269
+ }
221
270
  };
222
271
  })();
223
272
  //#endregion
@@ -527,6 +576,44 @@ function computeSessionSpend(events, billing, catalog) {
527
576
  return priceEvents(events, billing, new Map(catalog.map((model) => [model.id, model.name])));
528
577
  }
529
578
  /**
579
+ * Price one completed Turn's billed usage at the official per-model rates,
580
+ * identified by its closing assistant message id. The turn's events are those
581
+ * between its `turn/start` and `turn/end` (both matched by the message's own
582
+ * turn coordinate); each priced event applies the peak/off-peak table by its
583
+ * Beijing-time hour and weekday. A message that cannot be located, a turn
584
+ * without bracketing `turn/start` / `turn/end` events (for example after
585
+ * compaction), or a session with no priced usage prices to zero.
586
+ * @param events - one session's complete event log.
587
+ * @param billing - resolved pricing with peak-hour windows.
588
+ * @param catalog - model display rows, in presentation order.
589
+ * @param messageId - the closing assistant message's durable id.
590
+ * @returns the turn's total cost in CNY.
591
+ */
592
+ function computeTurnSpend(events, billing, catalog, messageId) {
593
+ const names = new Map(catalog.map((model) => [model.id, model.name]));
594
+ let turn;
595
+ for (const event of events) {
596
+ if (event.type !== "assistant/message") continue;
597
+ if (event.data.message.id !== messageId) continue;
598
+ turn = event.data.turn;
599
+ break;
600
+ }
601
+ if (turn === void 0) return { total: 0 };
602
+ const accumulator = new SpendAccumulator();
603
+ let active = false;
604
+ for (const event of events) {
605
+ if (event.type === "turn/start" && event.data.turn === turn) {
606
+ active = true;
607
+ continue;
608
+ }
609
+ if (event.type === "turn/end" && event.data.turn === turn) break;
610
+ if (!active) continue;
611
+ const priced = priceEvent(event, billing, names);
612
+ if (priced !== void 0) accumulator.add(priced);
613
+ }
614
+ return { total: accumulator.finish().total };
615
+ }
616
+ /**
530
617
  * Price every event whose Beijing-time calendar day is the day of `now`,
531
618
  * aggregating across every session's event log. Events from other Beijing
532
619
  * days are ignored, so a caller passes the concatenated logs of all sessions.
@@ -653,6 +740,23 @@ function foldBillingUnit(unit, events) {
653
740
  * @module @rayadesu/dsh-llm-billing/today-spend
654
741
  */
655
742
  /**
743
+ * Fold one session's durable display title: the latest `session/title`
744
+ * event's text (last-wins, matching the `title` projection), or `null` before
745
+ * the first title lands. The fold runs over the complete log, so an explicit
746
+ * user rename is picked up as soon as its event commits.
747
+ * @param events - one session's complete event log.
748
+ * @returns the session's current title, or `null` when untitled.
749
+ */
750
+ function foldSessionTitle(events) {
751
+ for (let index = events.length - 1; index >= 0; index--) {
752
+ const event = events[index];
753
+ if (event.type !== "session/title") continue;
754
+ const data = event.data;
755
+ return typeof data.title === "string" ? data.title : null;
756
+ }
757
+ return null;
758
+ }
759
+ /**
656
760
  * Bounded parallel fan-out: run `run` over `items` with at most `limit` in
657
761
  * flight. A shared index counter hands each worker its next job, so the
658
762
  * dispatch is O(n) overall (array `shift()` would be O(n) per pop).
@@ -672,6 +776,8 @@ async function withConcurrency(items, limit, run) {
672
776
  * coalesces concurrent misses, and a `force` bypass for the manual refresh
673
777
  * path. Cross-day invalidation is automatic (the day key changes); a failed
674
778
  * scan leaves the previous value in place and retries on the next call.
779
+ * @typeParam T - the cached aggregate's value shape (the spend or its
780
+ * per-session breakdown).
675
781
  */
676
782
  var TodaySpendCache = class {
677
783
  scan;
@@ -682,9 +788,9 @@ var TodaySpendCache = class {
682
788
  cachedAt = 0;
683
789
  inFlight;
684
790
  /**
791
+ * @param scan - the aggregate computation behind a miss.
685
792
  * @param ttlMs - time window in milliseconds (default 60 000).
686
793
  * @param now - clock source (injectable for tests).
687
- * @param scan - the aggregate computation behind a miss.
688
794
  */
689
795
  constructor(scan, ttlMs = 6e4, now = () => /* @__PURE__ */ new Date()) {
690
796
  this.scan = scan;
@@ -725,7 +831,7 @@ var TodaySpendCache = class {
725
831
  */
726
832
  var TodaySpendScanner = class {
727
833
  deps;
728
- /** Cold sessions resolved on the projection path: id → revision + unit state. */
834
+ /** Cold sessions resolved on the projection path: id → revision + unit state + title. */
729
835
  coldResolved = /* @__PURE__ */ new Map();
730
836
  /** Cold sessions resolved on the events path: id → revision (events were collected). */
731
837
  lastEventsScan;
@@ -742,11 +848,56 @@ var TodaySpendScanner = class {
742
848
  this.deps.ensureUnit?.();
743
849
  return this.scanProjections(dayKey);
744
850
  }
851
+ /**
852
+ * Compute today's per-session spend for one Beijing day, sorted by cost
853
+ * descending. Sessions with no priced usage on the day are omitted; each
854
+ * row carries the session's durable title folded from its log.
855
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
856
+ * @returns today's per-session rows, highest first.
857
+ */
858
+ async scanSessions(dayKey) {
859
+ const rows = this.deps.projections?.() === void 0 ? await this.scanSessionsEvents(dayKey) : await this.scanSessionsProjections(dayKey);
860
+ rows.sort((left, right) => right.total - left.total);
861
+ return { sessions: rows };
862
+ }
863
+ /**
864
+ * Resolve one cold session's billing unit state and display title through
865
+ * the projection-cache ladder (cached row first, then a detached local
866
+ * fold over a full inspect). A cache-served value carries no title (the
867
+ * ladder only stores projection values), so such rows report `title: null`
868
+ * until the session is inspected again.
869
+ * @param id - the cold session's id.
870
+ * @returns the resolved state and title, or `undefined` when unreadable.
871
+ */
872
+ async resolveCold(id) {
873
+ const { persistence, projectionCache, unit, logger } = this.deps;
874
+ const cache = projectionCache?.();
875
+ if (cache !== void 0) try {
876
+ const value = (await cache.coldSnapshot(id)).values[BILLING_UNIT_KEY];
877
+ if (value !== void 0) return {
878
+ value,
879
+ title: null
880
+ };
881
+ } catch (error) {
882
+ logger.warn(`llm-billing: projection cold read for session ${id} failed: ${String(error)}`);
883
+ }
884
+ const persistenceService = persistence?.();
885
+ if (persistenceService === void 0) return void 0;
886
+ try {
887
+ const inspection = await persistenceService.inspect(id);
888
+ return {
889
+ value: foldBillingUnit(unit, inspection.events),
890
+ title: foldSessionTitle(inspection.events)
891
+ };
892
+ } catch (error) {
893
+ logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
894
+ return;
895
+ }
896
+ }
745
897
  /** Projection path: eager cells for live sessions, revision-gated cold ladder for the rest. */
746
898
  async scanProjections(dayKey) {
747
- const { sessions, persistence, projections, projectionCache, unit, logger } = this.deps;
899
+ const { sessions, persistence, projections } = this.deps;
748
900
  const projectionsService = projections?.();
749
- const cache = projectionCache?.();
750
901
  let total = emptyTodaySpend();
751
902
  const liveIds = /* @__PURE__ */ new Set();
752
903
  if (sessions !== void 0) {
@@ -774,21 +925,10 @@ var TodaySpendScanner = class {
774
925
  });
775
926
  }
776
927
  await withConcurrency(pending, 8, async ({ id, revision }) => {
777
- let value;
778
- if (cache !== void 0) try {
779
- value = (await cache.coldSnapshot(id)).values[BILLING_UNIT_KEY];
780
- } catch (error) {
781
- logger.warn(`llm-billing: projection cold read for session ${id} failed: ${String(error)}`);
782
- }
783
- if (value === void 0) try {
784
- const inspection = await persistenceService.inspect(id);
785
- value = foldBillingUnit(unit, inspection.events);
786
- } catch (error) {
787
- logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
788
- }
789
- if (value !== void 0) this.coldResolved.set(id, {
928
+ const resolved = await this.resolveCold(id);
929
+ if (resolved !== void 0) this.coldResolved.set(id, {
790
930
  revision,
791
- value
931
+ ...resolved
792
932
  });
793
933
  });
794
934
  for (const { id } of pending) {
@@ -846,6 +986,134 @@ var TodaySpendScanner = class {
846
986
  if (truncated) logger.warn(`llm-billing: today's events exceeded ${maxEvents}; result truncated`);
847
987
  return accumulator.finish();
848
988
  }
989
+ /**
990
+ * Projection-path per-session scan: eager cells for live sessions (title
991
+ * folded from the live log, so a rename is reflected immediately),
992
+ * revision-gated cold ladder for the rest (title resolved on inspect,
993
+ * `null` when served from the projection cache).
994
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
995
+ * @returns unsorted per-session rows for the day.
996
+ */
997
+ async scanSessionsProjections(dayKey) {
998
+ const { sessions, persistence, projections } = this.deps;
999
+ const projectionsService = projections?.();
1000
+ const rows = /* @__PURE__ */ new Map();
1001
+ const liveIds = /* @__PURE__ */ new Set();
1002
+ if (sessions !== void 0) {
1003
+ const store = sessions();
1004
+ if (store !== void 0) for (const session of store.list()) {
1005
+ liveIds.add(session.id);
1006
+ const state = projectionsService?.stateOf(session, BILLING_UNIT_KEY);
1007
+ if (state !== void 0 && state.dayKey === dayKey) rows.set(session.id, {
1008
+ sessionId: session.id,
1009
+ title: foldSessionTitle(session.events),
1010
+ total: state.spend.total
1011
+ });
1012
+ }
1013
+ }
1014
+ const persistenceService = persistence?.();
1015
+ if (persistenceService === void 0) return [...rows.values()];
1016
+ const snapshots = await persistenceService.listSnapshots();
1017
+ const pending = [];
1018
+ for (const { header, revision } of snapshots) {
1019
+ if (liveIds.has(header.id)) continue;
1020
+ const resolved = this.coldResolved.get(header.id);
1021
+ if (resolved !== void 0 && resolved.revision === revision) {
1022
+ if (resolved.value.dayKey === dayKey) rows.set(header.id, {
1023
+ sessionId: header.id,
1024
+ title: resolved.title,
1025
+ total: resolved.value.spend.total
1026
+ });
1027
+ continue;
1028
+ }
1029
+ pending.push({
1030
+ id: header.id,
1031
+ revision
1032
+ });
1033
+ }
1034
+ await withConcurrency(pending, 8, async ({ id, revision }) => {
1035
+ const resolved = await this.resolveCold(id);
1036
+ if (resolved !== void 0) this.coldResolved.set(id, {
1037
+ revision,
1038
+ ...resolved
1039
+ });
1040
+ });
1041
+ for (const { id } of pending) {
1042
+ const resolved = this.coldResolved.get(id);
1043
+ if (resolved !== void 0 && resolved.value.dayKey === dayKey) rows.set(id, {
1044
+ sessionId: id,
1045
+ title: resolved.title,
1046
+ total: resolved.value.spend.total
1047
+ });
1048
+ }
1049
+ return [...rows.values()];
1050
+ }
1051
+ /**
1052
+ * Events-path per-session scan: price today's events in a single pass,
1053
+ * accumulating per session (per-event Beijing-day filter during collection,
1054
+ * hard cap), gated by revisions. Titles fold from each session's complete
1055
+ * log — a `session/title` event can predate today — so a rename is reflected
1056
+ * as soon as the session's log is re-read.
1057
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
1058
+ * @returns unsorted per-session rows for the day.
1059
+ */
1060
+ async scanSessionsEvents(dayKey) {
1061
+ const { sessions, persistence, maxEvents, logger, billing, catalog } = this.deps;
1062
+ const names = new Map(catalog.map((model) => [model.id, model.name]));
1063
+ const rows = /* @__PURE__ */ new Map();
1064
+ const liveIds = /* @__PURE__ */ new Set();
1065
+ let collected = 0;
1066
+ let truncated = false;
1067
+ const collect = (id, events) => {
1068
+ let row = rows.get(id);
1069
+ if (row === void 0) {
1070
+ row = {
1071
+ title: foldSessionTitle(events),
1072
+ total: 0
1073
+ };
1074
+ rows.set(id, row);
1075
+ }
1076
+ for (const event of events) {
1077
+ if (beijingDayKey(new Date(event.time)) !== dayKey) continue;
1078
+ collected += 1;
1079
+ if (collected > maxEvents) {
1080
+ truncated = true;
1081
+ return;
1082
+ }
1083
+ const priced = priceEvent(event, billing, names);
1084
+ if (priced !== void 0) row.total += priced.cost;
1085
+ }
1086
+ };
1087
+ if (sessions !== void 0) {
1088
+ const store = sessions();
1089
+ if (store !== void 0) for (const session of store.list()) {
1090
+ liveIds.add(session.id);
1091
+ collect(session.id, session.events);
1092
+ if (truncated) break;
1093
+ }
1094
+ }
1095
+ const persistenceService = persistence?.();
1096
+ if (!truncated && persistenceService !== void 0) {
1097
+ const snapshots = await persistenceService.listSnapshots();
1098
+ for (const { header, revision } of snapshots) {
1099
+ if (liveIds.has(header.id)) continue;
1100
+ if (this.lastEventsScan?.get(header.id) === revision) continue;
1101
+ try {
1102
+ collect(header.id, (await persistenceService.inspect(header.id)).events);
1103
+ } catch (error) {
1104
+ logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
1105
+ }
1106
+ if (truncated) break;
1107
+ }
1108
+ if (!truncated) this.lastEventsScan = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot.revision]));
1109
+ }
1110
+ if (truncated) logger.warn(`llm-billing: today's events exceeded ${maxEvents}; result truncated`);
1111
+ return [...rows.entries()].filter(([, row]) => row.total > 0).map(([sessionId, row]) => ({
1112
+ sessionId,
1113
+ title: row.title,
1114
+ total: row.total
1115
+ }));
1116
+ }
849
1117
  };
850
1118
  //#endregion
851
1119
  //#region lib/types/index.js
@@ -1011,12 +1279,19 @@ function apply(ctx, config) {
1011
1279
  catalog
1012
1280
  });
1013
1281
  const todayCache = new TodaySpendCache((dayKey) => scanner.scan(dayKey), TODAY_SPEND_CACHE_MS);
1282
+ const todaySessionsCache = new TodaySpendCache((dayKey) => scanner.scanSessions(dayKey), TODAY_SPEND_CACHE_MS);
1014
1283
  const fetchTodaySpend = async (force = false) => todayCache.get(force);
1284
+ const fetchTodaySessionsSpend = async (force = false) => todaySessionsCache.get(force);
1285
+ const fetchTurnSpend = async (sessionId, messageId) => {
1286
+ return computeTurnSpend(await sessionEvents(ctx, sessionId), billing, catalog, messageId);
1287
+ };
1015
1288
  new DeepSeekBalanceGateway(ctx, {
1016
1289
  fetchBalance,
1017
1290
  fetchSessionSpend,
1018
- fetchTodaySpend
1291
+ fetchTodaySpend,
1292
+ fetchTodaySessionsSpend,
1293
+ fetchTurnSpend
1019
1294
  });
1020
1295
  }
1021
1296
  //#endregion
1022
- export { BILLING_UNIT_KEY, Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, PUBLIC_BASE_URL, SpendAccumulator, TODAY_SPEND_CACHE_MS, TODAY_SPEND_MAX_EVENTS, TodaySpendCache, TodaySpendScanner, addEventContribution, apply, beijingDayKey, billingTodaySpendDefinition, computeSessionSpend, computeTodaySpend, emptyTodaySpend, fetchDeepSeekBalance, foldBillingUnit, isPeak, mergeTodaySpend, name, parseDeepSeekBalance, priceEvent, resolveBilling };
1297
+ export { BILLING_UNIT_KEY, Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, PUBLIC_BASE_URL, SpendAccumulator, TODAY_SPEND_CACHE_MS, TODAY_SPEND_MAX_EVENTS, TodaySpendCache, TodaySpendScanner, addEventContribution, apply, beijingDayKey, billingTodaySpendDefinition, computeSessionSpend, computeTodaySpend, computeTurnSpend, emptyTodaySpend, fetchDeepSeekBalance, foldBillingUnit, foldSessionTitle, isPeak, mergeTodaySpend, name, parseDeepSeekBalance, priceEvent, resolveBilling };
@@ -27,6 +27,14 @@ const _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema = z.object
27
27
  'outputCost': z.number(),
28
28
  })),
29
29
  })
30
+ const _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema = z.union([z.undefined(), z.literal(false), z.literal(true)])
31
+ const _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema = z.object({
32
+ 'sessions': z.array(z.object({
33
+ 'sessionId': z.intersection(z.string(), z.unknown()),
34
+ 'title': z.union([z.literal(null), z.string()]),
35
+ 'total': z.number(),
36
+ })),
37
+ })
30
38
  const _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema = z.union([z.undefined(), z.literal(false), z.literal(true)])
31
39
  const _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema = z.object({
32
40
  'total': z.number(),
@@ -44,6 +52,11 @@ const _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema = z.object({
44
52
  'outputCost': z.number(),
45
53
  })),
46
54
  })
55
+ const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema = z.intersection(z.string(), z.unknown())
56
+ const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema = z.string()
57
+ const _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema = z.object({
58
+ 'total': z.number(),
59
+ })
47
60
 
48
61
  export const TYPERT = {
49
62
  package: '@rayadesu/dsh-llm-billing',
@@ -64,7 +77,7 @@ export const TYPERT = {
64
77
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekBalance',
65
78
  schema: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema,
66
79
  },
67
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":143,"column":3},
80
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":154,"column":3},
68
81
  },
69
82
  {
70
83
  id: '@rayadesu/dsh-llm-billing#billing/getSessionSpend',
@@ -89,7 +102,33 @@ export const TYPERT = {
89
102
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionSpend',
90
103
  schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema,
91
104
  },
92
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":154,"column":3},
105
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":165,"column":3},
106
+ },
107
+ {
108
+ id: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend',
109
+ service: 'billing',
110
+ namespace: 'billing',
111
+ method: 'getTodaySessionsSpend',
112
+ invocation: { kind: 'direct' },
113
+ parameters: [
114
+ {
115
+ name: 'force',
116
+ wire: 'force',
117
+ source: 'json',
118
+ acceptsUndefined: true,
119
+ codec: {
120
+ mode: 'strict',
121
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend:force',
122
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema,
123
+ },
124
+ },
125
+ ],
126
+ result: {
127
+ mode: 'strict',
128
+ typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend',
129
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema,
130
+ },
131
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":192,"column":3},
93
132
  },
94
133
  {
95
134
  id: '@rayadesu/dsh-llm-billing#billing/getTodaySpend',
@@ -115,7 +154,42 @@ export const TYPERT = {
115
154
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySpend',
116
155
  schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema,
117
156
  },
118
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":167,"column":3},
157
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":178,"column":3},
158
+ },
159
+ {
160
+ id: '@rayadesu/dsh-llm-billing#billing/getTurnSpend',
161
+ service: 'billing',
162
+ namespace: 'billing',
163
+ method: 'getTurnSpend',
164
+ invocation: { kind: 'direct' },
165
+ parameters: [
166
+ {
167
+ name: 'sessionId',
168
+ wire: 'sessionId',
169
+ source: 'json',
170
+ codec: {
171
+ mode: 'strict',
172
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTurnSpend:sessionId',
173
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema,
174
+ },
175
+ },
176
+ {
177
+ name: 'messageId',
178
+ wire: 'messageId',
179
+ source: 'json',
180
+ codec: {
181
+ mode: 'strict',
182
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTurnSpend:messageId',
183
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema,
184
+ },
185
+ },
186
+ ],
187
+ result: {
188
+ mode: 'strict',
189
+ typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend',
190
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema,
191
+ },
192
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":205,"column":3},
119
193
  },
120
194
  ],
121
195
  model: {
@@ -3,18 +3,22 @@ import type {
3
3
  RemoteResult,
4
4
  TypertRemoteContribution,
5
5
  } from '@deepseek-ai/dsh-typert-protocol'
6
- import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySpend } from '@rayadesu/dsh-llm-billing/types'
6
+ import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySessionsSpend, DeepSeekTodaySpend, DeepSeekTurnSpend } from '@rayadesu/dsh-llm-billing/types'
7
7
 
8
8
  declare module '@deepseek-ai/dsh-typert-protocol' {
9
9
  interface TypertRemoteNamespace$62696c6c696e67 {
10
10
  getBalance: () => Promise<RemoteResult<DeepSeekBalance>>
11
11
  getSessionSpend: (sessionId: SessionId) => Promise<RemoteResult<DeepSeekSessionSpend>>
12
+ getTodaySessionsSpend: (force?: boolean) => Promise<RemoteResult<DeepSeekTodaySessionsSpend>>
12
13
  getTodaySpend: (force?: boolean) => Promise<RemoteResult<DeepSeekTodaySpend>>
14
+ getTurnSpend: (sessionId: SessionId, messageId: string) => Promise<RemoteResult<DeepSeekTurnSpend>>
13
15
  }
14
16
  interface TypertRemoteMap {
15
17
  'billing/getBalance': () => Promise<RemoteResult<DeepSeekBalance>>
16
18
  'billing/getSessionSpend': (sessionId: SessionId) => Promise<RemoteResult<DeepSeekSessionSpend>>
19
+ 'billing/getTodaySessionsSpend': (force?: boolean) => Promise<RemoteResult<DeepSeekTodaySessionsSpend>>
17
20
  'billing/getTodaySpend': (force?: boolean) => Promise<RemoteResult<DeepSeekTodaySpend>>
21
+ 'billing/getTurnSpend': (sessionId: SessionId, messageId: string) => Promise<RemoteResult<DeepSeekTurnSpend>>
18
22
  }
19
23
  interface TypertRemoteNamespaceMap {
20
24
  'billing': TypertRemoteNamespace$62696c6c696e67