@guojinpeng/dsh-cost-meter 0.1.3 → 0.1.5

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/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  // src/index.js
2
- import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
3
- import { homedir } from "os";
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"];
@@ -119,7 +116,7 @@ function createHistoryModels(sessionQuery, { readConfig: readConfig2, writeConfi
119
116
  let missing;
120
117
  do {
121
118
  missing = Object.fromEntries([...models].filter(needsPrice).map((key) => [key, zeroRate()]));
122
- if (!initializedOnce || Object.keys(missing).length) {
119
+ if (Object.keys(missing).length || !initializedOnce && !Object.keys(config.pricing).length) {
123
120
  initializedOnce = true;
124
121
  try {
125
122
  Object.assign(initialized, (await initializePrices({ ...config, pricing: missing })).config.pricing);
@@ -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/index.js
298
- import { lastAssistantStreamChunk } from "@deepseek-ai/dsh-llm";
299
- import { deriveTurnTokenUsage } from "@deepseek-ai/dsh-token-meter/client";
300
- var inject = ["sessionProjections"];
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
- var int = z.number().int().nonnegative();
475
- var bucketSchema = z.object({
476
- uncachedInputTokens: int,
477
- outputTokens: int,
478
- cacheReadTokens: int,
479
- cacheWriteTokens: int
480
- }).strict();
481
- var routeSchema = z.object({ provider: z.string(), model: z.string() }).strict();
482
- var lastSlotSchema = z.object({ turn: int, step: int, buckets: bucketSchema, route: routeSchema.nullable() }).strict();
483
- var turnRecordSchema = z.object({
484
- totalTokens: int,
485
- tokens: bucketSchema,
486
- byRoute: z.record(z.string(), bucketSchema),
487
- recovered: z.boolean()
488
- }).strict();
489
- var stateSchema = z.object({
490
- totals: bucketSchema,
491
- byRoute: z.record(z.string(), bucketSchema),
492
- last: lastSlotSchema.nullable(),
493
- buffer: z.array(z.any()),
494
- bufferTurn: z.number().int().nullable(),
495
- turns: z.record(z.string(), turnRecordSchema),
496
- skippedTurns: int
497
- }).strict();
498
- var wireTokensSchema = z.object({ input: int, output: int, read: int, write: int }).strict();
499
- var wireTurnSchema = z.object({
500
- micros: int,
501
- totalTokens: int,
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 z.ZodError ? 400 : 502, { error: error.message });
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, join(dirname2(CONFIG_PATH), "data", "models.dev.json"));
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 (existsSync(CONFIG_PATH) && !existsSync(backup)) copyFileSync(CONFIG_PATH, backup);
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(() => webCtx.webServer.register({ kind: "exact", path, handler }), `guojinpeng/dsh-cost-meter: ${path}`);
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",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "DSH 会话和每轮费用估算插件,支持 models.dev 价格同步与自定义模型单价。配置位于 ~/.dsh/guojinpeng/dsh-cost-meter/config.json。",
6
6
  "license": "MIT",
@@ -33,9 +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": "node scripts/pack-release.js",
38
+ "pack:release": "npm pack",
39
39
  "test": "node --test tests/*.test.js"
40
40
  },
41
41
  "dsh": {