@rayadesu/dsh-llm-billing 0.2.3 → 0.2.4

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.
@@ -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_REMOTE = {
49
62
  package: '@rayadesu/dsh-llm-billing',
@@ -61,7 +74,7 @@ export const TYPERT_REMOTE = {
61
74
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekBalance',
62
75
  schema: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema,
63
76
  },
64
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":143,"column":3},
77
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":154,"column":3},
65
78
  },
66
79
  {
67
80
  id: '@rayadesu/dsh-llm-billing#billing/getSessionSpend',
@@ -86,7 +99,33 @@ export const TYPERT_REMOTE = {
86
99
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionSpend',
87
100
  schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema,
88
101
  },
89
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":154,"column":3},
102
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":165,"column":3},
103
+ },
104
+ {
105
+ id: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend',
106
+ service: 'billing',
107
+ namespace: 'billing',
108
+ method: 'getTodaySessionsSpend',
109
+ invocation: { kind: 'direct' },
110
+ parameters: [
111
+ {
112
+ name: 'force',
113
+ wire: 'force',
114
+ source: 'json',
115
+ acceptsUndefined: true,
116
+ codec: {
117
+ mode: 'strict',
118
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend:force',
119
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema,
120
+ },
121
+ },
122
+ ],
123
+ result: {
124
+ mode: 'strict',
125
+ typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend',
126
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema,
127
+ },
128
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":192,"column":3},
90
129
  },
91
130
  {
92
131
  id: '@rayadesu/dsh-llm-billing#billing/getTodaySpend',
@@ -112,7 +151,42 @@ export const TYPERT_REMOTE = {
112
151
  typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySpend',
113
152
  schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema,
114
153
  },
115
- sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":167,"column":3},
154
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":178,"column":3},
155
+ },
156
+ {
157
+ id: '@rayadesu/dsh-llm-billing#billing/getTurnSpend',
158
+ service: 'billing',
159
+ namespace: 'billing',
160
+ method: 'getTurnSpend',
161
+ invocation: { kind: 'direct' },
162
+ parameters: [
163
+ {
164
+ name: 'sessionId',
165
+ wire: 'sessionId',
166
+ source: 'json',
167
+ codec: {
168
+ mode: 'strict',
169
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTurnSpend:sessionId',
170
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema,
171
+ },
172
+ },
173
+ {
174
+ name: 'messageId',
175
+ wire: 'messageId',
176
+ source: 'json',
177
+ codec: {
178
+ mode: 'strict',
179
+ typeSymbol: '@rayadesu/dsh-llm-billing#billing/getTurnSpend:messageId',
180
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema,
181
+ },
182
+ },
183
+ ],
184
+ result: {
185
+ mode: 'strict',
186
+ typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend',
187
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema,
188
+ },
189
+ sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":205,"column":3},
116
190
  },
117
191
  ],
118
192
  }
@@ -9,7 +9,7 @@
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
10
  import type { SessionId } from '@deepseek-ai/dsh-session';
11
11
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
12
- import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySpend } from './types.ts';
12
+ import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySessionsSpend, DeepSeekTodaySpend, DeepSeekTurnSpend } from './types.ts';
13
13
  /**
14
14
  * Parse and validate the DeepSeek balance response at the wire boundary.
15
15
  * @param body - decoded JSON response body.
@@ -38,6 +38,17 @@ export interface DeepSeekBalanceGatewayOptions {
38
38
  * refresh path); a `force` miss still reuses revision-gated increments.
39
39
  */
40
40
  fetchTodaySpend: (force?: boolean) => Promise<DeepSeekTodaySpend>;
41
+ /**
42
+ * Compute today's billed spend per session through the plugin's resolved
43
+ * facts, sorted by cost descending. `force` bypasses the host-side time
44
+ * window like {@link fetchTodaySpend}.
45
+ */
46
+ fetchTodaySessionsSpend: (force?: boolean) => Promise<DeepSeekTodaySessionsSpend>;
47
+ /**
48
+ * Compute one completed Turn's billed spend through the plugin's resolved
49
+ * facts, identified by its closing assistant message id.
50
+ */
51
+ fetchTurnSpend: (sessionId: SessionId, messageId: string) => Promise<DeepSeekTurnSpend>;
41
52
  }
42
53
  /**
43
54
  * Remote-only service exposing the DeepSeek account balance and session spend.
@@ -74,6 +85,25 @@ export declare class DeepSeekBalanceGateway extends TypertRemoteService {
74
85
  * @returns today's total cost plus one row per priced model.
75
86
  */
76
87
  getTodaySpend(force?: boolean): Promise<DeepSeekTodaySpend>;
88
+ /**
89
+ * Read today's billed spend per session, priced per event by its
90
+ * Beijing-time calendar day, hour, and weekday (weekends are always
91
+ * off-peak). Rows carry the session's durable title and sort by cost
92
+ * descending; sessions with no priced usage on the day are omitted.
93
+ * @param force - bypass the host-side 60s cache (manual refresh); omitted
94
+ * means a cached read.
95
+ * @returns today's per-session rows, highest first.
96
+ */
97
+ getTodaySessionsSpend(force?: boolean): Promise<DeepSeekTodaySessionsSpend>;
98
+ /**
99
+ * Read one completed Turn's billed spend, priced per event by its
100
+ * Beijing-time hour and weekday (weekends are always off-peak).
101
+ * @param sessionId - the session owning the Turn.
102
+ * @param messageId - the closing assistant message's durable id, which
103
+ * locates the Turn in the session log.
104
+ * @returns the Turn's total cost in CNY.
105
+ */
106
+ getTurnSpend(sessionId: SessionId, messageId: string): Promise<DeepSeekTurnSpend>;
77
107
  }
78
108
  export default DeepSeekBalanceGateway;
79
109
  //# sourceMappingURL=balance.d.ts.map
