@guojinpeng/dsh-cost-meter 0.1.2 → 0.1.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.md +21 -16
- package/README.zh-CN.md +20 -16
- package/dist/client.js +748 -722
- package/dist/index.js +129 -111
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// src/index.js
|
|
2
|
-
import { copyFileSync
|
|
3
|
-
import {
|
|
4
|
-
import { dirname as dirname2, join } from "path";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { createHash } from "crypto";
|
|
2
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync2 } from "fs";
|
|
3
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
7
4
|
|
|
8
5
|
// src/pricing.js
|
|
9
6
|
var RATE_KEYS = ["input", "output", "cacheRead", "cacheWrite"];
|
|
@@ -294,14 +291,10 @@ var PLUGIN_ID = "guojinpeng/dsh-cost-meter";
|
|
|
294
291
|
var HTTP_BASE = "/guojinpeng/dsh-cost-meter";
|
|
295
292
|
var PROJECTION_KEY = "guojinpengCostUsage";
|
|
296
293
|
|
|
297
|
-
// src/
|
|
298
|
-
import {
|
|
299
|
-
import {
|
|
300
|
-
|
|
301
|
-
var name = PLUGIN_ID;
|
|
302
|
-
var TURN_RETENTION = 200;
|
|
303
|
-
var CONFIG_ROUTE = `${HTTP_BASE}/config`;
|
|
304
|
-
var MAX_BODY_BYTES = 1024 * 1024;
|
|
294
|
+
// src/server/config-store.js
|
|
295
|
+
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
296
|
+
import { homedir } from "os";
|
|
297
|
+
import { dirname as dirname2, join } from "path";
|
|
305
298
|
var STAT_INTERVAL_MS = 1e3;
|
|
306
299
|
var CONFIG_DIR_NAME = "dsh-cost-meter";
|
|
307
300
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -383,6 +376,84 @@ function seedConfigFile(logger) {
|
|
|
383
376
|
);
|
|
384
377
|
}
|
|
385
378
|
}
|
|
379
|
+
|
|
380
|
+
// src/server/projection.js
|
|
381
|
+
import { deriveTurnTokenUsage } from "@deepseek-ai/dsh-token-meter/client";
|
|
382
|
+
|
|
383
|
+
// src/server/schemas.js
|
|
384
|
+
import { z } from "zod";
|
|
385
|
+
var int = z.number().int().nonnegative();
|
|
386
|
+
var bucketSchema = z.object({
|
|
387
|
+
uncachedInputTokens: int,
|
|
388
|
+
outputTokens: int,
|
|
389
|
+
cacheReadTokens: int,
|
|
390
|
+
cacheWriteTokens: int
|
|
391
|
+
}).strict();
|
|
392
|
+
var routeSchema = z.object({ provider: z.string(), model: z.string() }).strict();
|
|
393
|
+
var lastSlotSchema = z.object({ turn: int, step: int, buckets: bucketSchema, route: routeSchema.nullable() }).strict();
|
|
394
|
+
var turnRecordSchema = z.object({
|
|
395
|
+
totalTokens: int,
|
|
396
|
+
tokens: bucketSchema,
|
|
397
|
+
byRoute: z.record(z.string(), bucketSchema),
|
|
398
|
+
recovered: z.boolean()
|
|
399
|
+
}).strict();
|
|
400
|
+
var stateSchema = z.object({
|
|
401
|
+
totals: bucketSchema,
|
|
402
|
+
byRoute: z.record(z.string(), bucketSchema),
|
|
403
|
+
last: lastSlotSchema.nullable(),
|
|
404
|
+
buffer: z.array(z.any()),
|
|
405
|
+
bufferTurn: z.number().int().nullable(),
|
|
406
|
+
turns: z.record(z.string(), turnRecordSchema),
|
|
407
|
+
skippedTurns: int
|
|
408
|
+
}).strict();
|
|
409
|
+
var wireTokensSchema = z.object({ input: int, output: int, read: int, write: int }).strict();
|
|
410
|
+
var wireTurnSchema = z.object({
|
|
411
|
+
micros: int,
|
|
412
|
+
totalTokens: int,
|
|
413
|
+
route: z.string(),
|
|
414
|
+
tokens: wireTokensSchema,
|
|
415
|
+
bucketMicros: wireTokensSchema,
|
|
416
|
+
priced: z.boolean(),
|
|
417
|
+
recovered: z.boolean()
|
|
418
|
+
}).strict();
|
|
419
|
+
var wireRouteSchema = z.object({ route: z.string(), micros: int, totalTokens: int, priced: z.boolean() }).strict();
|
|
420
|
+
var viewSchema = z.object({
|
|
421
|
+
accounting: z.object({
|
|
422
|
+
totals: bucketSchema,
|
|
423
|
+
byRoute: z.record(z.string(), bucketSchema),
|
|
424
|
+
turns: z.record(z.string(), turnRecordSchema),
|
|
425
|
+
skippedTurns: int
|
|
426
|
+
}).strict(),
|
|
427
|
+
currency: z.string(),
|
|
428
|
+
cnyPerUsd: z.number(),
|
|
429
|
+
totalMicros: int,
|
|
430
|
+
totalTokens: int,
|
|
431
|
+
tokens: wireTokensSchema,
|
|
432
|
+
bucketMicros: wireTokensSchema,
|
|
433
|
+
cacheHitPct: z.number().nullable(),
|
|
434
|
+
skippedTurns: int,
|
|
435
|
+
unpricedTokens: int,
|
|
436
|
+
showDock: z.boolean(),
|
|
437
|
+
showTurn: z.boolean(),
|
|
438
|
+
routes: z.array(wireRouteSchema),
|
|
439
|
+
turns: z.record(z.string(), wireTurnSchema)
|
|
440
|
+
}).strict();
|
|
441
|
+
var rateSchema = z.object({
|
|
442
|
+
input: z.number().nonnegative(),
|
|
443
|
+
output: z.number().nonnegative(),
|
|
444
|
+
cacheRead: z.number().nonnegative(),
|
|
445
|
+
cacheWrite: z.number().nonnegative()
|
|
446
|
+
}).strict();
|
|
447
|
+
var configInputSchema = z.object({
|
|
448
|
+
currency: z.enum(["USD", "CNY"]),
|
|
449
|
+
cnyPerUsd: z.number().positive(),
|
|
450
|
+
showDockChip: z.boolean(),
|
|
451
|
+
showTurnChip: z.boolean(),
|
|
452
|
+
pricing: z.record(z.string(), rateSchema)
|
|
453
|
+
}).strict();
|
|
454
|
+
|
|
455
|
+
// src/server/usage-events.js
|
|
456
|
+
import { lastAssistantStreamChunk } from "@deepseek-ai/dsh-llm";
|
|
386
457
|
function zeroBuckets() {
|
|
387
458
|
return { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
388
459
|
}
|
|
@@ -471,62 +542,34 @@ function compactEvent(event) {
|
|
|
471
542
|
if (sample !== void 0) compact.stream = [{ type: "chunk", chunk: { type: "usage", usage: sample } }];
|
|
472
543
|
return { type, data: compact };
|
|
473
544
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
cacheReadTokens:
|
|
479
|
-
cacheWriteTokens:
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
route: z.string(),
|
|
503
|
-
tokens: wireTokensSchema,
|
|
504
|
-
bucketMicros: wireTokensSchema,
|
|
505
|
-
priced: z.boolean(),
|
|
506
|
-
recovered: z.boolean()
|
|
507
|
-
}).strict();
|
|
508
|
-
var wireRouteSchema = z.object({ route: z.string(), micros: int, totalTokens: int, priced: z.boolean() }).strict();
|
|
509
|
-
var viewSchema = z.object({
|
|
510
|
-
accounting: z.object({
|
|
511
|
-
totals: bucketSchema,
|
|
512
|
-
byRoute: z.record(z.string(), bucketSchema),
|
|
513
|
-
turns: z.record(z.string(), turnRecordSchema),
|
|
514
|
-
skippedTurns: int
|
|
515
|
-
}).strict(),
|
|
516
|
-
currency: z.string(),
|
|
517
|
-
cnyPerUsd: z.number(),
|
|
518
|
-
totalMicros: int,
|
|
519
|
-
totalTokens: int,
|
|
520
|
-
tokens: wireTokensSchema,
|
|
521
|
-
bucketMicros: wireTokensSchema,
|
|
522
|
-
cacheHitPct: z.number().nullable(),
|
|
523
|
-
skippedTurns: int,
|
|
524
|
-
unpricedTokens: int,
|
|
525
|
-
showDock: z.boolean(),
|
|
526
|
-
showTurn: z.boolean(),
|
|
527
|
-
routes: z.array(wireRouteSchema),
|
|
528
|
-
turns: z.record(z.string(), wireTurnSchema)
|
|
529
|
-
}).strict();
|
|
545
|
+
function recoverBuckets(aggregate) {
|
|
546
|
+
const input = aggregate.uncachedInputTokens;
|
|
547
|
+
const output = aggregate.outputTokens;
|
|
548
|
+
const prompt = Math.max(0, aggregate.totalTokens - output);
|
|
549
|
+
let read = typeof aggregate.cacheReadTokens === "number" ? aggregate.cacheReadTokens : null;
|
|
550
|
+
let write = typeof aggregate.cacheWriteTokens === "number" ? aggregate.cacheWriteTokens : null;
|
|
551
|
+
const recovered = read === null || write === null;
|
|
552
|
+
if (read === null && write === null) {
|
|
553
|
+
read = Math.max(0, prompt - input);
|
|
554
|
+
write = 0;
|
|
555
|
+
} else if (read === null) {
|
|
556
|
+
read = Math.max(0, prompt - input - write);
|
|
557
|
+
} else if (write === null) {
|
|
558
|
+
write = Math.max(0, prompt - input - read);
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
buckets: {
|
|
562
|
+
uncachedInputTokens: input,
|
|
563
|
+
outputTokens: output,
|
|
564
|
+
cacheReadTokens: read,
|
|
565
|
+
cacheWriteTokens: write
|
|
566
|
+
},
|
|
567
|
+
recovered
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/server/projection.js
|
|
572
|
+
var TURN_RETENTION = 200;
|
|
530
573
|
function initialState() {
|
|
531
574
|
return {
|
|
532
575
|
totals: zeroBuckets(),
|
|
@@ -597,31 +640,6 @@ function applySample(state, event) {
|
|
|
597
640
|
last: { turn, step, buckets, route: nextRoute }
|
|
598
641
|
};
|
|
599
642
|
}
|
|
600
|
-
function recoverBuckets(aggregate) {
|
|
601
|
-
const input = aggregate.uncachedInputTokens;
|
|
602
|
-
const output = aggregate.outputTokens;
|
|
603
|
-
const prompt = Math.max(0, aggregate.totalTokens - output);
|
|
604
|
-
let read = typeof aggregate.cacheReadTokens === "number" ? aggregate.cacheReadTokens : null;
|
|
605
|
-
let write = typeof aggregate.cacheWriteTokens === "number" ? aggregate.cacheWriteTokens : null;
|
|
606
|
-
const recovered = read === null || write === null;
|
|
607
|
-
if (read === null && write === null) {
|
|
608
|
-
read = Math.max(0, prompt - input);
|
|
609
|
-
write = 0;
|
|
610
|
-
} else if (read === null) {
|
|
611
|
-
read = Math.max(0, prompt - input - write);
|
|
612
|
-
} else if (write === null) {
|
|
613
|
-
write = Math.max(0, prompt - input - read);
|
|
614
|
-
}
|
|
615
|
-
return {
|
|
616
|
-
buckets: {
|
|
617
|
-
uncachedInputTokens: input,
|
|
618
|
-
outputTokens: output,
|
|
619
|
-
cacheReadTokens: read,
|
|
620
|
-
cacheWriteTokens: write
|
|
621
|
-
},
|
|
622
|
-
recovered
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
643
|
function turnBucketsByRoute(events) {
|
|
626
644
|
var _a;
|
|
627
645
|
let byRoute = {};
|
|
@@ -725,6 +743,11 @@ function createCostUsageProjection(readResolvedConfig = readConfig) {
|
|
|
725
743
|
function configSignature(config) {
|
|
726
744
|
return JSON.stringify([config.currency, config.cnyPerUsd, config.showDockChip, config.showTurnChip, config.pricing]);
|
|
727
745
|
}
|
|
746
|
+
|
|
747
|
+
// src/server/config-handler.js
|
|
748
|
+
import { createHash } from "crypto";
|
|
749
|
+
import { z as z2 } from "zod";
|
|
750
|
+
var MAX_BODY_BYTES = 1024 * 1024;
|
|
728
751
|
function respond(res, status, payload) {
|
|
729
752
|
res.writeHead(status, {
|
|
730
753
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -749,19 +772,6 @@ function readBody(req, limit) {
|
|
|
749
772
|
});
|
|
750
773
|
}
|
|
751
774
|
var revisionOf = (config) => createHash("sha256").update(JSON.stringify(config)).digest("hex");
|
|
752
|
-
var rateSchema = z.object({
|
|
753
|
-
input: z.number().nonnegative(),
|
|
754
|
-
output: z.number().nonnegative(),
|
|
755
|
-
cacheRead: z.number().nonnegative(),
|
|
756
|
-
cacheWrite: z.number().nonnegative()
|
|
757
|
-
}).strict();
|
|
758
|
-
var configInputSchema = z.object({
|
|
759
|
-
currency: z.enum(["USD", "CNY"]),
|
|
760
|
-
cnyPerUsd: z.number().positive(),
|
|
761
|
-
showDockChip: z.boolean(),
|
|
762
|
-
showTurnChip: z.boolean(),
|
|
763
|
-
pricing: z.record(z.string(), rateSchema)
|
|
764
|
-
}).strict();
|
|
765
775
|
async function configPayload(history) {
|
|
766
776
|
const config = await history.config();
|
|
767
777
|
return { config, models: history.list(), revision: revisionOf(config), path: CONFIG_PATH };
|
|
@@ -813,18 +823,23 @@ function createConfigHandler(connection, history, prices = createModelsDev()) {
|
|
|
813
823
|
history.save(next);
|
|
814
824
|
respond(res, 200, { ...await configPayload(history), ...report ? { report } : {} });
|
|
815
825
|
} catch (error) {
|
|
816
|
-
respond(res, error instanceof
|
|
826
|
+
respond(res, error instanceof z2.ZodError ? 400 : 502, { error: error.message });
|
|
817
827
|
}
|
|
818
828
|
return;
|
|
819
829
|
}
|
|
820
830
|
respond(res, 405, { error: "method not allowed" });
|
|
821
831
|
};
|
|
822
832
|
}
|
|
833
|
+
|
|
834
|
+
// src/index.js
|
|
835
|
+
var inject = ["sessionProjections"];
|
|
836
|
+
var name = PLUGIN_ID;
|
|
837
|
+
var CONFIG_ROUTE = `${HTTP_BASE}/config`;
|
|
823
838
|
function apply(ctx) {
|
|
824
839
|
seedConfigFile(ctx.logger);
|
|
825
840
|
ctx.sessionProjections.register(createCostUsageProjection());
|
|
826
841
|
ctx.inject(["sessionQuery"], (queryCtx) => {
|
|
827
|
-
const prices = createModelsDev(void 0,
|
|
842
|
+
const prices = createModelsDev(void 0, join2(dirname3(CONFIG_PATH), "data", "models.dev.json"));
|
|
828
843
|
const history = createHistoryModels(queryCtx.sessionQuery, {
|
|
829
844
|
readConfig,
|
|
830
845
|
writeConfig,
|
|
@@ -835,7 +850,7 @@ function apply(ctx) {
|
|
|
835
850
|
},
|
|
836
851
|
backupConfig: () => {
|
|
837
852
|
const backup = `${CONFIG_PATH}.v0.1.1.bak`;
|
|
838
|
-
if (
|
|
853
|
+
if (existsSync2(CONFIG_PATH) && !existsSync2(backup)) copyFileSync2(CONFIG_PATH, backup);
|
|
839
854
|
}
|
|
840
855
|
});
|
|
841
856
|
queryCtx.on("session/event", (_session, event) => {
|
|
@@ -851,7 +866,10 @@ function apply(ctx) {
|
|
|
851
866
|
queryCtx.inject(["webServer", "connection"], (webCtx) => {
|
|
852
867
|
const handler = createConfigHandler(webCtx.connection, history, prices);
|
|
853
868
|
for (const path of [CONFIG_ROUTE, `${HTTP_BASE}/pricing/sync`]) {
|
|
854
|
-
webCtx.effect(
|
|
869
|
+
webCtx.effect(
|
|
870
|
+
() => webCtx.webServer.register({ kind: "exact", path, handler }),
|
|
871
|
+
`guojinpeng/dsh-cost-meter: ${path}`
|
|
872
|
+
);
|
|
855
873
|
}
|
|
856
874
|
});
|
|
857
875
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guojinpeng/dsh-cost-meter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH 会话和每轮费用估算插件,支持 models.dev 价格同步与自定义模型单价。配置位于 ~/.dsh/guojinpeng/dsh-cost-meter/config.json。",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"cordis.patch.yml"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsup src/index.js src/client.js --format esm --out-dir dist --clean --no-splitting",
|
|
36
|
+
"build": "tsup src/index.js src/client.js --format esm --out-dir dist --clean --no-splitting --loader .css=text",
|
|
37
37
|
"prepack": "npm run build",
|
|
38
|
+
"pack:release": "npm pack",
|
|
38
39
|
"test": "node --test tests/*.test.js"
|
|
39
40
|
},
|
|
40
41
|
"dsh": {
|