sidekick-shared 0.25.0 → 0.26.0

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +49 -3
  3. package/dist/accountChanges.js +22 -3
  4. package/dist/accountRegistry.d.ts +22 -1
  5. package/dist/accountRegistry.js +37 -14
  6. package/dist/accountStatus.d.ts +9 -1
  7. package/dist/accountStatus.js +3 -3
  8. package/dist/accounts.d.ts +3 -1
  9. package/dist/accounts.js +84 -64
  10. package/dist/aggregation/EventAggregator.d.ts +15 -2
  11. package/dist/aggregation/EventAggregator.js +54 -9
  12. package/dist/aggregation/costProvenance.d.ts +14 -0
  13. package/dist/aggregation/costProvenance.js +35 -0
  14. package/dist/aggregation/snapshot.d.ts +13 -2
  15. package/dist/aggregation/snapshot.js +57 -6
  16. package/dist/aggregation/types.d.ts +30 -1
  17. package/dist/analytics/compactionLedger.js +15 -5
  18. package/dist/analytics/qualityScore.js +2 -1
  19. package/dist/browser.d.ts +11 -1
  20. package/dist/browser.js +29 -1
  21. package/dist/codexProfiles.d.ts +3 -1
  22. package/dist/codexProfiles.js +4 -2
  23. package/dist/codexQuota.d.ts +2 -0
  24. package/dist/codexQuota.js +11 -66
  25. package/dist/failingTools.d.ts +37 -0
  26. package/dist/failingTools.js +70 -0
  27. package/dist/formatters/sessionDump.js +13 -7
  28. package/dist/formatting.d.ts +13 -0
  29. package/dist/formatting.js +35 -0
  30. package/dist/historicalStore.d.ts +47 -0
  31. package/dist/historicalStore.js +312 -0
  32. package/dist/index.d.ts +46 -12
  33. package/dist/index.js +112 -24
  34. package/dist/node.d.ts +19 -0
  35. package/dist/node.js +47 -1
  36. package/dist/observedContextWindows.d.ts +7 -0
  37. package/dist/observedContextWindows.js +17 -2
  38. package/dist/observedSessionCollector.d.ts +3 -8
  39. package/dist/observedSessionCollector.js +10 -27
  40. package/dist/parsers/sessionActivityDetector.d.ts +24 -2
  41. package/dist/parsers/sessionActivityDetector.js +95 -17
  42. package/dist/parsers/sessionPathResolver.d.ts +11 -0
  43. package/dist/parsers/sessionPathResolver.js +13 -10
  44. package/dist/peakHours.d.ts +34 -3
  45. package/dist/peakHours.js +92 -7
  46. package/dist/pricingCatalog.d.ts +7 -0
  47. package/dist/pricingCatalog.js +8 -5
  48. package/dist/providers/claudeCode.js +11 -96
  49. package/dist/providers/codex.d.ts +2 -0
  50. package/dist/providers/codex.js +94 -259
  51. package/dist/providers/detect.d.ts +2 -0
  52. package/dist/providers/detect.js +29 -24
  53. package/dist/providers/factory.d.ts +6 -0
  54. package/dist/providers/openCode.d.ts +2 -0
  55. package/dist/providers/openCode.js +105 -121
  56. package/dist/providers/rolloutWalker.d.ts +53 -0
  57. package/dist/providers/rolloutWalker.js +209 -0
  58. package/dist/providers/types.d.ts +30 -0
  59. package/dist/quota.d.ts +30 -3
  60. package/dist/quota.js +35 -3
  61. package/dist/quotaHistory.d.ts +1 -1
  62. package/dist/quotaHistory.js +36 -35
  63. package/dist/quotaResolver.d.ts +81 -0
  64. package/dist/quotaResolver.js +213 -0
  65. package/dist/quotaSnapshots.d.ts +15 -2
  66. package/dist/quotaSnapshots.js +25 -3
  67. package/dist/quotaThresholds.d.ts +60 -0
  68. package/dist/quotaThresholds.js +90 -0
  69. package/dist/report/htmlReportGenerator.js +10 -4
  70. package/dist/report/sessionReportInputs.d.ts +23 -0
  71. package/dist/report/sessionReportInputs.js +34 -0
  72. package/dist/schemas/index.d.ts +2 -0
  73. package/dist/schemas/index.js +10 -1
  74. package/dist/schemas/quota.d.ts +139 -0
  75. package/dist/schemas/quota.js +17 -2
  76. package/dist/schemas/quotaHistory.d.ts +1 -0
  77. package/dist/schemas/quotaHistory.js +1 -1
  78. package/dist/schemas/sessionEvent.d.ts +6 -0
  79. package/dist/schemas/sessionEvent.js +1 -0
  80. package/dist/schemas/stateFile.d.ts +198 -0
  81. package/dist/schemas/stateFile.js +77 -0
  82. package/dist/schemas/transcript.js +2 -0
  83. package/dist/sessionFingerprint.d.ts +25 -0
  84. package/dist/sessionFingerprint.js +46 -0
  85. package/dist/sessionPreviews.d.ts +7 -1
  86. package/dist/sessionPreviews.js +15 -3
  87. package/dist/sessionStats.d.ts +51 -0
  88. package/dist/sessionStats.js +166 -0
  89. package/dist/stateFile.d.ts +113 -0
  90. package/dist/stateFile.js +160 -0
  91. package/dist/statusline/claudeStatuslinePayload.d.ts +89 -0
  92. package/dist/statusline/claudeStatuslinePayload.js +152 -0
  93. package/dist/statusline/formatter.d.ts +13 -0
  94. package/dist/statusline/formatter.js +48 -6
  95. package/dist/statusline/index.d.ts +10 -2
  96. package/dist/statusline/index.js +19 -1
  97. package/dist/tokenSummary.d.ts +62 -0
  98. package/dist/tokenSummary.js +72 -0
  99. package/dist/transcript.d.ts +5 -0
  100. package/dist/transcript.js +2 -0
  101. package/dist/types/observedSessionV1.d.ts +2 -0
  102. package/dist/types/observedSessionV1.js +75 -18
  103. package/dist/types/sessionEvent.d.ts +2 -0
  104. package/dist/usage/billingBlocks.d.ts +85 -0
  105. package/dist/usage/billingBlocks.js +153 -0
  106. package/dist/usage/importSessionHistory.d.ts +53 -0
  107. package/dist/usage/importSessionHistory.js +88 -0
  108. package/dist/usage/usageEvents.d.ts +92 -0
  109. package/dist/usage/usageEvents.js +322 -0
  110. package/dist/usage/usageReports.d.ts +76 -0
  111. package/dist/usage/usageReports.js +208 -0
  112. package/dist/watchers/factory.d.ts +6 -0
  113. package/dist/watchers/factory.js +30 -25
  114. package/dist/zaiQuotaApi.d.ts +2 -0
  115. package/dist/zaiQuotaApi.js +2 -0
  116. package/package.json +1 -1
