@rayadesu/dsh-llm-billing 0.3.6 → 0.3.8
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 +3 -1
- package/README.zh.md +3 -1
- package/lib/index.js +86 -17
- package/lib/types/billing.d.ts +2 -0
- package/lib/types/billing.js +11 -0
- package/lib/types/index.d.ts +3 -3
- package/lib/types/index.js +5 -4
- package/lib/types/projection.d.ts +14 -2
- package/lib/types/today-spend.d.ts +65 -19
- package/lib/types/today-spend.js +47 -11
- package/package.json +17 -17
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: 3c0ae19286d6935190659ec1e1c93b7b63ab5c14
|
|
6
|
+
README.zh.md: 08adb0d018b376fb5fa995b29cbb6790063f4814
|
package/README.md
CHANGED
|
@@ -35,7 +35,9 @@ A forked session (DSH's "fork" of a conversation) opens its log with a verbatim
|
|
|
35
35
|
|
|
36
36
|
## Runtime compatibility
|
|
37
37
|
|
|
38
|
-
Since 0.1.2-alpha.4, DSH replaced the live `Session` log surface `Session.events` with `Session.snapshotEvents()` (no args = the full current log) and `Session.ownEvents()`, and moved `SessionHeader.seedLength` to `Session.inheritedEventCount` (the persistence `inspect()` result carries the value beside `meta`; `listSnapshots()` headers keep only the boolean `isSeeded`). Every log read in the plugin goes through the structural adapters `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`, which accept both the ≤ 0.1.1-rc.2 and the 0.1.2-alpha.4+ shapes — the npm release baseline (`^0.1.
|
|
38
|
+
Since 0.1.2-alpha.4, DSH replaced the live `Session` log surface `Session.events` with `Session.snapshotEvents()` (no args = the full current log) and `Session.ownEvents()`, and moved `SessionHeader.seedLength` to `Session.inheritedEventCount` (the persistence `inspect()` result carries the value beside `meta`; `listSnapshots()` headers keep only the boolean `isSeeded`). Every log read in the plugin goes through the structural adapters `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`, which accept both the ≤ 0.1.1-rc.2 and the 0.1.2-alpha.4+ shapes — the npm release baseline (`^0.1.2-alpha.5`) and the ahead-of-npm monorepo runtime both work without modification. On an unknown surface that has neither shape the plugin fails loudly rather than silently pricing an empty log.
|
|
39
|
+
|
|
40
|
+
The persistence service itself changed surface too: 0.1.1-rc.2 exposes `inspect(id)` / `listSnapshots()`, while the 0.1.2-alpha.5 handle-based seam (in the monorepo after the released build) exposes `open(id, 'read')` + `SessionHandle.read()` / `list()`. The scanner reads both families through `persistenceInspect` / `persistenceListSnapshots` (the handle is always closed, including after a failed read), so the same plugin serves the published alpha line and the refactored checkout.
|
|
39
41
|
|
|
40
42
|
## Configuration
|
|
41
43
|
|
package/README.zh.md
CHANGED
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
|
|
36
36
|
## 运行时兼容性
|
|
37
37
|
|
|
38
|
-
0.1.2-alpha.4 起,DSH 把 live `Session` 的日志读取表面从 `Session.events` 改为 `Session.snapshotEvents()`(无参 = 当前全量日志)与 `Session.ownEvents()`,并把 `SessionHeader.seedLength` 移至 `Session.inheritedEventCount`(持久化侧 `inspect()` 的结果在 `meta` 之外携带该值,`listSnapshots()` 的 header 只剩布尔 `isSeeded`)。插件的所有日志读取都走结构适配器 `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`,同时接受 ≤ 0.1.1-rc.2 与 0.1.2-alpha.4+ 两种形状——npm 发布基线(`^0.1.
|
|
38
|
+
0.1.2-alpha.4 起,DSH 把 live `Session` 的日志读取表面从 `Session.events` 改为 `Session.snapshotEvents()`(无参 = 当前全量日志)与 `Session.ownEvents()`,并把 `SessionHeader.seedLength` 移至 `Session.inheritedEventCount`(持久化侧 `inspect()` 的结果在 `meta` 之外携带该值,`listSnapshots()` 的 header 只剩布尔 `isSeeded`)。插件的所有日志读取都走结构适配器 `liveSessionEvents` / `forkBoundaryOf` / `isSeededSession`,同时接受 ≤ 0.1.1-rc.2 与 0.1.2-alpha.4+ 两种形状——npm 发布基线(`^0.1.2-alpha.5`)与超前于它的 monorepo 运行时代码均无需改动即可工作。遇到两种形状都没有的未知运行时表面时,插件会显式失败而不是静默按零花费计价。
|
|
39
|
+
|
|
40
|
+
持久化服务的表面同样换代:0.1.1-rc.2 提供 `inspect(id)` / `listSnapshots()`,而 handle 化的 0.1.2-alpha.5 seam(位于已发布构建之后的 monorepo 中)提供 `open(id, 'read')` + `SessionHandle.read()` / `list()`。扫描器通过 `persistenceInspect` / `persistenceListSnapshots` 同时读取两代表面(handle 总会关闭,读取失败时也一样),因此同一套插件既能服务已发布的 alpha 线,也能服务重构后的 checkout。
|
|
39
41
|
|
|
40
42
|
## 配置
|
|
41
43
|
|
package/lib/index.js
CHANGED
|
@@ -337,6 +337,32 @@ const DEFAULT_MODEL_PRICING = [
|
|
|
337
337
|
cacheMissInput: 1.5,
|
|
338
338
|
output: 4.5
|
|
339
339
|
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
model: "mimo-v2.5-pro",
|
|
343
|
+
peak: {
|
|
344
|
+
cacheHitInput: .025,
|
|
345
|
+
cacheMissInput: 3,
|
|
346
|
+
output: 6
|
|
347
|
+
},
|
|
348
|
+
offPeak: {
|
|
349
|
+
cacheHitInput: .025,
|
|
350
|
+
cacheMissInput: 3,
|
|
351
|
+
output: 6
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
model: "mimo-v2.5",
|
|
356
|
+
peak: {
|
|
357
|
+
cacheHitInput: .02,
|
|
358
|
+
cacheMissInput: 1,
|
|
359
|
+
output: 2
|
|
360
|
+
},
|
|
361
|
+
offPeak: {
|
|
362
|
+
cacheHitInput: .02,
|
|
363
|
+
cacheMissInput: 1,
|
|
364
|
+
output: 2
|
|
365
|
+
}
|
|
340
366
|
}
|
|
341
367
|
];
|
|
342
368
|
/**
|
|
@@ -836,6 +862,45 @@ function liveSessionEvents(session) {
|
|
|
836
862
|
if (session.snapshotEvents !== void 0) return session.snapshotEvents();
|
|
837
863
|
throw new Error("llm-billing: session log surface is neither Session.events nor Session.snapshotEvents");
|
|
838
864
|
}
|
|
865
|
+
function isHandlePersistence(persistence) {
|
|
866
|
+
return typeof persistence.open === "function";
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* List every stored session snapshot across both persistence runtime families:
|
|
870
|
+
* `listSnapshots` (≤ 0.1.1-rc.2) or `list` (0.1.2-alpha.5+).
|
|
871
|
+
* @param persistence - the persistence service slice.
|
|
872
|
+
* @returns one snapshot per stored session.
|
|
873
|
+
*/
|
|
874
|
+
function persistenceListSnapshots(persistence) {
|
|
875
|
+
return isHandlePersistence(persistence) ? persistence.list() : persistence.listSnapshots();
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Read one stored session's complete event log and durable inherited boundary
|
|
879
|
+
* across both persistence runtime families: legacy `inspect` (≤ 0.1.1-rc.2)
|
|
880
|
+
* or `open` + handle `read` (0.1.2-alpha.5+; the handle is closed after the
|
|
881
|
+
* read). Both throw when the session does not exist.
|
|
882
|
+
* @param persistence - the persistence service slice.
|
|
883
|
+
* @param id - the stored session to read.
|
|
884
|
+
* @returns the session's complete event log plus its inherited-prefix boundary.
|
|
885
|
+
*/
|
|
886
|
+
async function persistenceInspect(persistence, id) {
|
|
887
|
+
if (isHandlePersistence(persistence)) {
|
|
888
|
+
const handle = await persistence.open(id, "read");
|
|
889
|
+
try {
|
|
890
|
+
return {
|
|
891
|
+
events: await handle.read(),
|
|
892
|
+
seedLength: forkBoundaryOf(handle)
|
|
893
|
+
};
|
|
894
|
+
} finally {
|
|
895
|
+
await handle.close();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const inspection = await persistence.inspect(id);
|
|
899
|
+
return {
|
|
900
|
+
events: inspection.events,
|
|
901
|
+
seedLength: forkBoundaryOf(inspection)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
839
904
|
/**
|
|
840
905
|
* Bounded parallel fan-out: run `run` over `items` with at most `limit` in
|
|
841
906
|
* flight. A shared index counter hands each worker its next job, so the
|
|
@@ -975,10 +1040,10 @@ var TodaySpendScanner = class {
|
|
|
975
1040
|
}
|
|
976
1041
|
}
|
|
977
1042
|
try {
|
|
978
|
-
const
|
|
1043
|
+
const read = await persistenceInspect(persistenceService, id);
|
|
979
1044
|
return {
|
|
980
|
-
value: foldOwnBilling(this.deps.unit,
|
|
981
|
-
title: foldSessionTitle(
|
|
1045
|
+
value: foldOwnBilling(this.deps.unit, read.events, read.seedLength),
|
|
1046
|
+
title: foldSessionTitle(read.events)
|
|
982
1047
|
};
|
|
983
1048
|
} catch (error) {
|
|
984
1049
|
logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
|
|
@@ -1029,7 +1094,7 @@ var TodaySpendScanner = class {
|
|
|
1029
1094
|
}
|
|
1030
1095
|
const persistenceService = persistence?.();
|
|
1031
1096
|
if (persistenceService === void 0) return total;
|
|
1032
|
-
const snapshots = await persistenceService
|
|
1097
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
1033
1098
|
const pending = [];
|
|
1034
1099
|
for (const { header, revision } of snapshots) {
|
|
1035
1100
|
if (liveIds.has(header.id)) continue;
|
|
@@ -1094,13 +1159,13 @@ var TodaySpendScanner = class {
|
|
|
1094
1159
|
}
|
|
1095
1160
|
const persistenceService = persistence?.();
|
|
1096
1161
|
if (!truncated && persistenceService !== void 0) {
|
|
1097
|
-
const snapshots = await persistenceService
|
|
1162
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
1098
1163
|
for (const { header, revision } of snapshots) {
|
|
1099
1164
|
if (liveIds.has(header.id)) continue;
|
|
1100
1165
|
if (this.lastEventsScan?.get(header.id) === revision) continue;
|
|
1101
1166
|
try {
|
|
1102
|
-
const
|
|
1103
|
-
collect(
|
|
1167
|
+
const read = await persistenceInspect(persistenceService, header.id);
|
|
1168
|
+
collect(read.events, read.seedLength);
|
|
1104
1169
|
} catch (error) {
|
|
1105
1170
|
logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
|
|
1106
1171
|
}
|
|
@@ -1141,7 +1206,7 @@ var TodaySpendScanner = class {
|
|
|
1141
1206
|
}
|
|
1142
1207
|
const persistenceService = persistence?.();
|
|
1143
1208
|
if (persistenceService === void 0) return [...rows.values()];
|
|
1144
|
-
const snapshots = await persistenceService
|
|
1209
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
1145
1210
|
const pending = [];
|
|
1146
1211
|
for (const { header, revision } of snapshots) {
|
|
1147
1212
|
if (liveIds.has(header.id)) continue;
|
|
@@ -1227,13 +1292,13 @@ var TodaySpendScanner = class {
|
|
|
1227
1292
|
}
|
|
1228
1293
|
const persistenceService = persistence?.();
|
|
1229
1294
|
if (!truncated && persistenceService !== void 0) {
|
|
1230
|
-
const snapshots = await persistenceService
|
|
1295
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
1231
1296
|
for (const { header, revision } of snapshots) {
|
|
1232
1297
|
if (liveIds.has(header.id)) continue;
|
|
1233
1298
|
if (this.lastEventsScan?.get(header.id) === revision) continue;
|
|
1234
1299
|
try {
|
|
1235
|
-
const
|
|
1236
|
-
collect(header.id,
|
|
1300
|
+
const read = await persistenceInspect(persistenceService, header.id);
|
|
1301
|
+
collect(header.id, read.events, read.seedLength);
|
|
1237
1302
|
} catch (error) {
|
|
1238
1303
|
logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
|
|
1239
1304
|
}
|
|
@@ -1290,6 +1355,14 @@ const DEFAULT_MODELS = [
|
|
|
1290
1355
|
{
|
|
1291
1356
|
id: "deepseek-v4-flash-vision-exp",
|
|
1292
1357
|
name: "DeepSeek-V4-Flash-Vision-Exp"
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
id: "mimo-v2.5-pro",
|
|
1361
|
+
name: "MiMo-V2.5-Pro"
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
id: "mimo-v2.5",
|
|
1365
|
+
name: "MiMo-V2.5"
|
|
1293
1366
|
}
|
|
1294
1367
|
];
|
|
1295
1368
|
const billingModel = z.object({
|
|
@@ -1342,11 +1415,7 @@ async function sessionEvents(ctx, sessionId) {
|
|
|
1342
1415
|
};
|
|
1343
1416
|
const persistence = ctx.get("sessionPersistence");
|
|
1344
1417
|
if (persistence !== void 0) try {
|
|
1345
|
-
|
|
1346
|
-
return {
|
|
1347
|
-
events: inspection.events,
|
|
1348
|
-
seedLength: forkBoundaryOf(inspection)
|
|
1349
|
-
};
|
|
1418
|
+
return await persistenceInspect(persistence, sessionId);
|
|
1350
1419
|
} catch (error) {
|
|
1351
1420
|
throw new LlmError(`llm-billing: session ${sessionId} not found`, "NOT_FOUND", { cause: error });
|
|
1352
1421
|
}
|
|
@@ -1440,4 +1509,4 @@ function apply(ctx, config) {
|
|
|
1440
1509
|
});
|
|
1441
1510
|
}
|
|
1442
1511
|
//#endregion
|
|
1443
|
-
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, foldOwnBilling, foldSessionTitle, forkBoundaryOf, isPeak, isSeededSession, liveSessionEvents, mergeTodaySpend, name, parseDeepSeekBalance, priceEvent, resolveBilling };
|
|
1512
|
+
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, foldOwnBilling, foldSessionTitle, forkBoundaryOf, isPeak, isSeededSession, liveSessionEvents, mergeTodaySpend, name, parseDeepSeekBalance, persistenceInspect, persistenceListSnapshots, priceEvent, resolveBilling };
|
package/lib/types/billing.d.ts
CHANGED
|
@@ -110,10 +110,12 @@ export interface ForkBoundarySource {
|
|
|
110
110
|
/** ≤ 0.1.1-rc.2: the header slice of a `Session` (its `header.seedLength`). */
|
|
111
111
|
readonly header?: {
|
|
112
112
|
readonly seedLength?: number;
|
|
113
|
+
readonly isSeeded?: boolean;
|
|
113
114
|
};
|
|
114
115
|
/** ≤ 0.1.1-rc.2: the `meta` of a persistence `inspect` result (its `seedLength`). */
|
|
115
116
|
readonly meta?: {
|
|
116
117
|
readonly seedLength?: number;
|
|
118
|
+
readonly isSeeded?: boolean;
|
|
117
119
|
};
|
|
118
120
|
}
|
|
119
121
|
/**
|
package/lib/types/billing.js
CHANGED
|
@@ -40,6 +40,17 @@ export const DEFAULT_MODEL_PRICING = [
|
|
|
40
40
|
peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
|
|
41
41
|
offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
|
|
42
42
|
},
|
|
43
|
+
// MiMo-V2.5 series (Xiaomi): flat rate, no peak/off-peak distinction.
|
|
44
|
+
{
|
|
45
|
+
model: 'mimo-v2.5-pro',
|
|
46
|
+
peak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
47
|
+
offPeak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
model: 'mimo-v2.5',
|
|
51
|
+
peak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
52
|
+
offPeak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
53
|
+
},
|
|
43
54
|
];
|
|
44
55
|
/**
|
|
45
56
|
* Resolve optional configuration to a pricing table, defaulting omitted or
|
package/lib/types/index.d.ts
CHANGED
|
@@ -28,9 +28,9 @@ export { addEventContribution, beijingDayKey, computeSessionSpend, computeTodayS
|
|
|
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, foldOwnBilling } from './projection.ts';
|
|
31
|
-
export type { BillingUnitState } from './projection.ts';
|
|
32
|
-
export { foldSessionTitle, liveSessionEvents, TodaySpendCache, TodaySpendScanner } from './today-spend.ts';
|
|
33
|
-
export type { ScannerPersistedHeader, ScannerSession, TodaySpendScannerDeps } from './today-spend.ts';
|
|
31
|
+
export type { BillingUnitFold, BillingUnitState } from './projection.ts';
|
|
32
|
+
export { foldSessionTitle, liveSessionEvents, persistenceInspect, persistenceListSnapshots, TodaySpendCache, TodaySpendScanner } from './today-spend.ts';
|
|
33
|
+
export type { ScannerPersistedHeader, ScannerPersistence, ScannerPersistenceHandle, ScannerPersistenceLegacy, ScannerPersistedRead, 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. */
|
|
36
36
|
export declare const PUBLIC_BASE_URL = "https://api.deepseek.com";
|
package/lib/types/index.js
CHANGED
|
@@ -27,11 +27,11 @@ import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
|
|
|
27
27
|
import { DeepSeekBalanceGateway, fetchDeepSeekBalance } from "./balance.js";
|
|
28
28
|
import { computeSessionSpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, forkBoundaryOf, mergeTodaySpend, resolveBilling, } from "./billing.js";
|
|
29
29
|
import { billingTodaySpendDefinition } from "./projection.js";
|
|
30
|
-
import { liveSessionEvents, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
|
|
30
|
+
import { liveSessionEvents, persistenceInspect, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
|
|
31
31
|
export { DeepSeekBalanceGateway, fetchDeepSeekBalance, parseDeepSeekBalance } from "./balance.js";
|
|
32
32
|
export { addEventContribution, beijingDayKey, computeSessionSpend, computeTodaySpend, computeTurnSpend, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, emptyTodaySpend, forkBoundaryOf, isPeak, isSeededSession, mergeTodaySpend, priceEvent, resolveBilling, SpendAccumulator, } from "./billing.js";
|
|
33
33
|
export { BILLING_UNIT_KEY, billingTodaySpendDefinition, foldBillingUnit, foldOwnBilling } from "./projection.js";
|
|
34
|
-
export { foldSessionTitle, liveSessionEvents, TodaySpendCache, TodaySpendScanner } from "./today-spend.js";
|
|
34
|
+
export { foldSessionTitle, liveSessionEvents, persistenceInspect, persistenceListSnapshots, 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';
|
|
@@ -41,6 +41,8 @@ const DEFAULT_MODELS = [
|
|
|
41
41
|
{ id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash' },
|
|
42
42
|
{ id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
|
|
43
43
|
{ id: 'deepseek-v4-flash-vision-exp', name: 'DeepSeek-V4-Flash-Vision-Exp' },
|
|
44
|
+
{ id: 'mimo-v2.5-pro', name: 'MiMo-V2.5-Pro' },
|
|
45
|
+
{ id: 'mimo-v2.5', name: 'MiMo-V2.5' },
|
|
44
46
|
];
|
|
45
47
|
const billingModel = z.object({
|
|
46
48
|
id: z.string().required(),
|
|
@@ -93,8 +95,7 @@ async function sessionEvents(ctx, sessionId) {
|
|
|
93
95
|
const persistence = ctx.get('sessionPersistence');
|
|
94
96
|
if (persistence !== undefined) {
|
|
95
97
|
try {
|
|
96
|
-
|
|
97
|
-
return { events: inspection.events, seedLength: forkBoundaryOf(inspection) };
|
|
98
|
+
return await persistenceInspect(persistence, sessionId);
|
|
98
99
|
}
|
|
99
100
|
catch (error) {
|
|
100
101
|
throw new LlmError(`llm-billing: session ${sessionId} not found`, 'NOT_FOUND', { cause: error });
|
|
@@ -64,8 +64,20 @@ export declare function billingTodaySpendDefinition(billing: ResolvedBilling, ca
|
|
|
64
64
|
id: string;
|
|
65
65
|
name: string;
|
|
66
66
|
}[]): BillingUnitDefinition;
|
|
67
|
+
/** The fold halves of a billing unit, as the detached cold recipes call them. */
|
|
68
|
+
export interface BillingUnitFold {
|
|
69
|
+
/**
|
|
70
|
+
* Initial state for the empty log. DSH ≤ 0.1.1-rc.2 declared `init()` with
|
|
71
|
+
* no parameters; 0.1.2-alpha.5+ passes the Session header and inherited
|
|
72
|
+
* count. The unit ignores both, so the structural type accepts either call
|
|
73
|
+
* shape.
|
|
74
|
+
*/
|
|
75
|
+
init(...metadata: never[]): BillingUnitState;
|
|
76
|
+
/** Pure transition: previous state + one committed event → next state. */
|
|
77
|
+
apply(state: BillingUnitState, event: SessionEvent): BillingUnitState;
|
|
78
|
+
}
|
|
67
79
|
/** Fold a unit from init over one session's event log (the detached cold recipe). */
|
|
68
|
-
export declare function foldBillingUnit(unit:
|
|
80
|
+
export declare function foldBillingUnit(unit: BillingUnitFold, events: readonly SessionEvent[]): BillingUnitState;
|
|
69
81
|
/**
|
|
70
82
|
* Fold a unit from init over one session's OWN events only: the complete log
|
|
71
83
|
* minus its inherited fork prefix (`seq < seedLength`). A forked child's
|
|
@@ -78,5 +90,5 @@ export declare function foldBillingUnit(unit: Pick<ProjectionDefinition<'billing
|
|
|
78
90
|
* ({@link forkBoundaryOf}); 0 for an unseeded session.
|
|
79
91
|
* @returns the unit state folded over the session's own events.
|
|
80
92
|
*/
|
|
81
|
-
export declare function foldOwnBilling(unit:
|
|
93
|
+
export declare function foldOwnBilling(unit: BillingUnitFold, events: readonly SessionEvent[], seedLength?: number): BillingUnitState;
|
|
82
94
|
//# sourceMappingURL=projection.d.ts.map
|
|
@@ -42,8 +42,7 @@ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
|
|
|
42
42
|
import type { SessionPersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
|
|
43
43
|
import type { ResolvedBilling } from './billing.ts';
|
|
44
44
|
import type { DeepSeekTodaySessionsSpend, DeepSeekTodaySpend } from './types.ts';
|
|
45
|
-
import { BILLING_UNIT_KEY, type BillingUnitState } from './projection.ts';
|
|
46
|
-
import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection';
|
|
45
|
+
import { BILLING_UNIT_KEY, type BillingUnitFold, type BillingUnitState } from './projection.ts';
|
|
47
46
|
/**
|
|
48
47
|
* Fold one session's durable display title: the latest `session/title`
|
|
49
48
|
* event's text (last-wins, matching the `title` projection), or `null` before
|
|
@@ -64,7 +63,7 @@ export interface ScannerSession {
|
|
|
64
63
|
/** ≤ 0.1.1-rc.2: the full event log snapshot. */
|
|
65
64
|
readonly events?: readonly SessionEvent[];
|
|
66
65
|
/** 0.1.2-alpha.4+: materialize an immutable log snapshot; no args = the full current log. */
|
|
67
|
-
|
|
66
|
+
snapshotEvents?(fromSeq?: number, toSeqExclusive?: number): readonly SessionEvent[];
|
|
68
67
|
/** 0.1.2-alpha.4+: the durable inherited-prefix length (0 for an unseeded session). */
|
|
69
68
|
readonly inheritedEventCount?: number;
|
|
70
69
|
/** Durable header slice: `seedLength` (older runtime) or `isSeeded` (newer runtime). */
|
|
@@ -88,6 +87,67 @@ export interface ScannerPersistedHeader {
|
|
|
88
87
|
/** 0.1.2-alpha.4+: whether the session has a fork-inherited prefix (exact cut arrives with the inspect result). */
|
|
89
88
|
readonly isSeeded?: boolean;
|
|
90
89
|
}
|
|
90
|
+
/** One stored-session read: the full event log plus the durable inherited boundary. */
|
|
91
|
+
export interface ScannerPersistedRead {
|
|
92
|
+
readonly events: readonly SessionEvent[];
|
|
93
|
+
/** Inherited-prefix length (fork seed length); 0 for an unseeded session. */
|
|
94
|
+
readonly seedLength: number;
|
|
95
|
+
}
|
|
96
|
+
/** ≤ 0.1.1-rc.2 persistence slice: service-level `inspect` / `listSnapshots`. */
|
|
97
|
+
export interface ScannerPersistenceLegacy {
|
|
98
|
+
listSnapshots(): Promise<readonly {
|
|
99
|
+
header: ScannerPersistedHeader;
|
|
100
|
+
revision: SessionPersistenceRevision;
|
|
101
|
+
}[]>;
|
|
102
|
+
inspect(id: SessionId): Promise<{
|
|
103
|
+
meta?: {
|
|
104
|
+
readonly seedLength?: number;
|
|
105
|
+
readonly isSeeded?: boolean;
|
|
106
|
+
};
|
|
107
|
+
/** 0.1.2-alpha.4+: the exact inherited cut travels beside, not inside, the header. */
|
|
108
|
+
inheritedEventCount?: number;
|
|
109
|
+
events: readonly SessionEvent[];
|
|
110
|
+
}>;
|
|
111
|
+
}
|
|
112
|
+
/** 0.1.2-alpha.5+ handle-based persistence slice: service-level `list` / `open` + `SessionHandle`. */
|
|
113
|
+
export interface ScannerPersistenceHandle {
|
|
114
|
+
list(): Promise<readonly {
|
|
115
|
+
header: ScannerPersistedHeader;
|
|
116
|
+
revision: SessionPersistenceRevision;
|
|
117
|
+
}[]>;
|
|
118
|
+
open(id: SessionId, access: 'read'): Promise<{
|
|
119
|
+
readonly header?: {
|
|
120
|
+
readonly seedLength?: number;
|
|
121
|
+
readonly isSeeded?: boolean;
|
|
122
|
+
};
|
|
123
|
+
/** 0.1.2-alpha.5+: the handle carries the exact inherited cut beside the header. */
|
|
124
|
+
readonly inheritedEventCount?: number;
|
|
125
|
+
read(): Promise<readonly SessionEvent[]>;
|
|
126
|
+
close(): Promise<void>;
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
/** Structural union the scanner reads through, accepting both persistence runtime families. */
|
|
130
|
+
export type ScannerPersistence = ScannerPersistenceLegacy | ScannerPersistenceHandle;
|
|
131
|
+
/**
|
|
132
|
+
* List every stored session snapshot across both persistence runtime families:
|
|
133
|
+
* `listSnapshots` (≤ 0.1.1-rc.2) or `list` (0.1.2-alpha.5+).
|
|
134
|
+
* @param persistence - the persistence service slice.
|
|
135
|
+
* @returns one snapshot per stored session.
|
|
136
|
+
*/
|
|
137
|
+
export declare function persistenceListSnapshots(persistence: ScannerPersistence): Promise<readonly {
|
|
138
|
+
header: ScannerPersistedHeader;
|
|
139
|
+
revision: SessionPersistenceRevision;
|
|
140
|
+
}[]>;
|
|
141
|
+
/**
|
|
142
|
+
* Read one stored session's complete event log and durable inherited boundary
|
|
143
|
+
* across both persistence runtime families: legacy `inspect` (≤ 0.1.1-rc.2)
|
|
144
|
+
* or `open` + handle `read` (0.1.2-alpha.5+; the handle is closed after the
|
|
145
|
+
* read). Both throw when the session does not exist.
|
|
146
|
+
* @param persistence - the persistence service slice.
|
|
147
|
+
* @param id - the stored session to read.
|
|
148
|
+
* @returns the session's complete event log plus its inherited-prefix boundary.
|
|
149
|
+
*/
|
|
150
|
+
export declare function persistenceInspect(persistence: ScannerPersistence, id: SessionId): Promise<ScannerPersistedRead>;
|
|
91
151
|
/** Structural slices of the optional services the scanner reads through. */
|
|
92
152
|
export interface TodaySpendScannerDeps {
|
|
93
153
|
/** Resolves the live SessionStore at scan time (absent in headless assemblies). */
|
|
@@ -95,21 +155,7 @@ export interface TodaySpendScannerDeps {
|
|
|
95
155
|
list(): readonly ScannerSession[];
|
|
96
156
|
} | undefined;
|
|
97
157
|
/** Resolves the persistence backend at scan time (absent without persistence). */
|
|
98
|
-
persistence?: () =>
|
|
99
|
-
listSnapshots(): Promise<readonly {
|
|
100
|
-
header: ScannerPersistedHeader;
|
|
101
|
-
revision: SessionPersistenceRevision;
|
|
102
|
-
}[]>;
|
|
103
|
-
inspect(id: SessionId): Promise<{
|
|
104
|
-
meta?: {
|
|
105
|
-
readonly seedLength?: number;
|
|
106
|
-
readonly isSeeded?: boolean;
|
|
107
|
-
};
|
|
108
|
-
/** 0.1.2-alpha.4+: the exact inherited cut travels beside, not inside, the header. */
|
|
109
|
-
inheritedEventCount?: number;
|
|
110
|
-
events: readonly SessionEvent[];
|
|
111
|
-
}>;
|
|
112
|
-
} | undefined;
|
|
158
|
+
persistence?: () => ScannerPersistence | undefined;
|
|
113
159
|
/** Resolves the session-projection registry at scan time (absent → events path). */
|
|
114
160
|
projections?: () => {
|
|
115
161
|
stateOf(session: ScannerSession, key: typeof BILLING_UNIT_KEY): BillingUnitState | undefined;
|
|
@@ -128,7 +174,7 @@ export interface TodaySpendScannerDeps {
|
|
|
128
174
|
*/
|
|
129
175
|
ensureUnit?: () => void;
|
|
130
176
|
/** The billing unit's fold (the projection path's detached cold recipe). */
|
|
131
|
-
unit:
|
|
177
|
+
unit: BillingUnitFold;
|
|
132
178
|
/** Hard cap on today's events collected by the events path. */
|
|
133
179
|
maxEvents: number;
|
|
134
180
|
/** Warn sink for truncation and unreadable sessions. */
|
package/lib/types/today-spend.js
CHANGED
|
@@ -75,6 +75,42 @@ export function liveSessionEvents(session) {
|
|
|
75
75
|
return session.snapshotEvents();
|
|
76
76
|
throw new Error('llm-billing: session log surface is neither Session.events nor Session.snapshotEvents');
|
|
77
77
|
}
|
|
78
|
+
function isHandlePersistence(persistence) {
|
|
79
|
+
return typeof persistence.open === 'function';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* List every stored session snapshot across both persistence runtime families:
|
|
83
|
+
* `listSnapshots` (≤ 0.1.1-rc.2) or `list` (0.1.2-alpha.5+).
|
|
84
|
+
* @param persistence - the persistence service slice.
|
|
85
|
+
* @returns one snapshot per stored session.
|
|
86
|
+
*/
|
|
87
|
+
export function persistenceListSnapshots(persistence) {
|
|
88
|
+
return isHandlePersistence(persistence)
|
|
89
|
+
? persistence.list()
|
|
90
|
+
: persistence.listSnapshots();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read one stored session's complete event log and durable inherited boundary
|
|
94
|
+
* across both persistence runtime families: legacy `inspect` (≤ 0.1.1-rc.2)
|
|
95
|
+
* or `open` + handle `read` (0.1.2-alpha.5+; the handle is closed after the
|
|
96
|
+
* read). Both throw when the session does not exist.
|
|
97
|
+
* @param persistence - the persistence service slice.
|
|
98
|
+
* @param id - the stored session to read.
|
|
99
|
+
* @returns the session's complete event log plus its inherited-prefix boundary.
|
|
100
|
+
*/
|
|
101
|
+
export async function persistenceInspect(persistence, id) {
|
|
102
|
+
if (isHandlePersistence(persistence)) {
|
|
103
|
+
const handle = await persistence.open(id, 'read');
|
|
104
|
+
try {
|
|
105
|
+
return { events: await handle.read(), seedLength: forkBoundaryOf(handle) };
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await handle.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const inspection = await persistence.inspect(id);
|
|
112
|
+
return { events: inspection.events, seedLength: forkBoundaryOf(inspection) };
|
|
113
|
+
}
|
|
78
114
|
/**
|
|
79
115
|
* Bounded parallel fan-out: run `run` over `items` with at most `limit` in
|
|
80
116
|
* flight. A shared index counter hands each worker its next job, so the
|
|
@@ -225,10 +261,10 @@ export class TodaySpendScanner {
|
|
|
225
261
|
}
|
|
226
262
|
}
|
|
227
263
|
try {
|
|
228
|
-
const
|
|
264
|
+
const read = await persistenceInspect(persistenceService, id);
|
|
229
265
|
return {
|
|
230
|
-
value: foldOwnBilling(this.deps.unit,
|
|
231
|
-
title: foldSessionTitle(
|
|
266
|
+
value: foldOwnBilling(this.deps.unit, read.events, read.seedLength),
|
|
267
|
+
title: foldSessionTitle(read.events),
|
|
232
268
|
};
|
|
233
269
|
}
|
|
234
270
|
catch (error) {
|
|
@@ -293,7 +329,7 @@ export class TodaySpendScanner {
|
|
|
293
329
|
const persistenceService = persistence?.();
|
|
294
330
|
if (persistenceService === undefined)
|
|
295
331
|
return total;
|
|
296
|
-
const snapshots = await persistenceService
|
|
332
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
297
333
|
const pending = [];
|
|
298
334
|
for (const { header, revision } of snapshots) {
|
|
299
335
|
if (liveIds.has(header.id))
|
|
@@ -362,15 +398,15 @@ export class TodaySpendScanner {
|
|
|
362
398
|
}
|
|
363
399
|
const persistenceService = persistence?.();
|
|
364
400
|
if (!truncated && persistenceService !== undefined) {
|
|
365
|
-
const snapshots = await persistenceService
|
|
401
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
366
402
|
for (const { header, revision } of snapshots) {
|
|
367
403
|
if (liveIds.has(header.id))
|
|
368
404
|
continue;
|
|
369
405
|
if (this.lastEventsScan?.get(header.id) === revision)
|
|
370
406
|
continue;
|
|
371
407
|
try {
|
|
372
|
-
const
|
|
373
|
-
collect(
|
|
408
|
+
const read = await persistenceInspect(persistenceService, header.id);
|
|
409
|
+
collect(read.events, read.seedLength);
|
|
374
410
|
}
|
|
375
411
|
catch (error) {
|
|
376
412
|
// One unreadable session must not blank the whole-day aggregate.
|
|
@@ -427,7 +463,7 @@ export class TodaySpendScanner {
|
|
|
427
463
|
const persistenceService = persistence?.();
|
|
428
464
|
if (persistenceService === undefined)
|
|
429
465
|
return [...rows.values()];
|
|
430
|
-
const snapshots = await persistenceService
|
|
466
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
431
467
|
const pending = [];
|
|
432
468
|
for (const { header, revision } of snapshots) {
|
|
433
469
|
if (liveIds.has(header.id))
|
|
@@ -507,15 +543,15 @@ export class TodaySpendScanner {
|
|
|
507
543
|
}
|
|
508
544
|
const persistenceService = persistence?.();
|
|
509
545
|
if (!truncated && persistenceService !== undefined) {
|
|
510
|
-
const snapshots = await persistenceService
|
|
546
|
+
const snapshots = await persistenceListSnapshots(persistenceService);
|
|
511
547
|
for (const { header, revision } of snapshots) {
|
|
512
548
|
if (liveIds.has(header.id))
|
|
513
549
|
continue;
|
|
514
550
|
if (this.lastEventsScan?.get(header.id) === revision)
|
|
515
551
|
continue;
|
|
516
552
|
try {
|
|
517
|
-
const
|
|
518
|
-
collect(header.id,
|
|
553
|
+
const read = await persistenceInspect(persistenceService, header.id);
|
|
554
|
+
collect(header.id, read.events, read.seedLength);
|
|
519
555
|
}
|
|
520
556
|
catch (error) {
|
|
521
557
|
// One unreadable session must not blank the whole-day aggregate.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rayadesu/dsh-llm-billing",
|
|
3
3
|
"description": "Standalone DeepSeek account-balance and session-spend provider exposed through the billing Remote",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
},
|
|
53
53
|
"license": "MIT",
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-launch-environment": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
62
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
55
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.5",
|
|
56
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.5",
|
|
57
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.5",
|
|
58
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
|
|
59
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
|
|
60
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.5",
|
|
61
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.5",
|
|
62
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.5",
|
|
63
63
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"zod": "^4.4.3"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
71
|
-
"@deepseek-ai/dsh-launch-environment": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
74
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
75
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
76
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
77
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
70
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.5",
|
|
71
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.5",
|
|
72
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.5",
|
|
73
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
|
|
74
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
|
|
75
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.5",
|
|
76
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.5",
|
|
77
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.5",
|
|
78
78
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
79
79
|
}
|
|
80
80
|
}
|