@@ -136,15 +136,21 @@ let DeepSeekBalanceGateway = (() => {
136
136
  let _getBalance_decorators;
137
137
  let _getSessionSpend_decorators;
138
138
  let _getTodaySpend_decorators;
139
+ let _getTodaySessionsSpend_decorators;
140
+ let _getTurnSpend_decorators;
139
141
  return class DeepSeekBalanceGateway extends _classSuper {
140
142
  static {
141
143
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
142
144
  _getBalance_decorators = [Remote('getBalance')];
143
145
  _getSessionSpend_decorators = [Remote('getSessionSpend')];
144
146
  _getTodaySpend_decorators = [Remote('getTodaySpend')];
147
+ _getTodaySessionsSpend_decorators = [Remote('getTodaySessionsSpend')];
148
+ _getTurnSpend_decorators = [Remote('getTurnSpend')];
145
149
  __esDecorate(this, null, _getBalance_decorators, { kind: "method", name: "getBalance", static: false, private: false, access: { has: obj => "getBalance" in obj, get: obj => obj.getBalance }, metadata: _metadata }, null, _instanceExtraInitializers);
146
150
  __esDecorate(this, null, _getSessionSpend_decorators, { kind: "method", name: "getSessionSpend", static: false, private: false, access: { has: obj => "getSessionSpend" in obj, get: obj => obj.getSessionSpend }, metadata: _metadata }, null, _instanceExtraInitializers);
147
151
  __esDecorate(this, null, _getTodaySpend_decorators, { kind: "method", name: "getTodaySpend", static: false, private: false, access: { has: obj => "getTodaySpend" in obj, get: obj => obj.getTodaySpend }, metadata: _metadata }, null, _instanceExtraInitializers);
152
+ __esDecorate(this, null, _getTodaySessionsSpend_decorators, { kind: "method", name: "getTodaySessionsSpend", static: false, private: false, access: { has: obj => "getTodaySessionsSpend" in obj, get: obj => obj.getTodaySessionsSpend }, metadata: _metadata }, null, _instanceExtraInitializers);
153
+ __esDecorate(this, null, _getTurnSpend_decorators, { kind: "method", name: "getTurnSpend", static: false, private: false, access: { has: obj => "getTurnSpend" in obj, get: obj => obj.getTurnSpend }, metadata: _metadata }, null, _instanceExtraInitializers);
148
154
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
149
155
  }
150
156
  options = __runInitializers(this, _instanceExtraInitializers);
@@ -184,6 +190,29 @@ let DeepSeekBalanceGateway = (() => {
184
190
  getTodaySpend(force) {
185
191
  return this.options.fetchTodaySpend(force ?? false);
186
192
  }
193
+ /**
194
+ * Read today's billed spend per session, priced per event by its
195
+ * Beijing-time calendar day, hour, and weekday (weekends are always
196
+ * off-peak). Rows carry the session's durable title and sort by cost
197
+ * descending; sessions with no priced usage on the day are omitted.
198
+ * @param force - bypass the host-side 60s cache (manual refresh); omitted
199
+ * means a cached read.
200
+ * @returns today's per-session rows, highest first.
201
+ */
202
+ getTodaySessionsSpend(force) {
203
+ return this.options.fetchTodaySessionsSpend(force ?? false);
204
+ }
205
+ /**
206
+ * Read one completed Turn's billed spend, priced per event by its
207
+ * Beijing-time hour and weekday (weekends are always off-peak).
208
+ * @param sessionId - the session owning the Turn.
209
+ * @param messageId - the closing assistant message's durable id, which
210
+ * locates the Turn in the session log.
211
+ * @returns the Turn's total cost in CNY.
212
+ */
213
+ getTurnSpend(sessionId, messageId) {
214
+ return this.options.fetchTurnSpend(sessionId, messageId);
215
+ }
187
216
  };
188
217
  })();
189
218
  export { DeepSeekBalanceGateway };
@@ -13,7 +13,7 @@
13
13
  * @module @rayadesu/dsh-llm-billing/billing
14
14
  */
15
15
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
16
- import type { DeepSeekSessionSpend, DeepSeekTodaySpend } from './types.ts';
16
+ import type { DeepSeekSessionSpend, DeepSeekTodaySpend, DeepSeekTurnSpend } from './types.ts';
17
17
  /** One token price point, in CNY per 1M tokens. */
18
18
  export interface DeepSeekTokenPrice {
19
19
  /** 1M input cache-hit tokens. */
@@ -187,6 +187,24 @@ export declare function computeSessionSpend(events: readonly SessionEvent[], bil
187
187
  id: string;
188
188
  name: string;
189
189
  }[]): DeepSeekSessionSpend;
190
+ /**
191
+ * Price one completed Turn's billed usage at the official per-model rates,
192
+ * identified by its closing assistant message id. The turn's events are those
193
+ * between its `turn/start` and `turn/end` (both matched by the message's own
194
+ * turn coordinate); each priced event applies the peak/off-peak table by its
195
+ * Beijing-time hour and weekday. A message that cannot be located, a turn
196
+ * without bracketing `turn/start` / `turn/end` events (for example after
197
+ * compaction), or a session with no priced usage prices to zero.
198
+ * @param events - one session's complete event log.
199
+ * @param billing - resolved pricing with peak-hour windows.
200
+ * @param catalog - model display rows, in presentation order.
201
+ * @param messageId - the closing assistant message's durable id.
202
+ * @returns the turn's total cost in CNY.
203
+ */
204
+ export declare function computeTurnSpend(events: readonly SessionEvent[], billing: ResolvedBilling, catalog: readonly {
205
+ id: string;
206
+ name: string;
207
+ }[], messageId: string): DeepSeekTurnSpend;
190
208
  /**
191
209
  * Price every event whose Beijing-time calendar day is the day of `now`,
192
210
  * aggregating across every session's event log. Events from other Beijing
@@ -272,6 +272,50 @@ export function computeSessionSpend(events, billing, catalog) {
272
272
  const names = new Map(catalog.map(model => [model.id, model.name]));
273
273
  return priceEvents(events, billing, names);
274
274
  }
275
+ /**
276
+ * Price one completed Turn's billed usage at the official per-model rates,
277
+ * identified by its closing assistant message id. The turn's events are those
278
+ * between its `turn/start` and `turn/end` (both matched by the message's own
279
+ * turn coordinate); each priced event applies the peak/off-peak table by its
280
+ * Beijing-time hour and weekday. A message that cannot be located, a turn
281
+ * without bracketing `turn/start` / `turn/end` events (for example after
282
+ * compaction), or a session with no priced usage prices to zero.
283
+ * @param events - one session's complete event log.
284
+ * @param billing - resolved pricing with peak-hour windows.
285
+ * @param catalog - model display rows, in presentation order.
286
+ * @param messageId - the closing assistant message's durable id.
287
+ * @returns the turn's total cost in CNY.
288
+ */
289
+ export function computeTurnSpend(events, billing, catalog, messageId) {
290
+ const names = new Map(catalog.map(model => [model.id, model.name]));
291
+ let turn;
292
+ for (const event of events) {
293
+ if (event.type !== 'assistant/message')
294
+ continue;
295
+ if (event.data.message.id !== messageId)
296
+ continue;
297
+ turn = event.data.turn;
298
+ break;
299
+ }
300
+ if (turn === undefined)
301
+ return { total: 0 };
302
+ const accumulator = new SpendAccumulator();
303
+ let active = false;
304
+ for (const event of events) {
305
+ if (event.type === 'turn/start' && event.data.turn === turn) {
306
+ active = true;
307
+ continue;
308
+ }
309
+ if (event.type === 'turn/end' && event.data.turn === turn)
310
+ break;
311
+ if (!active)
312
+ continue;
313
+ const priced = priceEvent(event, billing, names);
314
+ if (priced !== undefined)
315
+ accumulator.add(priced);
316
+ }
317
+ return { total: accumulator.finish().total };
318
+ }
275
319
  /**
276
320
  * Price every event whose Beijing-time calendar day is the day of `now`,
277
321
  * aggregating across every session's event log. Events from other Beijing
@@ -24,12 +24,12 @@ 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, beijingDayKey, computeSessionSpend, computeTodaySpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyTodaySpend, isPeak, mergeTodaySpend, priceEvent, resolveBilling, SpendAccumulator, } from './billing.ts';
27
+ export { addEventContribution, beijingDayKey, computeSessionSpend, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyTodaySpend, isPeak, mergeTodaySpend, priceEvent, resolveBilling, SpendAccumulator, } from './billing.ts';
28
28
  export type { BillingConfig, BillingConfigModel, BillingEventContribution, DeepSeekModelPricing, DeepSeekTokenPrice, PeakHourWindow, ResolvedBilling, } from './billing.ts';
29
29
  export type * from './types.ts';
30
30
  export { BILLING_UNIT_KEY, billingTodaySpendDefinition, foldBillingUnit } from './projection.ts';
31
31
  export type { BillingUnitState } from './projection.ts';
32
- export { TodaySpendCache, TodaySpendScanner } from './today-spend.ts';
32
+ export { foldSessionTitle, TodaySpendCache, TodaySpendScanner } from './today-spend.ts';
33
33
  export type { ScannerPersistedHeader, ScannerSession, TodaySpendScannerDeps } from './today-spend.ts';
34
34
  export declare const name = "llm-billing";
35
35
  /** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
@@ -25,13 +25,13 @@ import { assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm';
25
25
  import { credentialRef } from '@deepseek-ai/dsh-credentials';
26
26
  import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
27
27
  import { DeepSeekBalanceGateway, fetchDeepSeekBalance } from "./balance.js";
28
- import { computeSessionSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, mergeTodaySpend, resolveBilling, } from "./billing.js";
28
+ import { computeSessionSpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, mergeTodaySpend, resolveBilling, } from "./billing.js";
29
29
  import { billingTodaySpendDefinition } from "./projection.js";
30
30
  import { TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
31
31
  export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from "./balance.js";
32
- export { addEventContribution, beijingDayKey, computeSessionSpend, computeTodaySpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyTodaySpend, isPeak, mergeTodaySpend, priceEvent, resolveBilling, SpendAccumulator, } from "./billing.js";
32
+ export { addEventContribution, beijingDayKey, computeSessionSpend, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyTodaySpend, isPeak, mergeTodaySpend, priceEvent, resolveBilling, SpendAccumulator, } from "./billing.js";
33
33
  export { BILLING_UNIT_KEY, billingTodaySpendDefinition, foldBillingUnit } from "./projection.js";
34
- export { TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
34
+ export { foldSessionTitle, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
35
35
  export const name = 'llm-billing';
36
36
  const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY';
37
37
  const BASE_URL_ENV = 'DEEPSEEK_BASE_URL';
@@ -186,7 +186,13 @@ export function apply(ctx, config) {
186
186
  catalog,
187
187
  });
188
188
  const todayCache = new TodaySpendCache(dayKey => scanner.scan(dayKey), TODAY_SPEND_CACHE_MS);
189
+ const todaySessionsCache = new TodaySpendCache(dayKey => scanner.scanSessions(dayKey), TODAY_SPEND_CACHE_MS);
189
190
  const fetchTodaySpend = async (force = false) => todayCache.get(force);
190
- new DeepSeekBalanceGateway(ctx, { fetchBalance, fetchSessionSpend, fetchTodaySpend });
191
+ const fetchTodaySessionsSpend = async (force = false) => todaySessionsCache.get(force);
192
+ const fetchTurnSpend = async (sessionId, messageId) => {
193
+ const events = await sessionEvents(ctx, sessionId);
194
+ return computeTurnSpend(events, billing, catalog, messageId);
195
+ };
196
+ new DeepSeekBalanceGateway(ctx, { fetchBalance, fetchSessionSpend, fetchTodaySpend, fetchTodaySessionsSpend, fetchTurnSpend });
191
197
  }
192
198
  //# sourceMappingURL=index.js.map
@@ -23,9 +23,18 @@
23
23
  import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
24
24
  import type { SessionPersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
25
25
  import type { ResolvedBilling } from './billing.ts';
26
- import type { DeepSeekTodaySpend } from './types.ts';
26
+ import type { DeepSeekTodaySessionsSpend, DeepSeekTodaySpend } from './types.ts';
27
27
  import { BILLING_UNIT_KEY, type BillingUnitState } from './projection.ts';
28
28
  import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection';
29
+ /**
30
+ * Fold one session's durable display title: the latest `session/title`
31
+ * event's text (last-wins, matching the `title` projection), or `null` before
32
+ * the first title lands. The fold runs over the complete log, so an explicit
33
+ * user rename is picked up as soon as its event commits.
34
+ * @param events - one session's complete event log.
35
+ * @returns the session's current title, or `null` when untitled.
36
+ */
37
+ export declare function foldSessionTitle(events: readonly SessionEvent[]): string | null;
29
38
  /** Structural slice of a live session the scanner reads. */
30
39
  export interface ScannerSession {
31
40
  readonly id: SessionId;
@@ -89,8 +98,10 @@ export interface TodaySpendScannerDeps {
89
98
  * coalesces concurrent misses, and a `force` bypass for the manual refresh
90
99
  * path. Cross-day invalidation is automatic (the day key changes); a failed
91
100
  * scan leaves the previous value in place and retries on the next call.
101
+ * @typeParam T - the cached aggregate's value shape (the spend or its
102
+ * per-session breakdown).
92
103
  */
93
- export declare class TodaySpendCache {
104
+ export declare class TodaySpendCache<T = DeepSeekTodaySpend> {
94
105
  private readonly scan;
95
106
  private readonly ttlMs;
96
107
  private readonly now;
@@ -99,18 +110,18 @@ export declare class TodaySpendCache {
99
110
  private cachedAt;
100
111
  private inFlight;
101
112
  /**
113
+ * @param scan - the aggregate computation behind a miss.
102
114
  * @param ttlMs - time window in milliseconds (default 60 000).
103
115
  * @param now - clock source (injectable for tests).
104
- * @param scan - the aggregate computation behind a miss.
105
116
  */
106
- constructor(scan: (dayKey: string) => Promise<DeepSeekTodaySpend>, ttlMs?: number, now?: () => Date);
117
+ constructor(scan: (dayKey: string) => Promise<T>, ttlMs?: number, now?: () => Date);
107
118
  /**
108
119
  * Read today's spend, cached per Beijing day within the TTL window.
109
120
  * @param force - bypass the time window (manual refresh); the day-key gate
110
121
  * and the in-flight coalescing still apply to non-force callers.
111
122
  * @returns today's spend.
112
123
  */
113
- get(force?: boolean): Promise<DeepSeekTodaySpend>;
124
+ get(force?: boolean): Promise<T>;
114
125
  }
115
126
  /**
116
127
  * The aggregate computation behind a cache miss. Chooses the projection path
@@ -120,7 +131,7 @@ export declare class TodaySpendCache {
120
131
  */
121
132
  export declare class TodaySpendScanner {
122
133
  private readonly deps;
123
- /** Cold sessions resolved on the projection path: id → revision + unit state. */
134
+ /** Cold sessions resolved on the projection path: id → revision + unit state + title. */
124
135
  private readonly coldResolved;
125
136
  /** Cold sessions resolved on the events path: id → revision (events were collected). */
126
137
  private lastEventsScan;
@@ -131,6 +142,24 @@ export declare class TodaySpendScanner {
131
142
  * @returns today's spend across every session.
132
143
  */
133
144
  scan(dayKey: string): Promise<DeepSeekTodaySpend>;
145
+ /**
146
+ * Compute today's per-session spend for one Beijing day, sorted by cost
147
+ * descending. Sessions with no priced usage on the day are omitted; each
148
+ * row carries the session's durable title folded from its log.
149
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
150
+ * @returns today's per-session rows, highest first.
151
+ */
152
+ scanSessions(dayKey: string): Promise<DeepSeekTodaySessionsSpend>;
153
+ /**
154
+ * Resolve one cold session's billing unit state and display title through
155
+ * the projection-cache ladder (cached row first, then a detached local
156
+ * fold over a full inspect). A cache-served value carries no title (the
157
+ * ladder only stores projection values), so such rows report `title: null`
158
+ * until the session is inspected again.
159
+ * @param id - the cold session's id.
160
+ * @returns the resolved state and title, or `undefined` when unreadable.
161
+ */
162
+ private resolveCold;
134
163
  /** Projection path: eager cells for live sessions, revision-gated cold ladder for the rest. */
135
164
  private scanProjections;
136
165
  /**
@@ -138,5 +167,24 @@ export declare class TodaySpendScanner {
138
167
  * filter during collection, hard cap), gated by revisions.
139
168
  */
140
169
  private scanEvents;
170
+ /**
171
+ * Projection-path per-session scan: eager cells for live sessions (title
172
+ * folded from the live log, so a rename is reflected immediately),
173
+ * revision-gated cold ladder for the rest (title resolved on inspect,
174
+ * `null` when served from the projection cache).
175
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
176
+ * @returns unsorted per-session rows for the day.
177
+ */
178
+ private scanSessionsProjections;
179
+ /**
180
+ * Events-path per-session scan: price today's events in a single pass,
181
+ * accumulating per session (per-event Beijing-day filter during collection,
182
+ * hard cap), gated by revisions. Titles fold from each session's complete
183
+ * log — a `session/title` event can predate today — so a rename is reflected
184
+ * as soon as the session's log is re-read.
185
+ * @param dayKey - the Beijing-time calendar-day key to aggregate.
186
+ * @returns unsorted per-session rows for the day.
187
+ */
188
+ private scanSessionsEvents;
141
189
  }
142
190
  //# sourceMappingURL=today-spend.d.ts.map