@@ -14,7 +14,7 @@ import type { SerializedFrequencyState } from './FrequencyTracker';
14
14
  import type { SerializedHeatmapState } from './HeatmapTracker';
15
15
  import type { SerializedPatternState } from './PatternExtractor';
16
16
  /** Schema version for serialized snapshots. */
17
- export declare const SNAPSHOT_SCHEMA_VERSION = 4;
17
+ export declare const SNAPSHOT_SCHEMA_VERSION = 5;
18
18
  /**
19
19
  * JSON-serializable snapshot of EventAggregator state.
20
20
  * Used by session snapshot sidecar files for fast resume.
@@ -28,7 +28,14 @@ export interface SerializedAggregatorState {
28
28
  cacheRead: number;
29
29
  reasoning: number;
30
30
  total: number;
31
+ /** @deprecated Mirrors `costUsd`; kept so older readers of the field keep working. */
31
32
  reportedCost: number;
33
+ costUsd: number;
34
+ reportedCostUsd: number;
35
+ estimatedCostUsd: number;
36
+ reportedCalls: number;
37
+ estimatedCalls: number;
38
+ unpricedCalls: number;
32
39
  };
33
40
  modelUsage: Array<[
34
41
  string,
@@ -100,7 +107,13 @@ export declare class EventAggregator {
100
107
  private cacheReadTokens;
101
108
  private reasoningTokens;
102
109
  private totalTokens;
103
- private reportedCost;
110
+ /** Running cost in USD (reported + estimated). */
111
+ private costUsd;
112
+ private reportedCostUsd;
113
+ private estimatedCostUsd;
114
+ private reportedCalls;
115
+ private estimatedCalls;
116
+ private unpricedCalls;
104
117
  private modelUsage;
105
118
  private currentContextSize;
106
119
  private previousContextSize;
@@ -16,6 +16,7 @@ const planExtractor_1 = require("../parsers/planExtractor");
16
16
  const eventBridge_1 = require("../watchers/eventBridge");
17
17
  const toolSummary_1 = require("../formatters/toolSummary");
18
18
  const noiseClassifier_1 = require("../formatters/noiseClassifier");
19
+ const costProvenance_1 = require("./costProvenance");
19
20
  const FrequencyTracker_1 = require("./FrequencyTracker");
20
21
  const HeatmapTracker_1 = require("./HeatmapTracker");
21
22
  const PatternExtractor_1 = require("./PatternExtractor");
@@ -30,7 +31,7 @@ const DEFAULT_BURN_WINDOW_MS = 5 * 60_000;
30
31
  const DEFAULT_BURN_SAMPLE_MS = 10_000;
31
32
  const COMPACTION_DROP_THRESHOLD = 0.8; // >20% drop
32
33
  /** Schema version for serialized snapshots. */
33
- exports.SNAPSHOT_SCHEMA_VERSION = 4;
34
+ exports.SNAPSHOT_SCHEMA_VERSION = 5;
34
35
  // ── Goal gate detection regex ──
35
36
  const GOAL_GATE_REGEX = /\b(CRITICAL|MUST|blocker|required|must.?complete|goal.?gate|essential|do.?not.?skip|blocking)\b/i;
36
37
  // ── Todo dependency parsing ──
@@ -78,7 +79,13 @@ class EventAggregator {
78
79
  cacheReadTokens = 0;
79
80
  reasoningTokens = 0;
80
81
  totalTokens = 0;
81
- reportedCost = 0;
82
+ /** Running cost in USD (reported + estimated). */
83
+ costUsd = 0;
84
+ reportedCostUsd = 0;
85
+ estimatedCostUsd = 0;
86
+ reportedCalls = 0;
87
+ estimatedCalls = 0;
88
+ unpricedCalls = 0;
82
89
  // Per-model usage
83
90
  modelUsage = new Map();
84
91
  // Context size tracking
@@ -262,8 +269,10 @@ class EventAggregator {
262
269
  this.attributePlanUsage(normalized.totalTokens, cost);
263
270
  }
264
271
  else if (typeof event.cost === 'number' && Number.isFinite(event.cost) && event.cost >= 0) {
265
- // Compatibility for cost-only follow events.
266
- this.reportedCost += event.cost;
272
+ // Compatibility for cost-only follow events (provider-reported by definition).
273
+ this.costUsd += event.cost;
274
+ this.reportedCostUsd += event.cost;
275
+ this.reportedCalls += 1;
267
276
  }
268
277
  // 4. Tool tracking from FollowEvent
269
278
  if (event.type === 'tool_use' && event.toolName) {
@@ -309,7 +318,16 @@ class EventAggregator {
309
318
  cacheReadTokens: this.cacheReadTokens,
310
319
  reasoningTokens: this.reasoningTokens,
311
320
  totalTokens: this.totalTokens,
312
- reportedCost: this.reportedCost,
321
+ costUsd: this.costUsd,
322
+ reportedCostUsd: this.reportedCostUsd,
323
+ estimatedCostUsd: this.estimatedCostUsd,
324
+ unpricedCalls: this.unpricedCalls,
325
+ costProvenance: (0, costProvenance_1.classifyCostProvenance)({
326
+ reportedCalls: this.reportedCalls,
327
+ estimatedCalls: this.estimatedCalls,
328
+ unpricedCalls: this.unpricedCalls,
329
+ }),
330
+ reportedCost: this.costUsd,
313
331
  };
314
332
  }
315
333
  getModelStats() {
@@ -427,7 +445,12 @@ class EventAggregator {
427
445
  this.cacheReadTokens = 0;
428
446
  this.reasoningTokens = 0;
429
447
  this.totalTokens = 0;
430
- this.reportedCost = 0;
448
+ this.costUsd = 0;
449
+ this.reportedCostUsd = 0;
450
+ this.estimatedCostUsd = 0;
451
+ this.reportedCalls = 0;
452
+ this.estimatedCalls = 0;
453
+ this.unpricedCalls = 0;
431
454
  this.modelUsage.clear();
432
455
  this.currentContextSize = 0;
433
456
  this.previousContextSize = 0;
@@ -503,7 +526,13 @@ class EventAggregator {
503
526
  cacheRead: this.cacheReadTokens,
504
527
  reasoning: this.reasoningTokens,
505
528
  total: this.totalTokens,
506
- reportedCost: this.reportedCost,
529
+ reportedCost: this.costUsd,
530
+ costUsd: this.costUsd,
531
+ reportedCostUsd: this.reportedCostUsd,
532
+ estimatedCostUsd: this.estimatedCostUsd,
533
+ reportedCalls: this.reportedCalls,
534
+ estimatedCalls: this.estimatedCalls,
535
+ unpricedCalls: this.unpricedCalls,
507
536
  },
508
537
  modelUsage: Array.from(this.modelUsage.entries()),
509
538
  contextSize: this.currentContextSize,
@@ -550,7 +579,12 @@ class EventAggregator {
550
579
  this.cacheReadTokens = state.tokens.cacheRead;
551
580
  this.reasoningTokens = state.tokens.reasoning;
552
581
  this.totalTokens = state.tokens.total;
553
- this.reportedCost = state.tokens.reportedCost;
582
+ this.costUsd = state.tokens.costUsd;
583
+ this.reportedCostUsd = state.tokens.reportedCostUsd;
584
+ this.estimatedCostUsd = state.tokens.estimatedCostUsd;
585
+ this.reportedCalls = state.tokens.reportedCalls;
586
+ this.estimatedCalls = state.tokens.estimatedCalls;
587
+ this.unpricedCalls = state.tokens.unpricedCalls;
554
588
  this.modelUsage = new Map(state.modelUsage);
555
589
  this.currentContextSize = state.contextSize;
556
590
  this.previousContextSize = state.previousContextSize;
@@ -619,7 +653,18 @@ class EventAggregator {
619
653
  this.cacheReadTokens += cacheRead;
620
654
  this.reasoningTokens += reasoningTok;
621
655
  this.totalTokens += usage.totalTokens;
622
- this.reportedCost += cost;
656
+ this.costUsd += cost;
657
+ if (pricedUsage.source === 'provider-reported') {
658
+ this.reportedCostUsd += cost;
659
+ this.reportedCalls += 1;
660
+ }
661
+ else if (pricedUsage.source === 'unpriced') {
662
+ this.unpricedCalls += 1;
663
+ }
664
+ else {
665
+ this.estimatedCostUsd += cost;
666
+ this.estimatedCalls += 1;
667
+ }
623
668
  // Burn rate sample accumulation
624
669
  this.tokensSinceLastSample += usage.totalTokens;
625
670
  // Context size computation
@@ -0,0 +1,14 @@
1
+ import type { AggregatedCostProvenance, AggregatedTokens } from './types';
2
+ /** The fields `describeCostProvenance` reads; any `AggregatedTokens` qualifies. */
3
+ export type AggregatedCostProvenanceInput = Pick<AggregatedTokens, 'costProvenance' | 'unpricedCalls'>;
4
+ /**
5
+ * Human-readable qualifier for an aggregate cost, for report footers and
6
+ * table cells. Empty when there is nothing to qualify.
7
+ */
8
+ export declare function describeCostProvenance(tokens: AggregatedCostProvenanceInput): string;
9
+ /** Classify a cost breakdown by how many events fell into each bucket. */
10
+ export declare function classifyCostProvenance(counts: {
11
+ reportedCalls: number;
12
+ estimatedCalls: number;
13
+ unpricedCalls: number;
14
+ }): AggregatedCostProvenance;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeCostProvenance = describeCostProvenance;
4
+ exports.classifyCostProvenance = classifyCostProvenance;
5
+ const PROVENANCE_LABELS = {
6
+ reported: 'provider-reported',
7
+ estimated: 'estimated from catalog pricing',
8
+ mixed: 'partly provider-reported, partly estimated',
9
+ unpriced: 'unpriced',
10
+ none: '',
11
+ };
12
+ /**
13
+ * Human-readable qualifier for an aggregate cost, for report footers and
14
+ * table cells. Empty when there is nothing to qualify.
15
+ */
16
+ function describeCostProvenance(tokens) {
17
+ const base = PROVENANCE_LABELS[tokens.costProvenance] ?? '';
18
+ const unpriced = tokens.unpricedCalls > 0
19
+ ? `${tokens.unpricedCalls} unpriced call${tokens.unpricedCalls === 1 ? '' : 's'}`
20
+ : '';
21
+ if (base && unpriced)
22
+ return `${base} · ${unpriced}`;
23
+ return base || unpriced;
24
+ }
25
+ /** Classify a cost breakdown by how many events fell into each bucket. */
26
+ function classifyCostProvenance(counts) {
27
+ const { reportedCalls, estimatedCalls, unpricedCalls } = counts;
28
+ if (reportedCalls > 0 && estimatedCalls > 0)
29
+ return 'mixed';
30
+ if (reportedCalls > 0)
31
+ return 'reported';
32
+ if (estimatedCalls > 0)
33
+ return 'estimated';
34
+ return unpricedCalls > 0 ? 'unpriced' : 'none';
35
+ }
@@ -8,6 +8,8 @@
8
8
  * @module aggregation/snapshot
9
9
  */
10
10
  import { type SerializedAggregatorState } from './EventAggregator';
11
+ /** Newest snapshots kept on disk; older ones are pruned opportunistically. */
12
+ export declare const MAX_SNAPSHOT_FILES = 200;
11
13
  /** Snapshot sidecar file content. */
12
14
  export interface SessionSnapshot {
13
15
  /** Schema version for forward-compatibility checks. */
@@ -32,10 +34,19 @@ export declare function getSnapshotPath(sessionId: string): string;
32
34
  /**
33
35
  * Saves a session snapshot to disk.
34
36
  *
35
- * Creates the snapshots directory if it doesn't exist.
36
- * Writes atomically via rename to avoid partial reads.
37
+ * Creates the snapshots directory if it doesn't exist. Uses the shared
38
+ * fsynced atomic writer with a per-process temp name, so the CLI dashboard and
39
+ * the extension host snapshotting the same session cannot collide on a shared
40
+ * `.tmp` file. Every {@link PRUNE_EVERY_N_SAVES} saves the directory is
41
+ * trimmed to the newest {@link MAX_SNAPSHOT_FILES}; snapshots are caches, so
42
+ * dropping an old one only costs a replay.
37
43
  */
38
44
  export declare function saveSnapshot(snapshot: SessionSnapshot): void;
45
+ /**
46
+ * Deletes all but the newest `keep` snapshot files (by modification time).
47
+ * Returns how many files were removed. Best-effort; never throws.
48
+ */
49
+ export declare function pruneSnapshots(keep?: number): number;
39
50
  /**
40
51
  * Loads a session snapshot from disk if it exists and is valid.
41
52
  *
@@ -42,17 +42,25 @@ var __importStar = (this && this.__importStar) || (function () {
42
42
  };
43
43
  })();
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.MAX_SNAPSHOT_FILES = void 0;
45
46
  exports.getSnapshotPath = getSnapshotPath;
46
47
  exports.saveSnapshot = saveSnapshot;
48
+ exports.pruneSnapshots = pruneSnapshots;
47
49
  exports.loadSnapshot = loadSnapshot;
48
50
  exports.deleteSnapshot = deleteSnapshot;
49
51
  exports.isSnapshotValid = isSnapshotValid;
50
52
  const fs = __importStar(require("fs"));
51
53
  const path = __importStar(require("path"));
52
54
  const paths_1 = require("../paths");
55
+ const atomic_1 = require("../writers/atomic");
53
56
  const EventAggregator_1 = require("./EventAggregator");
54
57
  /** Current schema version — bump when format changes. */
55
58
  const SNAPSHOT_VERSION = EventAggregator_1.SNAPSHOT_SCHEMA_VERSION;
59
+ /** Newest snapshots kept on disk; older ones are pruned opportunistically. */
60
+ exports.MAX_SNAPSHOT_FILES = 200;
61
+ /** Prune runs once per this many saves, so steady-state writes stay one file each. */
62
+ const PRUNE_EVERY_N_SAVES = 25;
63
+ let savesSincePrune = 0;
56
64
  /** Returns the directory for snapshot files. */
57
65
  function getSnapshotsDir() {
58
66
  return path.join((0, paths_1.getConfigDir)(), 'snapshots');
@@ -66,8 +74,12 @@ function getSnapshotPath(sessionId) {
66
74
  /**
67
75
  * Saves a session snapshot to disk.
68
76
  *
69
- * Creates the snapshots directory if it doesn't exist.
70
- * Writes atomically via rename to avoid partial reads.
77
+ * Creates the snapshots directory if it doesn't exist. Uses the shared
78
+ * fsynced atomic writer with a per-process temp name, so the CLI dashboard and
79
+ * the extension host snapshotting the same session cannot collide on a shared
80
+ * `.tmp` file. Every {@link PRUNE_EVERY_N_SAVES} saves the directory is
81
+ * trimmed to the newest {@link MAX_SNAPSHOT_FILES}; snapshots are caches, so
82
+ * dropping an old one only costs a replay.
71
83
  */
72
84
  function saveSnapshot(snapshot) {
73
85
  try {
@@ -75,15 +87,54 @@ function saveSnapshot(snapshot) {
75
87
  if (!fs.existsSync(dir)) {
76
88
  fs.mkdirSync(dir, { recursive: true });
77
89
  }
78
- const filePath = getSnapshotPath(snapshot.sessionId);
79
- const tmpPath = filePath + '.tmp';
80
- fs.writeFileSync(tmpPath, JSON.stringify(snapshot), 'utf-8');
81
- fs.renameSync(tmpPath, filePath);
90
+ (0, atomic_1.atomicWriteJsonSync)(getSnapshotPath(snapshot.sessionId), snapshot);
91
+ savesSincePrune += 1;
92
+ if (savesSincePrune >= PRUNE_EVERY_N_SAVES) {
93
+ savesSincePrune = 0;
94
+ pruneSnapshots(exports.MAX_SNAPSHOT_FILES);
95
+ }
82
96
  }
83
97
  catch {
84
98
  // Non-critical — snapshot failure should never break session monitoring
85
99
  }
86
100
  }
101
+ /**
102
+ * Deletes all but the newest `keep` snapshot files (by modification time).
103
+ * Returns how many files were removed. Best-effort; never throws.
104
+ */
105
+ function pruneSnapshots(keep = exports.MAX_SNAPSHOT_FILES) {
106
+ try {
107
+ const dir = getSnapshotsDir();
108
+ const entries = fs
109
+ .readdirSync(dir)
110
+ .filter((name) => name.endsWith('.json'))
111
+ .map((name) => {
112
+ const filePath = path.join(dir, name);
113
+ try {
114
+ return { filePath, mtimeMs: fs.statSync(filePath).mtimeMs };
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ })
120
+ .filter((entry) => entry !== null)
121
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
122
+ let removed = 0;
123
+ for (const entry of entries.slice(Math.max(0, keep))) {
124
+ try {
125
+ fs.unlinkSync(entry.filePath);
126
+ removed += 1;
127
+ }
128
+ catch {
129
+ // Already gone or locked; skip.
130
+ }
131
+ }
132
+ return removed;
133
+ }
134
+ catch {
135
+ return 0;
136
+ }
137
+ }
87
138
  /**
88
139
  * Loads a session snapshot from disk if it exists and is valid.
89
140
  *
@@ -50,7 +50,17 @@ export interface EventAggregatorOptions {
50
50
  /** Read a plan file from disk (fallback when Edit tool is used instead of Write). */
51
51
  readPlanFile?: (path: string) => string | null;
52
52
  }
53
- /** Token accumulation totals (aggregation-specific, includes reportedCost). */
53
+ /**
54
+ * How an aggregate cost figure was produced.
55
+ *
56
+ * - `reported` — every priced event carried a provider-reported cost.
57
+ * - `estimated` — every priced event was priced from the model catalog.
58
+ * - `mixed` — some events reported, some estimated.
59
+ * - `unpriced` — no event could be priced at all.
60
+ * - `none` — no usage events yet.
61
+ */
62
+ export type AggregatedCostProvenance = 'reported' | 'estimated' | 'mixed' | 'unpriced' | 'none';
63
+ /** Token accumulation totals (aggregation-specific, includes cost). */
54
64
  export interface AggregatedTokens {
55
65
  inputTokens: number;
56
66
  outputTokens: number;
@@ -59,6 +69,25 @@ export interface AggregatedTokens {
59
69
  reasoningTokens: number;
60
70
  /** Overall total after applying provider reasoning-in-output semantics. */
61
71
  totalTokens: number;
72
+ /**
73
+ * Cost in USD for the priced portion of the session. Read `costProvenance`
74
+ * before labelling it: only `'reported'` came from the provider; anything
75
+ * else includes Sidekick's own catalog estimate.
76
+ */
77
+ costUsd: number;
78
+ /** Portion of `costUsd` the provider reported itself. */
79
+ reportedCostUsd: number;
80
+ /** Portion of `costUsd` Sidekick estimated from catalog pricing. */
81
+ estimatedCostUsd: number;
82
+ /** Usage events that had neither a reported cost nor a catalog price. */
83
+ unpricedCalls: number;
84
+ /** Classification of `costUsd`. */
85
+ costProvenance: AggregatedCostProvenance;
86
+ /**
87
+ * @deprecated Same value as `costUsd`. Despite the name it has always mixed
88
+ * catalog estimates with provider-reported figures; use `costUsd` together
89
+ * with `costProvenance`.
90
+ */
62
91
  reportedCost: number;
63
92
  }
64
93
  /** Per-model usage breakdown. */
@@ -2,18 +2,28 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.calculateCompactionLedger = calculateCompactionLedger;
4
4
  exports.formatCompactionLedger = formatCompactionLedger;
5
- const modelInfo_1 = require("../modelInfo");
5
+ const usageNormalization_1 = require("../usageNormalization");
6
6
  function calculateCompactionLedger(events, model) {
7
7
  const tokensEvicted = events.reduce((sum, event) => sum + Math.max(0, event.tokensReclaimed), 0);
8
8
  const reportedCount = events.filter((event) => event.source === 'reported').length;
9
9
  const heuristicCount = events.length - reportedCount;
10
- const pricing = model ? (0, modelInfo_1.getModelPricing)(model) : null;
10
+ // Re-establishing evicted context means sending it again as uncached input.
11
+ // Price it through the same normalized path every other cost figure uses so
12
+ // the ledger can never disagree with the session total on the same tokens.
13
+ const reestablishment = model
14
+ ? (0, usageNormalization_1.calculateNormalizedUsageCost)({
15
+ usage: (0, usageNormalization_1.normalizeProviderUsage)({
16
+ semantics: 'sidekick',
17
+ inputTokens: tokensEvicted,
18
+ reasoningIncludedInOutput: true,
19
+ }),
20
+ modelId: model,
21
+ })
22
+ : null;
11
23
  return {
12
24
  count: events.length,
13
25
  tokensEvicted,
14
- reestablishmentCostUsd: pricing
15
- ? (0, modelInfo_1.calculateCostWithPricing)({ inputTokens: tokensEvicted, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 }, pricing)
16
- : null,
26
+ reestablishmentCostUsd: reestablishment?.costUsd ?? null,
17
27
  source: reportedCount > 0 && heuristicCount > 0
18
28
  ? 'mixed'
19
29
  : reportedCount > 0
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scoreSessionQuality = scoreSessionQuality;
4
4
  exports.calculateQualityTrend = calculateQualityTrend;
5
+ const tokenSummary_1 = require("../tokenSummary");
5
6
  function awarded(maximum, ratio) {
6
7
  return Math.round(maximum * Math.max(0, Math.min(1, ratio)) * 10) / 10;
7
8
  }
@@ -10,7 +11,7 @@ function scoreSessionQuality(metrics) {
10
11
  const latencyRatio = latencyMs > 0 ? 1 - Math.min(1, latencyMs / 15_000) : 1;
11
12
  const completedCalls = metrics.toolStats.reduce((sum, tool) => sum + tool.completedCount, 0);
12
13
  const errorRate = completedCalls > 0 ? metrics.errorRollup.totalFailures / completedCalls : 0;
13
- const contextTokens = Math.max(1, metrics.tokens.inputTokens + metrics.tokens.cacheReadTokens + metrics.tokens.cacheWriteTokens);
14
+ const contextTokens = Math.max(1, (0, tokenSummary_1.summarizeTokens)(metrics.tokens).context);
14
15
  const evicted = metrics.compactionEvents.reduce((sum, event) => sum + event.tokensReclaimed, 0);
15
16
  const contextRatio = 1 - Math.min(1, evicted / contextTokens);
16
17
  const start = metrics.sessionStartTime ? Date.parse(metrics.sessionStartTime) : NaN;
package/dist/browser.d.ts CHANGED
@@ -11,11 +11,13 @@ export { getModelContextWindowSize, resolveModelContextWindow, DEFAULT_CONTEXT_W
11
11
  export type { ModelCatalogMatch, ModelContextWindowProvenance, ModelContextWindowSource, ResolvedModelContextWindow, } from './modelContext';
12
12
  export { normalizeProviderUsage, extractNormalizedUsage, calculateNormalizedUsageCost, } from './usageNormalization';
13
13
  export type { UsageProvider, UsageSemantics, UsageNormalizationProvenance, ProviderUsageInput, AnthropicUsageInput, OpenAIUsageInput, SidekickUsageInput, NormalizedUsage, PricingProvenance, NormalizedUsageCostInput, NormalizedUsageCost, } from './usageNormalization';
14
+ export { summarizeTokens, sumTokenTotals, TOKEN_CONTEXT_LABEL, TOKEN_TOTAL_LABEL, } from './tokenSummary';
15
+ export type { TokenSummary, TokenTotalsLike } from './tokenSummary';
14
16
  export { estimateTextTokens, estimateSerializedTokens } from './tokenEstimation';
15
17
  export type { ExactTokenCounterContext, ExactTokenCounter, TokenEstimationMethod, TokenEstimationConfidence, TokenEstimationOptions, TokenEstimate, } from './tokenEstimation';
16
18
  export { projectSessionTranscript } from './transcript';
17
19
  export type { TranscriptFidelity, TranscriptSourceProvenance, CanonicalTranscriptBlock, CanonicalTranscriptMessage, CanonicalToolCall, CanonicalTranscriptUsageTotals, CanonicalSessionTranscript, ProjectSessionTranscriptOptions, } from './transcript';
18
- export { formatDurationMs, formatTokenCount } from './formatting';
20
+ export { addLocalDays, formatDurationMs, formatLocalDateKey, formatTokenCount, parseLocalDateKey, } from './formatting';
19
21
  export type { FormatDurationMsOptions, FormatTokenCountOptions } from './formatting';
20
22
  export { parseMcpToolName } from './parsers/mcpToolName';
21
23
  export type { McpToolNameParts } from './parsers/mcpToolName';
@@ -29,3 +31,11 @@ export { parseModelId, getModelPricing, resolveModelPricing, getModelInfo, calcu
29
31
  export type { ModelPricing, CostTokenUsage, CostSource, CostProvenanceInput, CostWithProvenance, ModelProvider, ParsedModelId, ModelInfo, ModelDisplayInfo, ModelPricingMatch, ModelPricingProvenance, ModelPricingSource, ResolvedModelPricing, } from './modelInfo';
30
32
  export { exportResolvedModelCatalog, importResolvedModelCatalog, registerModelAlias, resolveModelAlias, RESOLVED_MODEL_CATALOG_SCHEMA_VERSION, } from './modelCatalog';
31
33
  export type { ImportResolvedModelCatalogResult, ResolvedModelCatalogEntry, ResolvedModelCatalogSnapshot, } from './modelCatalog';
34
+ export { BILLING_BLOCK_DURATION_MS, computeBillingBlocks, findActiveBillingBlock, } from './usage/billingBlocks';
35
+ export type { BillingBlock, BillingBlockInput, BillingBlockModelUsage, BillingBlockTokens, ComputeBillingBlocksOptions, } from './usage/billingBlocks';
36
+ export { bucketUsage, summarizeUsageRows, usageBucketKey, weekKey } from './usage/usageReports';
37
+ export type { BucketUsageOptions, UsageBucketRow, UsageGranularity, UsageGroupDimension, UsageTotals, } from './usage/usageReports';
38
+ export { applySessionSummary, isFileImported, markFileImported, removeSessionSummary, sessionSummaryFromStats, } from './historicalStore';
39
+ export type { ApplySessionSummaryOptions, SessionSummaryFromStatsOptions } from './historicalStore';
40
+ export { FAILING_TOOL_TREND_ARROWS, failingToolTrend, mergeFailingToolWindows, } from './failingTools';
41
+ export type { FailingToolTrend, FailingToolTrendRow, FailingToolWindow } from './failingTools';
package/dist/browser.js CHANGED
@@ -9,7 +9,8 @@
9
9
  * For pricing hydration (LiteLLM catalog refresh), use `sidekick-shared/node`.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.RESOLVED_MODEL_CATALOG_SCHEMA_VERSION = exports.resolveModelAlias = exports.registerModelAlias = exports.importResolvedModelCatalog = exports.exportResolvedModelCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.resolveModelPricing = exports.getModelPricing = exports.parseModelId = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.SESSION_PROVIDER_IDS = exports.RUNTIME_QUOTA_PROVIDER_IDS = exports.QUOTA_PROVIDER_IDS = exports.MODEL_PROVIDER_IDS = exports.ACCOUNT_PROVIDER_IDS = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.parseMcpToolName = exports.formatTokenCount = exports.formatDurationMs = exports.projectSessionTranscript = exports.estimateSerializedTokens = exports.estimateTextTokens = exports.calculateNormalizedUsageCost = exports.extractNormalizedUsage = exports.normalizeProviderUsage = exports.DEFAULT_CONTEXT_WINDOW = exports.resolveModelContextWindow = exports.getModelContextWindowSize = void 0;
12
+ exports.RESOLVED_MODEL_CATALOG_SCHEMA_VERSION = exports.resolveModelAlias = exports.registerModelAlias = exports.importResolvedModelCatalog = exports.exportResolvedModelCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.resolveModelPricing = exports.getModelPricing = exports.parseModelId = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.SESSION_PROVIDER_IDS = exports.RUNTIME_QUOTA_PROVIDER_IDS = exports.QUOTA_PROVIDER_IDS = exports.MODEL_PROVIDER_IDS = exports.ACCOUNT_PROVIDER_IDS = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.parseMcpToolName = exports.parseLocalDateKey = exports.formatTokenCount = exports.formatLocalDateKey = exports.formatDurationMs = exports.addLocalDays = exports.projectSessionTranscript = exports.estimateSerializedTokens = exports.estimateTextTokens = exports.TOKEN_TOTAL_LABEL = exports.TOKEN_CONTEXT_LABEL = exports.sumTokenTotals = exports.summarizeTokens = exports.calculateNormalizedUsageCost = exports.extractNormalizedUsage = exports.normalizeProviderUsage = exports.DEFAULT_CONTEXT_WINDOW = exports.resolveModelContextWindow = exports.getModelContextWindowSize = void 0;
13
+ exports.mergeFailingToolWindows = exports.failingToolTrend = exports.FAILING_TOOL_TREND_ARROWS = exports.sessionSummaryFromStats = exports.removeSessionSummary = exports.markFileImported = exports.isFileImported = exports.applySessionSummary = exports.weekKey = exports.usageBucketKey = exports.summarizeUsageRows = exports.bucketUsage = exports.findActiveBillingBlock = exports.computeBillingBlocks = exports.BILLING_BLOCK_DURATION_MS = void 0;
13
14
  var modelContext_1 = require("./modelContext");
14
15
  Object.defineProperty(exports, "getModelContextWindowSize", { enumerable: true, get: function () { return modelContext_1.getModelContextWindowSize; } });
15
16
  Object.defineProperty(exports, "resolveModelContextWindow", { enumerable: true, get: function () { return modelContext_1.resolveModelContextWindow; } });
@@ -18,14 +19,22 @@ var usageNormalization_1 = require("./usageNormalization");
18
19
  Object.defineProperty(exports, "normalizeProviderUsage", { enumerable: true, get: function () { return usageNormalization_1.normalizeProviderUsage; } });
19
20
  Object.defineProperty(exports, "extractNormalizedUsage", { enumerable: true, get: function () { return usageNormalization_1.extractNormalizedUsage; } });
20
21
  Object.defineProperty(exports, "calculateNormalizedUsageCost", { enumerable: true, get: function () { return usageNormalization_1.calculateNormalizedUsageCost; } });
22
+ var tokenSummary_1 = require("./tokenSummary");
23
+ Object.defineProperty(exports, "summarizeTokens", { enumerable: true, get: function () { return tokenSummary_1.summarizeTokens; } });
24
+ Object.defineProperty(exports, "sumTokenTotals", { enumerable: true, get: function () { return tokenSummary_1.sumTokenTotals; } });
25
+ Object.defineProperty(exports, "TOKEN_CONTEXT_LABEL", { enumerable: true, get: function () { return tokenSummary_1.TOKEN_CONTEXT_LABEL; } });
26
+ Object.defineProperty(exports, "TOKEN_TOTAL_LABEL", { enumerable: true, get: function () { return tokenSummary_1.TOKEN_TOTAL_LABEL; } });
21
27
  var tokenEstimation_1 = require("./tokenEstimation");
22
28
  Object.defineProperty(exports, "estimateTextTokens", { enumerable: true, get: function () { return tokenEstimation_1.estimateTextTokens; } });
23
29
  Object.defineProperty(exports, "estimateSerializedTokens", { enumerable: true, get: function () { return tokenEstimation_1.estimateSerializedTokens; } });
24
30
  var transcript_1 = require("./transcript");
25
31
  Object.defineProperty(exports, "projectSessionTranscript", { enumerable: true, get: function () { return transcript_1.projectSessionTranscript; } });
26
32
  var formatting_1 = require("./formatting");
33
+ Object.defineProperty(exports, "addLocalDays", { enumerable: true, get: function () { return formatting_1.addLocalDays; } });
27
34
  Object.defineProperty(exports, "formatDurationMs", { enumerable: true, get: function () { return formatting_1.formatDurationMs; } });
35
+ Object.defineProperty(exports, "formatLocalDateKey", { enumerable: true, get: function () { return formatting_1.formatLocalDateKey; } });
28
36
  Object.defineProperty(exports, "formatTokenCount", { enumerable: true, get: function () { return formatting_1.formatTokenCount; } });
37
+ Object.defineProperty(exports, "parseLocalDateKey", { enumerable: true, get: function () { return formatting_1.parseLocalDateKey; } });
29
38
  var mcpToolName_1 = require("./parsers/mcpToolName");
30
39
  Object.defineProperty(exports, "parseMcpToolName", { enumerable: true, get: function () { return mcpToolName_1.parseMcpToolName; } });
31
40
  var sessionContext_1 = require("./context/sessionContext");
@@ -64,3 +73,22 @@ Object.defineProperty(exports, "importResolvedModelCatalog", { enumerable: true,
64
73
  Object.defineProperty(exports, "registerModelAlias", { enumerable: true, get: function () { return modelCatalog_1.registerModelAlias; } });
65
74
  Object.defineProperty(exports, "resolveModelAlias", { enumerable: true, get: function () { return modelCatalog_1.resolveModelAlias; } });
66
75
  Object.defineProperty(exports, "RESOLVED_MODEL_CATALOG_SCHEMA_VERSION", { enumerable: true, get: function () { return modelCatalog_1.RESOLVED_MODEL_CATALOG_SCHEMA_VERSION; } });
76
+ var billingBlocks_1 = require("./usage/billingBlocks");
77
+ Object.defineProperty(exports, "BILLING_BLOCK_DURATION_MS", { enumerable: true, get: function () { return billingBlocks_1.BILLING_BLOCK_DURATION_MS; } });
78
+ Object.defineProperty(exports, "computeBillingBlocks", { enumerable: true, get: function () { return billingBlocks_1.computeBillingBlocks; } });
79
+ Object.defineProperty(exports, "findActiveBillingBlock", { enumerable: true, get: function () { return billingBlocks_1.findActiveBillingBlock; } });
80
+ var usageReports_1 = require("./usage/usageReports");
81
+ Object.defineProperty(exports, "bucketUsage", { enumerable: true, get: function () { return usageReports_1.bucketUsage; } });
82
+ Object.defineProperty(exports, "summarizeUsageRows", { enumerable: true, get: function () { return usageReports_1.summarizeUsageRows; } });
83
+ Object.defineProperty(exports, "usageBucketKey", { enumerable: true, get: function () { return usageReports_1.usageBucketKey; } });
84
+ Object.defineProperty(exports, "weekKey", { enumerable: true, get: function () { return usageReports_1.weekKey; } });
85
+ var historicalStore_1 = require("./historicalStore");
86
+ Object.defineProperty(exports, "applySessionSummary", { enumerable: true, get: function () { return historicalStore_1.applySessionSummary; } });
87
+ Object.defineProperty(exports, "isFileImported", { enumerable: true, get: function () { return historicalStore_1.isFileImported; } });
88
+ Object.defineProperty(exports, "markFileImported", { enumerable: true, get: function () { return historicalStore_1.markFileImported; } });
89
+ Object.defineProperty(exports, "removeSessionSummary", { enumerable: true, get: function () { return historicalStore_1.removeSessionSummary; } });
90
+ Object.defineProperty(exports, "sessionSummaryFromStats", { enumerable: true, get: function () { return historicalStore_1.sessionSummaryFromStats; } });
91
+ var failingTools_1 = require("./failingTools");
92
+ Object.defineProperty(exports, "FAILING_TOOL_TREND_ARROWS", { enumerable: true, get: function () { return failingTools_1.FAILING_TOOL_TREND_ARROWS; } });
93
+ Object.defineProperty(exports, "failingToolTrend", { enumerable: true, get: function () { return failingTools_1.failingToolTrend; } });
94
+ Object.defineProperty(exports, "mergeFailingToolWindows", { enumerable: true, get: function () { return failingTools_1.mergeFailingToolWindows; } });
@@ -31,7 +31,9 @@ export declare function resolveSidekickCodexHome(): string;
31
31
  * isn't the current active pointer, the pointer is re-pointed so registry-keyed data
32
32
  * (quota history, auto-switch) tracks reality too. Never creates or deletes profiles.
33
33
  */
34
- export declare function resolveActiveCodexAccount(): ResolvedActiveAccount;
34
+ export declare function resolveActiveCodexAccount(options?: {
35
+ selfHeal?: boolean;
36
+ }): ResolvedActiveAccount;
35
37
  export declare function getCodexExecutionEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
36
38
  export declare function prepareCodexAccount(label: string): CodexAccountManagerResult;
37
39
  /** {@link prepareCodexAccount} without blocking the event loop on CLI probes. */
@@ -380,13 +380,15 @@ function resolveSidekickCodexHome() {
380
380
  * isn't the current active pointer, the pointer is re-pointed so registry-keyed data
381
381
  * (quota history, auto-switch) tracks reality too. Never creates or deletes profiles.
382
382
  */
383
- function resolveActiveCodexAccount() {
383
+ function resolveActiveCodexAccount(options = {}) {
384
+ const selfHeal = options.selfHeal ?? true;
384
385
  const identity = readAuthIdentityFromRaw(readFileOrNull(path.join(resolveSidekickCodexHome(), 'auth.json')));
385
386
  if (identity && (identity.email || identity.workspaceId)) {
386
387
  const match = findProfileForIdentity(identity);
387
388
  if (match) {
388
389
  const active = (0, accountRegistry_1.getActiveSavedAccount)('codex');
389
- if (!active || active.id !== match.id) {
390
+ // See resolveActiveClaudeAccount: hot paths opt out of the repair write.
391
+ if (selfHeal && (!active || active.id !== match.id)) {
390
392
  // Self-heal is best-effort: a registry write failure (read-only/full
391
393
  // disk) must never break display, extension activation, or the quota
392
394
  // watcher's hot path. We still return the correct live identity below.
@@ -35,6 +35,8 @@ export interface CodexQuotaApiOptions {
35
35
  signal?: AbortSignal;
36
36
  timeoutMs?: number;
37
37
  }
38
+ /** Stamp a Codex quota sample with its runtime provider, projections, and account identity. */
39
+ export declare function enrichCodexQuota(state: QuotaState, account: SavedAccountProfile | null): ProviderQuotaState<'codex'>;
38
40
  export declare function quotaFromCodexRateLimits(rateLimits: CodexRateLimits | null | undefined, source?: 'api' | 'session' | 'cache', capturedAt?: string): QuotaState | null;
39
41
  export declare function readLatestCodexQuotaFromRollouts(sessionPaths: string[], options?: {
40
42
  source?: 'session' | 'cache';