@rayadesu/dsh-llm-billing 0.2.2 → 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.
- package/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/index.js +450 -117
- package/lib/typert.host.js +77 -3
- package/lib/typert.remote-client.d.ts +5 -1
- package/lib/typert.remote-client.js +77 -3
- package/lib/types/balance.d.ts +31 -1
- package/lib/types/balance.js +29 -0
- package/lib/types/billing.d.ts +41 -6
- package/lib/types/billing.js +132 -70
- package/lib/types/index.d.ts +7 -2
- package/lib/types/index.js +37 -5
- package/lib/types/today-spend.d.ts +62 -10
- package/lib/types/today-spend.js +252 -54
- package/lib/types/types.d.ts +23 -0
- package/package.json +1 -1
package/lib/types/today-spend.js
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* with write-back) or, without the cache service, one detached local fold
|
|
10
10
|
* over a full `inspect`. Persisted revisions gate every cold read, so a
|
|
11
11
|
* session whose log did not change since the last resolution costs nothing.
|
|
12
|
-
* - events path (plans A2/A3): collect only today's events
|
|
13
|
-
* Beijing-day filter during collection) with a hard cap,
|
|
14
|
-
* whose persisted revision is unchanged since the last
|
|
12
|
+
* - events path (plans A2/A3): collect and price only today's events in one
|
|
13
|
+
* pass (per-event Beijing-day filter during collection) with a hard cap,
|
|
14
|
+
* skipping sessions whose persisted revision is unchanged since the last
|
|
15
|
+
* scan.
|
|
15
16
|
*
|
|
16
17
|
* Both strategies run behind the same {@link TodaySpendCache}, so a miss
|
|
17
18
|
* happens at most once per 60 seconds per process, and a manual refresh
|
|
@@ -19,14 +20,42 @@
|
|
|
19
20
|
* unchanged log provably cannot change the aggregate.
|
|
20
21
|
* @module @rayadesu/dsh-llm-billing/today-spend
|
|
21
22
|
*/
|
|
22
|
-
import { beijingDayKey,
|
|
23
|
+
import { beijingDayKey, emptyTodaySpend, mergeTodaySpend, priceEvent, SpendAccumulator } from "./billing.js";
|
|
23
24
|
import { BILLING_UNIT_KEY, foldBillingUnit } from "./projection.js";
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Fold one session's durable display title: the latest `session/title`
|
|
27
|
+
* event's text (last-wins, matching the `title` projection), or `null` before
|
|
28
|
+
* the first title lands. The fold runs over the complete log, so an explicit
|
|
29
|
+
* user rename is picked up as soon as its event commits.
|
|
30
|
+
* @param events - one session's complete event log.
|
|
31
|
+
* @returns the session's current title, or `null` when untitled.
|
|
32
|
+
*/
|
|
33
|
+
export function foldSessionTitle(events) {
|
|
34
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
35
|
+
const event = events[index];
|
|
36
|
+
// `session/title` joined the SessionEvent union after the npm
|
|
37
|
+
// 0.1.1-rc.2 baseline this package builds against; read its payload
|
|
38
|
+
// through the structural escape hatch (runtime logs carry it).
|
|
39
|
+
if (event.type !== 'session/title')
|
|
40
|
+
continue;
|
|
41
|
+
const data = event.data;
|
|
42
|
+
return typeof data.title === 'string' ? data.title : null;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Bounded parallel fan-out: run `run` over `items` with at most `limit` in
|
|
48
|
+
* flight. A shared index counter hands each worker its next job, so the
|
|
49
|
+
* dispatch is O(n) overall (array `shift()` would be O(n) per pop).
|
|
50
|
+
*/
|
|
25
51
|
async function withConcurrency(items, limit, run) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
const total = items.length;
|
|
53
|
+
let next = 0;
|
|
54
|
+
await Promise.all(Array.from({ length: Math.min(limit, total) }, async () => {
|
|
55
|
+
for (let job = next; job < total; job = next) {
|
|
56
|
+
next += 1;
|
|
57
|
+
await run(items[job]);
|
|
58
|
+
}
|
|
30
59
|
}));
|
|
31
60
|
}
|
|
32
61
|
/**
|
|
@@ -34,6 +63,8 @@ async function withConcurrency(items, limit, run) {
|
|
|
34
63
|
* coalesces concurrent misses, and a `force` bypass for the manual refresh
|
|
35
64
|
* path. Cross-day invalidation is automatic (the day key changes); a failed
|
|
36
65
|
* scan leaves the previous value in place and retries on the next call.
|
|
66
|
+
* @typeParam T - the cached aggregate's value shape (the spend or its
|
|
67
|
+
* per-session breakdown).
|
|
37
68
|
*/
|
|
38
69
|
export class TodaySpendCache {
|
|
39
70
|
scan;
|
|
@@ -44,9 +75,9 @@ export class TodaySpendCache {
|
|
|
44
75
|
cachedAt = 0;
|
|
45
76
|
inFlight;
|
|
46
77
|
/**
|
|
78
|
+
* @param scan - the aggregate computation behind a miss.
|
|
47
79
|
* @param ttlMs - time window in milliseconds (default 60 000).
|
|
48
80
|
* @param now - clock source (injectable for tests).
|
|
49
|
-
* @param scan - the aggregate computation behind a miss.
|
|
50
81
|
*/
|
|
51
82
|
constructor(scan, ttlMs = 60_000, now = () => new Date()) {
|
|
52
83
|
this.scan = scan;
|
|
@@ -93,7 +124,7 @@ export class TodaySpendCache {
|
|
|
93
124
|
*/
|
|
94
125
|
export class TodaySpendScanner {
|
|
95
126
|
deps;
|
|
96
|
-
/** Cold sessions resolved on the projection path: id → revision + unit state. */
|
|
127
|
+
/** Cold sessions resolved on the projection path: id → revision + unit state + title. */
|
|
97
128
|
coldResolved = new Map();
|
|
98
129
|
/** Cold sessions resolved on the events path: id → revision (events were collected). */
|
|
99
130
|
lastEventsScan;
|
|
@@ -111,9 +142,60 @@ export class TodaySpendScanner {
|
|
|
111
142
|
this.deps.ensureUnit?.();
|
|
112
143
|
return this.scanProjections(dayKey);
|
|
113
144
|
}
|
|
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
|
+
async scanSessions(dayKey) {
|
|
153
|
+
const rows = this.deps.projections?.() === undefined
|
|
154
|
+
? await this.scanSessionsEvents(dayKey)
|
|
155
|
+
: await this.scanSessionsProjections(dayKey);
|
|
156
|
+
rows.sort((left, right) => right.total - left.total);
|
|
157
|
+
return { sessions: rows };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Resolve one cold session's billing unit state and display title through
|
|
161
|
+
* the projection-cache ladder (cached row first, then a detached local
|
|
162
|
+
* fold over a full inspect). A cache-served value carries no title (the
|
|
163
|
+
* ladder only stores projection values), so such rows report `title: null`
|
|
164
|
+
* until the session is inspected again.
|
|
165
|
+
* @param id - the cold session's id.
|
|
166
|
+
* @returns the resolved state and title, or `undefined` when unreadable.
|
|
167
|
+
*/
|
|
168
|
+
async resolveCold(id) {
|
|
169
|
+
const { persistence, projectionCache, unit, logger } = this.deps;
|
|
170
|
+
const cache = projectionCache?.();
|
|
171
|
+
if (cache !== undefined) {
|
|
172
|
+
try {
|
|
173
|
+
const value = (await cache.coldSnapshot(id)).values[BILLING_UNIT_KEY];
|
|
174
|
+
if (value !== undefined)
|
|
175
|
+
return { value, title: null };
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.warn(`llm-billing: projection cold read for session ${id} failed: ${String(error)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const persistenceService = persistence?.();
|
|
182
|
+
if (persistenceService === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
try {
|
|
185
|
+
const inspection = await persistenceService.inspect(id);
|
|
186
|
+
return { value: foldBillingUnit(unit, inspection.events), title: foldSessionTitle(inspection.events) };
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
// One unreadable session must not blank the whole-day aggregate.
|
|
190
|
+
logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
114
194
|
/** Projection path: eager cells for live sessions, revision-gated cold ladder for the rest. */
|
|
115
195
|
async scanProjections(dayKey) {
|
|
116
|
-
const { sessions, persistence, projections
|
|
196
|
+
const { sessions, persistence, projections } = this.deps;
|
|
197
|
+
// Services resolve once per scan, not per session / per cold task.
|
|
198
|
+
const projectionsService = projections?.();
|
|
117
199
|
let total = emptyTodaySpend();
|
|
118
200
|
const liveIds = new Set();
|
|
119
201
|
if (sessions !== undefined) {
|
|
@@ -121,7 +203,7 @@ export class TodaySpendScanner {
|
|
|
121
203
|
if (store !== undefined) {
|
|
122
204
|
for (const session of store.list()) {
|
|
123
205
|
liveIds.add(session.id);
|
|
124
|
-
const state =
|
|
206
|
+
const state = projectionsService?.stateOf(session, BILLING_UNIT_KEY);
|
|
125
207
|
if (state !== undefined && state.dayKey === dayKey) {
|
|
126
208
|
total = mergeTodaySpend(total, state.spend);
|
|
127
209
|
}
|
|
@@ -145,57 +227,183 @@ export class TodaySpendScanner {
|
|
|
145
227
|
pending.push({ id: header.id, revision });
|
|
146
228
|
}
|
|
147
229
|
await withConcurrency(pending, 8, async ({ id, revision }) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
230
|
+
const resolved = await this.resolveCold(id);
|
|
231
|
+
if (resolved !== undefined)
|
|
232
|
+
this.coldResolved.set(id, { revision, ...resolved });
|
|
233
|
+
});
|
|
234
|
+
for (const { id } of pending) {
|
|
235
|
+
const resolved = this.coldResolved.get(id);
|
|
236
|
+
if (resolved !== undefined && resolved.value.dayKey === dayKey) {
|
|
237
|
+
total = mergeTodaySpend(total, resolved.value.spend);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return total;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Events path: price today's events in a single pass (per-event Beijing-day
|
|
244
|
+
* filter during collection, hard cap), gated by revisions.
|
|
245
|
+
*/
|
|
246
|
+
async scanEvents(dayKey) {
|
|
247
|
+
const { sessions, persistence, maxEvents, logger, billing, catalog } = this.deps;
|
|
248
|
+
const names = new Map(catalog.map(model => [model.id, model.name]));
|
|
249
|
+
const accumulator = new SpendAccumulator();
|
|
250
|
+
const liveIds = new Set();
|
|
251
|
+
let collected = 0;
|
|
252
|
+
let truncated = false;
|
|
253
|
+
const collect = (events) => {
|
|
254
|
+
for (const event of events) {
|
|
255
|
+
if (beijingDayKey(new Date(event.time)) !== dayKey)
|
|
256
|
+
continue;
|
|
257
|
+
collected += 1;
|
|
258
|
+
if (collected > maxEvents) {
|
|
259
|
+
truncated = true;
|
|
260
|
+
return;
|
|
153
261
|
}
|
|
154
|
-
|
|
155
|
-
|
|
262
|
+
const priced = priceEvent(event, billing, names);
|
|
263
|
+
if (priced !== undefined)
|
|
264
|
+
accumulator.add(priced);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
if (sessions !== undefined) {
|
|
268
|
+
const store = sessions();
|
|
269
|
+
if (store !== undefined) {
|
|
270
|
+
for (const session of store.list()) {
|
|
271
|
+
liveIds.add(session.id);
|
|
272
|
+
collect(session.events);
|
|
273
|
+
if (truncated)
|
|
274
|
+
break;
|
|
156
275
|
}
|
|
157
276
|
}
|
|
158
|
-
|
|
277
|
+
}
|
|
278
|
+
const persistenceService = persistence?.();
|
|
279
|
+
if (!truncated && persistenceService !== undefined) {
|
|
280
|
+
const snapshots = await persistenceService.listSnapshots();
|
|
281
|
+
for (const { header, revision } of snapshots) {
|
|
282
|
+
if (liveIds.has(header.id))
|
|
283
|
+
continue;
|
|
284
|
+
if (this.lastEventsScan?.get(header.id) === revision)
|
|
285
|
+
continue;
|
|
159
286
|
try {
|
|
160
|
-
|
|
161
|
-
value = foldBillingUnit(unit, inspection.events);
|
|
287
|
+
collect((await persistenceService.inspect(header.id)).events);
|
|
162
288
|
}
|
|
163
289
|
catch (error) {
|
|
164
290
|
// One unreadable session must not blank the whole-day aggregate.
|
|
165
|
-
logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
|
|
291
|
+
logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
|
|
292
|
+
}
|
|
293
|
+
if (truncated)
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
// Only a complete pass may advance the revision watermark: a truncated
|
|
297
|
+
// pass left sessions unread, and recording them would skip their events
|
|
298
|
+
// on the next scan.
|
|
299
|
+
if (!truncated) {
|
|
300
|
+
this.lastEventsScan = new Map(snapshots.map(snapshot => [snapshot.header.id, snapshot.revision]));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (truncated)
|
|
304
|
+
logger.warn(`llm-billing: today's events exceeded ${maxEvents}; result truncated`);
|
|
305
|
+
return accumulator.finish();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Projection-path per-session scan: eager cells for live sessions (title
|
|
309
|
+
* folded from the live log, so a rename is reflected immediately),
|
|
310
|
+
* revision-gated cold ladder for the rest (title resolved on inspect,
|
|
311
|
+
* `null` when served from the projection cache).
|
|
312
|
+
* @param dayKey - the Beijing-time calendar-day key to aggregate.
|
|
313
|
+
* @returns unsorted per-session rows for the day.
|
|
314
|
+
*/
|
|
315
|
+
async scanSessionsProjections(dayKey) {
|
|
316
|
+
const { sessions, persistence, projections } = this.deps;
|
|
317
|
+
const projectionsService = projections?.();
|
|
318
|
+
const rows = new Map();
|
|
319
|
+
const liveIds = new Set();
|
|
320
|
+
if (sessions !== undefined) {
|
|
321
|
+
const store = sessions();
|
|
322
|
+
if (store !== undefined) {
|
|
323
|
+
for (const session of store.list()) {
|
|
324
|
+
liveIds.add(session.id);
|
|
325
|
+
const state = projectionsService?.stateOf(session, BILLING_UNIT_KEY);
|
|
326
|
+
if (state !== undefined && state.dayKey === dayKey) {
|
|
327
|
+
rows.set(session.id, {
|
|
328
|
+
sessionId: session.id,
|
|
329
|
+
title: foldSessionTitle(session.events),
|
|
330
|
+
total: state.spend.total,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
166
333
|
}
|
|
167
334
|
}
|
|
168
|
-
|
|
169
|
-
|
|
335
|
+
}
|
|
336
|
+
const persistenceService = persistence?.();
|
|
337
|
+
if (persistenceService === undefined)
|
|
338
|
+
return [...rows.values()];
|
|
339
|
+
const snapshots = await persistenceService.listSnapshots();
|
|
340
|
+
const pending = [];
|
|
341
|
+
for (const { header, revision } of snapshots) {
|
|
342
|
+
if (liveIds.has(header.id))
|
|
343
|
+
continue;
|
|
344
|
+
const resolved = this.coldResolved.get(header.id);
|
|
345
|
+
if (resolved !== undefined && resolved.revision === revision) {
|
|
346
|
+
if (resolved.value.dayKey === dayKey) {
|
|
347
|
+
rows.set(header.id, { sessionId: header.id, title: resolved.title, total: resolved.value.spend.total });
|
|
348
|
+
}
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
pending.push({ id: header.id, revision });
|
|
352
|
+
}
|
|
353
|
+
await withConcurrency(pending, 8, async ({ id, revision }) => {
|
|
354
|
+
const resolved = await this.resolveCold(id);
|
|
355
|
+
if (resolved !== undefined)
|
|
356
|
+
this.coldResolved.set(id, { revision, ...resolved });
|
|
170
357
|
});
|
|
171
358
|
for (const { id } of pending) {
|
|
172
359
|
const resolved = this.coldResolved.get(id);
|
|
173
360
|
if (resolved !== undefined && resolved.value.dayKey === dayKey) {
|
|
174
|
-
|
|
361
|
+
rows.set(id, { sessionId: id, title: resolved.title, total: resolved.value.spend.total });
|
|
175
362
|
}
|
|
176
363
|
}
|
|
177
|
-
return
|
|
364
|
+
return [...rows.values()];
|
|
178
365
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Events-path per-session scan: price today's events in a single pass,
|
|
368
|
+
* accumulating per session (per-event Beijing-day filter during collection,
|
|
369
|
+
* hard cap), gated by revisions. Titles fold from each session's complete
|
|
370
|
+
* log — a `session/title` event can predate today — so a rename is reflected
|
|
371
|
+
* as soon as the session's log is re-read.
|
|
372
|
+
* @param dayKey - the Beijing-time calendar-day key to aggregate.
|
|
373
|
+
* @returns unsorted per-session rows for the day.
|
|
374
|
+
*/
|
|
375
|
+
async scanSessionsEvents(dayKey) {
|
|
181
376
|
const { sessions, persistence, maxEvents, logger, billing, catalog } = this.deps;
|
|
182
|
-
const
|
|
377
|
+
const names = new Map(catalog.map(model => [model.id, model.name]));
|
|
378
|
+
const rows = new Map();
|
|
183
379
|
const liveIds = new Set();
|
|
380
|
+
let collected = 0;
|
|
184
381
|
let truncated = false;
|
|
382
|
+
const collect = (id, events) => {
|
|
383
|
+
let row = rows.get(id);
|
|
384
|
+
if (row === undefined) {
|
|
385
|
+
row = { title: foldSessionTitle(events), total: 0 };
|
|
386
|
+
rows.set(id, row);
|
|
387
|
+
}
|
|
388
|
+
for (const event of events) {
|
|
389
|
+
if (beijingDayKey(new Date(event.time)) !== dayKey)
|
|
390
|
+
continue;
|
|
391
|
+
collected += 1;
|
|
392
|
+
if (collected > maxEvents) {
|
|
393
|
+
truncated = true;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const priced = priceEvent(event, billing, names);
|
|
397
|
+
if (priced !== undefined)
|
|
398
|
+
row.total += priced.cost;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
185
401
|
if (sessions !== undefined) {
|
|
186
402
|
const store = sessions();
|
|
187
403
|
if (store !== undefined) {
|
|
188
404
|
for (const session of store.list()) {
|
|
189
405
|
liveIds.add(session.id);
|
|
190
|
-
|
|
191
|
-
if (beijingDayKey(new Date(event.time)) !== dayKey)
|
|
192
|
-
continue;
|
|
193
|
-
events.push(event);
|
|
194
|
-
if (events.length >= maxEvents) {
|
|
195
|
-
truncated = true;
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
406
|
+
collect(session.id, session.events);
|
|
199
407
|
if (truncated)
|
|
200
408
|
break;
|
|
201
409
|
}
|
|
@@ -210,16 +418,7 @@ export class TodaySpendScanner {
|
|
|
210
418
|
if (this.lastEventsScan?.get(header.id) === revision)
|
|
211
419
|
continue;
|
|
212
420
|
try {
|
|
213
|
-
|
|
214
|
-
for (const event of inspection.events) {
|
|
215
|
-
if (beijingDayKey(new Date(event.time)) !== dayKey)
|
|
216
|
-
continue;
|
|
217
|
-
events.push(event);
|
|
218
|
-
if (events.length >= maxEvents) {
|
|
219
|
-
truncated = true;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
421
|
+
collect(header.id, (await persistenceService.inspect(header.id)).events);
|
|
223
422
|
}
|
|
224
423
|
catch (error) {
|
|
225
424
|
// One unreadable session must not blank the whole-day aggregate.
|
|
@@ -237,10 +436,9 @@ export class TodaySpendScanner {
|
|
|
237
436
|
}
|
|
238
437
|
if (truncated)
|
|
239
438
|
logger.warn(`llm-billing: today's events exceeded ${maxEvents}; result truncated`);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return computeTodaySpend(events, billing, catalog, new Date(`${dayKey}T00:00:00Z`));
|
|
439
|
+
return [...rows.entries()]
|
|
440
|
+
.filter(([, row]) => row.total > 0)
|
|
441
|
+
.map(([sessionId, row]) => ({ sessionId, title: row.title, total: row.total }));
|
|
244
442
|
}
|
|
245
443
|
}
|
|
246
444
|
//# sourceMappingURL=today-spend.js.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* its generated artifacts, and the web UI.
|
|
4
4
|
* @module @rayadesu/dsh-llm-billing/types
|
|
5
5
|
*/
|
|
6
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
6
7
|
/** One currency line of the account balance returned by `GET /user/balance`. */
|
|
7
8
|
export interface DeepSeekBalanceLine {
|
|
8
9
|
/** Currency code, e.g. `CNY` or `USD`. */
|
|
@@ -60,4 +61,26 @@ export interface DeepSeekTodaySpend {
|
|
|
60
61
|
/** One row per model that reported usage AND has a pricing row; empty when today has no priced usage. */
|
|
61
62
|
models: readonly DeepSeekSessionSpendModel[];
|
|
62
63
|
}
|
|
64
|
+
/** One session's billed spend on one Beijing-time calendar day, priced per event by its Beijing-time hour and weekday (peak hours apply Monday–Friday only; weekends are off-peak). */
|
|
65
|
+
export interface DeepSeekTodaySessionSpend {
|
|
66
|
+
/** The session's durable identity. */
|
|
67
|
+
sessionId: SessionId;
|
|
68
|
+
/**
|
|
69
|
+
* The session's display title: the latest `session/title` event's text, or
|
|
70
|
+
* `null` when the session has no title (or the title could not be resolved).
|
|
71
|
+
*/
|
|
72
|
+
title: string | null;
|
|
73
|
+
/** Billed cost in CNY on the queried Beijing day. */
|
|
74
|
+
total: number;
|
|
75
|
+
}
|
|
76
|
+
/** Today's per-session billed spend across every session with a non-zero cost. */
|
|
77
|
+
export interface DeepSeekTodaySessionsSpend {
|
|
78
|
+
/** Sessions with today's spend, sorted by `total` descending. */
|
|
79
|
+
sessions: readonly DeepSeekTodaySessionSpend[];
|
|
80
|
+
}
|
|
81
|
+
/** The billed cost of one completed Turn, priced per event by its Beijing-time hour and weekday (peak hours apply Monday–Friday only; weekends are off-peak). */
|
|
82
|
+
export interface DeepSeekTurnSpend {
|
|
83
|
+
/** Total billed cost in CNY across every priced model in the Turn. */
|
|
84
|
+
total: number;
|
|
85
|
+
}
|
|
63
86
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED