sidekick-shared 0.27.2 → 0.27.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +57 -14
  3. package/dist/aggregation/EventAggregator.d.ts +12 -2
  4. package/dist/aggregation/EventAggregator.js +49 -19
  5. package/dist/browser.d.ts +2 -1
  6. package/dist/browser.js +9 -2
  7. package/dist/extractors/tokenUsage.js +1 -0
  8. package/dist/formatters/sessionDump.d.ts +10 -2
  9. package/dist/formatters/sessionDump.js +45 -23
  10. package/dist/index.d.ts +7 -3
  11. package/dist/index.js +31 -10
  12. package/dist/node.d.ts +6 -1
  13. package/dist/node.js +12 -2
  14. package/dist/parsers/codexParser.d.ts +22 -2
  15. package/dist/parsers/codexParser.js +64 -5
  16. package/dist/parsers/subagentScanner.d.ts +9 -0
  17. package/dist/parsers/subagentScanner.js +45 -12
  18. package/dist/parsers/subagentStatsCache.d.ts +17 -0
  19. package/dist/parsers/subagentStatsCache.js +82 -0
  20. package/dist/parsers/subagentTraceParser.js +19 -10
  21. package/dist/providers/claudeCode.js +8 -41
  22. package/dist/providers/codex.js +28 -14
  23. package/dist/providers/codexDatabase.d.ts +16 -1
  24. package/dist/providers/codexDatabase.js +49 -1
  25. package/dist/providers/openCode.js +30 -4
  26. package/dist/report/htmlReportGenerator.js +33 -11
  27. package/dist/report/sessionReportInputs.d.ts +3 -1
  28. package/dist/report/sessionReportInputs.js +2 -0
  29. package/dist/report/types.d.ts +5 -0
  30. package/dist/schemas/sessionEvent.d.ts +30 -6
  31. package/dist/schemas/sessionEvent.js +7 -1
  32. package/dist/schemas/stateFile.d.ts +2 -0
  33. package/dist/schemas/stateFile.js +1 -0
  34. package/dist/sessionTokenTotals.d.ts +46 -0
  35. package/dist/sessionTokenTotals.js +138 -0
  36. package/dist/stateFile.d.ts +7 -0
  37. package/dist/tokenSummary.d.ts +27 -1
  38. package/dist/tokenSummary.js +50 -3
  39. package/dist/types/codex.d.ts +4 -0
  40. package/dist/types/sessionEvent.d.ts +22 -0
  41. package/dist/usage/claudeUsageDedupe.d.ts +53 -0
  42. package/dist/usage/claudeUsageDedupe.js +195 -0
  43. package/dist/usage/subagentUsage.d.ts +13 -0
  44. package/dist/usage/subagentUsage.js +21 -0
  45. package/dist/usage/usageEvents.d.ts +18 -2
  46. package/dist/usage/usageEvents.js +83 -8
  47. package/dist/watchers/eventBridge.js +5 -0
  48. package/dist/watchers/jsonlWatcher.d.ts +5 -0
  49. package/dist/watchers/jsonlWatcher.js +40 -5
  50. package/dist/watchers/types.d.ts +5 -0
  51. package/package.json +1 -1
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.generateHtmlReport = generateHtmlReport;
9
9
  const costProvenance_1 = require("../aggregation/costProvenance");
10
10
  const tokenSummary_1 = require("../tokenSummary");
11
+ const sessionTokenTotals_1 = require("../sessionTokenTotals");
11
12
  const htmlHelpers_1 = require("./htmlHelpers");
12
13
  const logo_1 = require("./logo");
13
14
  /**
@@ -16,7 +17,7 @@ const logo_1 = require("./logo");
16
17
  function generateHtmlReport(metrics, transcript, options = {}) {
17
18
  const { sessionFileName, includeThinking = true, includeToolDetail = true, theme = 'dark', } = options;
18
19
  const duration = (0, htmlHelpers_1.formatDuration)(metrics.sessionStartTime, metrics.lastEventTime);
19
- const totalTokens = (0, tokenSummary_1.summarizeTokens)(metrics.tokens).total;
20
+ const tokens = (0, sessionTokenTotals_1.combineSessionTokenTotals)(metrics.tokens, options.subagents ?? []);
20
21
  return `<!DOCTYPE html>
21
22
  <html lang="en" data-theme="${theme}">
22
23
  <head>
@@ -27,7 +28,7 @@ ${generateStyles()}
27
28
  </head>
28
29
  <body>
29
30
  ${generateHeader(metrics, sessionFileName, duration)}
30
- ${generateStatsCards(metrics, totalTokens, duration)}
31
+ ${generateStatsCards(metrics, tokens, duration)}
31
32
  ${generateModelBreakdown(metrics)}
32
33
  ${generateToolBreakdown(metrics)}
33
34
  ${generateTranscriptControls()}
@@ -422,7 +423,9 @@ function generateHeader(metrics, sessionFileName, duration) {
422
423
  </div>
423
424
  </div>`;
424
425
  }
425
- function generateStatsCards(metrics, totalTokens, duration) {
426
+ function generateStatsCards(metrics, tokens, duration) {
427
+ const main = tokens.mainThread;
428
+ const hasSubagents = tokens.subagents.length > 0;
426
429
  const toolCount = metrics.toolStats.reduce((sum, t) => sum + t.successCount + t.failureCount, 0);
427
430
  const cards = [
428
431
  { label: 'Messages', value: String(metrics.messageCount), sub: `${metrics.eventCount} events` },
@@ -432,14 +435,28 @@ function generateStatsCards(metrics, totalTokens, duration) {
432
435
  sub: metrics.sessionStartTime ? (0, htmlHelpers_1.formatTimestamp)(metrics.sessionStartTime) + ' start' : '',
433
436
  },
434
437
  {
435
- label: tokenSummary_1.TOKEN_TOTAL_LABEL,
436
- value: (0, htmlHelpers_1.fmtTokens)(totalTokens),
437
- sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.inputTokens)} in / ${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.outputTokens)} out`,
438
+ label: hasSubagents ? `${tokenSummary_1.TOKEN_MAIN_THREAD_LABEL} — ${tokenSummary_1.TOKEN_TOTAL_LABEL}` : tokenSummary_1.TOKEN_TOTAL_LABEL,
439
+ value: (0, htmlHelpers_1.fmtTokens)(main.total),
440
+ sub: `${(0, htmlHelpers_1.fmtTokens)(main.input)} in · ${(0, htmlHelpers_1.fmtTokens)(main.cacheRead)} cache read · ${(0, htmlHelpers_1.fmtTokens)(main.cacheWrite)} cache write · ${(0, htmlHelpers_1.fmtTokens)(main.output)} out${main.billedOutsideBuckets > 0 ? ` · ${(0, htmlHelpers_1.fmtTokens)(main.billedOutsideBuckets)} reasoning` : ''}`,
438
441
  },
442
+ ...(hasSubagents
443
+ ? [
444
+ {
445
+ label: `${tokenSummary_1.TOKEN_SUBAGENTS_LABEL} (${tokens.subagents.length})`,
446
+ value: (0, htmlHelpers_1.fmtTokens)(tokens.subagentTotal.total),
447
+ sub: `${(0, htmlHelpers_1.fmtTokens)(tokens.subagentTotal.cacheRead)} cache read · ${(0, htmlHelpers_1.fmtTokens)(tokens.subagentTotal.output)} out`,
448
+ },
449
+ {
450
+ label: tokenSummary_1.TOKEN_SESSION_TOTAL_LABEL,
451
+ value: (0, htmlHelpers_1.fmtTokens)(tokens.combined.total),
452
+ sub: 'main thread + subagents',
453
+ },
454
+ ]
455
+ : []),
439
456
  {
440
- label: 'Cache',
441
- value: (0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheReadTokens),
442
- sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheWriteTokens)} written`,
457
+ label: 'Cache hit',
458
+ value: main.cacheHitRatio === null ? '—' : `${Math.round(main.cacheHitRatio * 100)}%`,
459
+ sub: `${(0, htmlHelpers_1.fmtTokens)(main.cacheRead)} of ${(0, htmlHelpers_1.fmtTokens)(main.context)} context tokens`,
443
460
  },
444
461
  {
445
462
  label: 'Tool Calls',
@@ -474,7 +491,7 @@ function generateModelBreakdown(metrics) {
474
491
  return `<div class="section">
475
492
  <div class="section-title">Model Breakdown</div>
476
493
  <table>
477
- <thead><tr><th>Model</th><th>Calls</th><th>Total Tokens</th><th>In / Out</th><th>Cost</th></tr></thead>
494
+ <thead><tr><th>Model</th><th>Calls</th><th>${tokenSummary_1.TOKEN_TOTAL_LABEL}</th><th>In / Out</th><th>Cost</th></tr></thead>
478
495
  <tbody>${rows}</tbody>
479
496
  </table>
480
497
  </div>`;
@@ -534,7 +551,12 @@ function renderMessage(entry, includeThinking, includeToolDetail) {
534
551
  ? `<span class="message-model">${(0, htmlHelpers_1.escapeHtml)(entry.model)}</span>`
535
552
  : '';
536
553
  const tokenStr = entry.usage
537
- ? `<span class="message-tokens">${(0, htmlHelpers_1.fmtTokens)(entry.usage.input_tokens + entry.usage.output_tokens)} tokens</span>`
554
+ ? `<span class="message-tokens">${(0, htmlHelpers_1.fmtTokens)((0, tokenSummary_1.summarizeTokens)({
555
+ inputTokens: entry.usage.input_tokens,
556
+ outputTokens: entry.usage.output_tokens,
557
+ cacheWriteTokens: entry.usage.cache_creation_input_tokens,
558
+ cacheReadTokens: entry.usage.cache_read_input_tokens,
559
+ }).total)} tokens incl. cache</span>`
538
560
  : '';
539
561
  const bodyParts = [];
540
562
  for (const block of entry.content) {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { AggregatedMetrics } from '../aggregation/types';
12
12
  import type { SessionProviderBase } from '../providers/types';
13
- import type { SessionEvent } from '../types/sessionEvent';
13
+ import type { SessionEvent, SubagentStats } from '../types/sessionEvent';
14
14
  import type { TranscriptEntry } from './types';
15
15
  export interface SessionReportInputs {
16
16
  sessionPath: string;
@@ -18,6 +18,8 @@ export interface SessionReportInputs {
18
18
  events: SessionEvent[];
19
19
  metrics: AggregatedMetrics;
20
20
  transcript: TranscriptEntry[];
21
+ /** The session's subagents; pass as `HtmlReportOptions.subagents` for the session total. */
22
+ subagents: SubagentStats[];
21
23
  }
22
24
  /** Read a session once and build the aggregator metrics and report transcript from it. */
23
25
  export declare function readSessionReportInputs(provider: SessionProviderBase, sessionPath: string): SessionReportInputs;
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.readSessionReportInputs = readSessionReportInputs;
14
14
  const EventAggregator_1 = require("../aggregation/EventAggregator");
15
15
  const sessionStats_1 = require("../sessionStats");
16
+ const sessionTokenTotals_1 = require("../sessionTokenTotals");
16
17
  const transcriptParser_1 = require("./transcriptParser");
17
18
  /** Read a session once and build the aggregator metrics and report transcript from it. */
18
19
  function readSessionReportInputs(provider, sessionPath) {
@@ -30,5 +31,6 @@ function readSessionReportInputs(provider, sessionPath) {
30
31
  events,
31
32
  metrics: aggregator.getMetrics(),
32
33
  transcript: (0, transcriptParser_1.parseTranscriptFromEvents)(events),
34
+ subagents: (0, sessionTokenTotals_1.scanSessionSubagents)(provider, sessionPath),
33
35
  };
34
36
  }
@@ -42,4 +42,9 @@ export interface HtmlReportOptions {
42
42
  includeToolDetail?: boolean;
43
43
  /** Color theme (default: 'dark'). */
44
44
  theme?: 'dark' | 'light';
45
+ /**
46
+ * The session's subagents (`provider.scanSubagents()`). Their tokens are
47
+ * reported beside the main thread and added to the session total.
48
+ */
49
+ subagents?: readonly import('../types/sessionEvent').SubagentStats[];
45
50
  }
@@ -51,8 +51,12 @@ export declare const sessionMessageSchema: z.ZodObject<{
51
51
  source: z.ZodOptional<z.ZodString>;
52
52
  }, z.core.$strip>;
53
53
  }, z.core.$strip>>;
54
+ usageKind: z.ZodOptional<z.ZodEnum<{
55
+ call: "call";
56
+ correction: "correction";
57
+ }>>;
54
58
  content: z.ZodOptional<z.ZodUnknown>;
55
- stop_reason: z.ZodOptional<z.ZodString>;
59
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
56
60
  }, z.core.$strip>;
57
61
  export declare const permissionModeSchema: z.ZodEnum<{
58
62
  default: "default";
@@ -141,8 +145,12 @@ export declare const userSessionEventSchema: z.ZodObject<{
141
145
  source: z.ZodOptional<z.ZodString>;
142
146
  }, z.core.$strip>;
143
147
  }, z.core.$strip>>;
148
+ usageKind: z.ZodOptional<z.ZodEnum<{
149
+ call: "call";
150
+ correction: "correction";
151
+ }>>;
144
152
  content: z.ZodOptional<z.ZodUnknown>;
145
- stop_reason: z.ZodOptional<z.ZodString>;
153
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
146
154
  }, z.core.$strip>;
147
155
  }, z.core.$strip>;
148
156
  export declare const assistantSessionEventSchema: z.ZodObject<{
@@ -226,8 +234,12 @@ export declare const assistantSessionEventSchema: z.ZodObject<{
226
234
  source: z.ZodOptional<z.ZodString>;
227
235
  }, z.core.$strip>;
228
236
  }, z.core.$strip>>;
237
+ usageKind: z.ZodOptional<z.ZodEnum<{
238
+ call: "call";
239
+ correction: "correction";
240
+ }>>;
229
241
  content: z.ZodOptional<z.ZodUnknown>;
230
- stop_reason: z.ZodOptional<z.ZodString>;
242
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
231
243
  }, z.core.$strip>;
232
244
  }, z.core.$strip>;
233
245
  export declare const summarySessionEventSchema: z.ZodType<SummarySessionEvent>;
@@ -312,8 +324,12 @@ export declare const systemSessionEventSchema: z.ZodObject<{
312
324
  source: z.ZodOptional<z.ZodString>;
313
325
  }, z.core.$strip>;
314
326
  }, z.core.$strip>>;
327
+ usageKind: z.ZodOptional<z.ZodEnum<{
328
+ call: "call";
329
+ correction: "correction";
330
+ }>>;
315
331
  content: z.ZodOptional<z.ZodUnknown>;
316
- stop_reason: z.ZodOptional<z.ZodString>;
332
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
317
333
  }, z.core.$strip>>;
318
334
  }, z.core.$strip>;
319
335
  export declare const toolUseSessionEventSchema: z.ZodObject<{
@@ -401,8 +417,12 @@ export declare const toolUseSessionEventSchema: z.ZodObject<{
401
417
  source: z.ZodOptional<z.ZodString>;
402
418
  }, z.core.$strip>;
403
419
  }, z.core.$strip>>;
420
+ usageKind: z.ZodOptional<z.ZodEnum<{
421
+ call: "call";
422
+ correction: "correction";
423
+ }>>;
404
424
  content: z.ZodOptional<z.ZodUnknown>;
405
- stop_reason: z.ZodOptional<z.ZodString>;
425
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
406
426
  }, z.core.$strip>>;
407
427
  }, z.core.$strip>;
408
428
  export declare const toolResultSessionEventSchema: z.ZodObject<{
@@ -491,8 +511,12 @@ export declare const toolResultSessionEventSchema: z.ZodObject<{
491
511
  source: z.ZodOptional<z.ZodString>;
492
512
  }, z.core.$strip>;
493
513
  }, z.core.$strip>>;
514
+ usageKind: z.ZodOptional<z.ZodEnum<{
515
+ call: "call";
516
+ correction: "correction";
517
+ }>>;
494
518
  content: z.ZodOptional<z.ZodUnknown>;
495
- stop_reason: z.ZodOptional<z.ZodString>;
519
+ stop_reason: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
496
520
  }, z.core.$strip>>;
497
521
  }, z.core.$strip>;
498
522
  export declare const sessionEventSchema: z.ZodType<SessionEvent>;
@@ -29,8 +29,14 @@ exports.sessionMessageSchema = zod_1.z.object({
29
29
  model: zod_1.z.string().optional(),
30
30
  usage: exports.messageUsageSchema.optional(),
31
31
  normalizedUsage: usageNormalization_1.normalizedUsageSchema.optional(),
32
+ usageKind: zod_1.z.enum(['call', 'correction']).optional(),
32
33
  content: zod_1.z.unknown().optional(),
33
- stop_reason: zod_1.z.string().optional(),
34
+ // Streaming lines can carry `stop_reason: null`; rejecting it dropped the
35
+ // whole event (text, tools, and usage), so normalize it to absent.
36
+ stop_reason: zod_1.z
37
+ .string()
38
+ .nullish()
39
+ .transform((value) => value ?? undefined),
34
40
  });
35
41
  // ── PermissionMode ──
36
42
  exports.permissionModeSchema = zod_1.z.enum([
@@ -58,6 +58,7 @@ export declare const stateFileContextSchema: z.ZodObject<{
58
58
  contextWindowSize: z.ZodNullable<z.ZodNumber>;
59
59
  totalInputTokens: z.ZodNullable<z.ZodNumber>;
60
60
  totalOutputTokens: z.ZodNullable<z.ZodNumber>;
61
+ totalTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
61
62
  }, z.core.$strip>;
62
63
  export declare const stateFileSessionSchema: z.ZodObject<{
63
64
  sessionId: z.ZodNullable<z.ZodString>;
@@ -166,6 +167,7 @@ export declare const sidekickStateFileSchema: z.ZodObject<{
166
167
  contextWindowSize: z.ZodNullable<z.ZodNumber>;
167
168
  totalInputTokens: z.ZodNullable<z.ZodNumber>;
168
169
  totalOutputTokens: z.ZodNullable<z.ZodNumber>;
170
+ totalTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
169
171
  }, z.core.$strip>>;
170
172
  session: z.ZodNullable<z.ZodObject<{
171
173
  sessionId: z.ZodNullable<z.ZodString>;
@@ -39,6 +39,7 @@ exports.stateFileContextSchema = zod_1.z.object({
39
39
  contextWindowSize: zod_1.z.number().nullable(),
40
40
  totalInputTokens: zod_1.z.number().nullable(),
41
41
  totalOutputTokens: zod_1.z.number().nullable(),
42
+ totalTokens: zod_1.z.number().nullable().optional(),
42
43
  });
43
44
  exports.stateFileSessionSchema = zod_1.z.object({
44
45
  sessionId: zod_1.z.string().nullable(),
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A session's token total, main thread plus subagents, in the shared
3
+ * `summarizeTokens` vocabulary.
4
+ *
5
+ * Subagents run in their own transcripts (Claude Code `subagents/agent-*.jsonl`,
6
+ * Codex spawned rollouts, OpenCode child sessions), so the main-thread reader
7
+ * never sees their usage. Every surface that shows "the session total" goes
8
+ * through this module so dump, report, and both dashboards agree.
9
+ *
10
+ * @module sessionTokenTotals
11
+ */
12
+ import type { SessionProviderBase } from './providers/types';
13
+ import type { SubagentStats } from './types/sessionEvent';
14
+ import { type TokenSummary, type TokenTotalsLike } from './tokenSummary';
15
+ export interface SubagentTokenSummary {
16
+ agentId: string;
17
+ agentType?: string;
18
+ description?: string;
19
+ summary: TokenSummary;
20
+ }
21
+ export interface SessionTokenTotals {
22
+ /** Tokens the session's own transcript billed. */
23
+ mainThread: TokenSummary;
24
+ /** One entry per subagent, in provider order. */
25
+ subagents: SubagentTokenSummary[];
26
+ /** All subagents together. */
27
+ subagentTotal: TokenSummary;
28
+ /** Main thread plus subagents. */
29
+ combined: TokenSummary;
30
+ }
31
+ /** Token record of one subagent, in `TokenTotalsLike` form. */
32
+ export declare function subagentTokenTotals(stats: SubagentStats): TokenTotalsLike;
33
+ /** Combine an already-known main-thread total with a session's subagent stats. */
34
+ export declare function combineSessionTokenTotals(mainThread: TokenTotalsLike, subagents: readonly SubagentStats[]): SessionTokenTotals;
35
+ /** Sum the usage a provider reader reports for one session transcript. */
36
+ export declare function readMainThreadTokenTotals(provider: SessionProviderBase, sessionPath: string): TokenTotalsLike;
37
+ /**
38
+ * Main thread plus subagents for one session. Pass `mainThread` when the
39
+ * caller already aggregated the transcript (an `EventAggregator`'s tokens) to
40
+ * skip re-reading it. Subagent discovery failures degrade to "no subagents".
41
+ */
42
+ export declare function collectSessionTokenTotals(provider: SessionProviderBase, sessionPath: string, options?: {
43
+ mainThread?: TokenTotalsLike;
44
+ }): SessionTokenTotals;
45
+ /** A session's subagents by transcript path; discovery failures yield none. */
46
+ export declare function scanSessionSubagents(provider: SessionProviderBase, sessionPath: string): SubagentStats[];
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * A session's token total, main thread plus subagents, in the shared
4
+ * `summarizeTokens` vocabulary.
5
+ *
6
+ * Subagents run in their own transcripts (Claude Code `subagents/agent-*.jsonl`,
7
+ * Codex spawned rollouts, OpenCode child sessions), so the main-thread reader
8
+ * never sees their usage. Every surface that shows "the session total" goes
9
+ * through this module so dump, report, and both dashboards agree.
10
+ *
11
+ * @module sessionTokenTotals
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.subagentTokenTotals = subagentTokenTotals;
48
+ exports.combineSessionTokenTotals = combineSessionTokenTotals;
49
+ exports.readMainThreadTokenTotals = readMainThreadTokenTotals;
50
+ exports.collectSessionTokenTotals = collectSessionTokenTotals;
51
+ exports.scanSessionSubagents = scanSessionSubagents;
52
+ const path = __importStar(require("path"));
53
+ const usageNormalization_1 = require("./usageNormalization");
54
+ const tokenSummary_1 = require("./tokenSummary");
55
+ /** Token record of one subagent, in `TokenTotalsLike` form. */
56
+ function subagentTokenTotals(stats) {
57
+ return {
58
+ inputTokens: stats.inputTokens,
59
+ outputTokens: stats.outputTokens,
60
+ cacheReadTokens: stats.cacheReadTokens,
61
+ cacheWriteTokens: stats.cacheWriteTokens,
62
+ reasoningTokens: stats.reasoningTokens,
63
+ ...(typeof stats.totalTokens === 'number' ? { totalTokens: stats.totalTokens } : {}),
64
+ };
65
+ }
66
+ /** Combine an already-known main-thread total with a session's subagent stats. */
67
+ function combineSessionTokenTotals(mainThread, subagents) {
68
+ const subagentRecords = subagents.map(subagentTokenTotals);
69
+ const main = (0, tokenSummary_1.summarizeTokens)(mainThread);
70
+ const mainRecord = {
71
+ inputTokens: main.input,
72
+ outputTokens: main.output,
73
+ cacheReadTokens: main.cacheRead,
74
+ cacheWriteTokens: main.cacheWrite,
75
+ reasoningTokens: main.reasoning,
76
+ totalTokens: main.total,
77
+ };
78
+ return {
79
+ mainThread: main,
80
+ subagents: subagents.map((stats, index) => ({
81
+ agentId: stats.agentId,
82
+ ...(stats.agentType ? { agentType: stats.agentType } : {}),
83
+ ...(stats.description ? { description: stats.description } : {}),
84
+ summary: (0, tokenSummary_1.summarizeTokens)(subagentRecords[index]),
85
+ })),
86
+ subagentTotal: (0, tokenSummary_1.summarizeTokens)((0, tokenSummary_1.sumTokenTotals)(subagentRecords)),
87
+ combined: (0, tokenSummary_1.summarizeTokens)((0, tokenSummary_1.sumTokenTotals)([mainRecord, ...subagentRecords])),
88
+ };
89
+ }
90
+ /** Sum the usage a provider reader reports for one session transcript. */
91
+ function readMainThreadTokenTotals(provider, sessionPath) {
92
+ const reader = provider.createReader(sessionPath);
93
+ const events = reader.readAll();
94
+ reader.flush();
95
+ let inputTokens = 0;
96
+ let outputTokens = 0;
97
+ let cacheReadTokens = 0;
98
+ let cacheWriteTokens = 0;
99
+ let reasoningTokens = 0;
100
+ let totalTokens = 0;
101
+ for (const event of events) {
102
+ const usage = (0, usageNormalization_1.extractNormalizedUsage)(event);
103
+ if (!usage)
104
+ continue;
105
+ inputTokens += usage.uncachedInputTokens;
106
+ outputTokens += usage.outputTokens;
107
+ cacheReadTokens += usage.cacheReadTokens;
108
+ cacheWriteTokens += usage.cacheWriteTokens;
109
+ reasoningTokens += usage.reasoningTokens;
110
+ totalTokens += usage.totalTokens;
111
+ }
112
+ return {
113
+ inputTokens,
114
+ outputTokens,
115
+ cacheReadTokens,
116
+ cacheWriteTokens,
117
+ reasoningTokens,
118
+ totalTokens,
119
+ };
120
+ }
121
+ /**
122
+ * Main thread plus subagents for one session. Pass `mainThread` when the
123
+ * caller already aggregated the transcript (an `EventAggregator`'s tokens) to
124
+ * skip re-reading it. Subagent discovery failures degrade to "no subagents".
125
+ */
126
+ function collectSessionTokenTotals(provider, sessionPath, options = {}) {
127
+ const mainThread = options.mainThread ?? readMainThreadTokenTotals(provider, sessionPath);
128
+ return combineSessionTokenTotals(mainThread, scanSessionSubagents(provider, sessionPath));
129
+ }
130
+ /** A session's subagents by transcript path; discovery failures yield none. */
131
+ function scanSessionSubagents(provider, sessionPath) {
132
+ try {
133
+ return provider.scanSubagents(path.dirname(sessionPath), provider.getSessionId(sessionPath));
134
+ }
135
+ catch {
136
+ return [];
137
+ }
138
+ }
@@ -44,8 +44,15 @@ export interface StateFileAccount {
44
44
  export interface StateFileContext {
45
45
  usedPercentage: number | null;
46
46
  contextWindowSize: number | null;
47
+ /** Input tokens including cache reads and writes (Claude Code status-line semantics). */
47
48
  totalInputTokens: number | null;
48
49
  totalOutputTokens: number | null;
50
+ /**
51
+ * Every token the session billed, cache included — the `summarizeTokens().total`
52
+ * figure the dashboards show. Absent or null when the writer does not know it
53
+ * (the status line only sees the current context).
54
+ */
55
+ totalTokens?: number | null;
49
56
  }
50
57
  export interface StateFileSession {
51
58
  sessionId: string | null;
@@ -28,6 +28,8 @@ export interface TokenTotalsLike {
28
28
  outputTokens: number;
29
29
  cacheWriteTokens?: number;
30
30
  cacheReadTokens?: number;
31
+ /** Reasoning tokens, when the source tracks them (display only). */
32
+ reasoningTokens?: number;
31
33
  /**
32
34
  * A provider-semantics-aware total, when the source already computed one
33
35
  * (for example `AggregatedTokens.totalTokens`). Preferred over the
@@ -51,12 +53,36 @@ export interface TokenSummary {
51
53
  cacheWrite: number;
52
54
  /** Fraction of context tokens that came from cache reads (0..1), or null with no input. */
53
55
  cacheHitRatio: number | null;
56
+ /** Reasoning tokens reported by the provider (display only; may be inside `output`). */
57
+ reasoning: number;
58
+ /**
59
+ * Tokens in `total` beyond the four buckets — reasoning billed outside
60
+ * `output` by providers that report it separately. Zero when the buckets
61
+ * already add up to `total`.
62
+ */
63
+ billedOutsideBuckets: number;
54
64
  }
55
65
  /** Column label for `TokenSummary.total` on any surface. */
56
66
  export declare const TOKEN_TOTAL_LABEL = "Total (incl. cache)";
57
67
  /** Column label for `TokenSummary.context` on any surface. */
58
68
  export declare const TOKEN_CONTEXT_LABEL = "Context";
69
+ /** Label for a session's own (main-thread) tokens when subagents are shown beside it. */
70
+ export declare const TOKEN_MAIN_THREAD_LABEL = "Main thread";
71
+ /** Label for the tokens a session's subagents used. */
72
+ export declare const TOKEN_SUBAGENTS_LABEL = "Subagents";
73
+ /** Label for main thread plus subagents. */
74
+ export declare const TOKEN_SESSION_TOTAL_LABEL = "Session total (incl. subagents)";
59
75
  /** Derive the shared token vocabulary from any four-bucket token record. */
60
76
  export declare function summarizeTokens(totals: TokenTotalsLike): TokenSummary;
61
- /** Sum several token records bucket-by-bucket before summarizing. */
77
+ /**
78
+ * One-line breakdown whose parts add up to the headline:
79
+ * `1.2M total incl. cache (12k in · 1.1M cache read · 40k cache write · 30k out)`.
80
+ * Pass the surface's own number formatter; the default prints plain integers.
81
+ */
82
+ export declare function formatTokenBreakdown(summary: TokenSummary, fmt?: (n: number) => string): string;
83
+ /**
84
+ * Sum several token records bucket-by-bucket before summarizing. `totalTokens`
85
+ * is summed only when every record carries one, so provider-aware totals
86
+ * survive the sum instead of falling back to the four-bucket arithmetic.
87
+ */
62
88
  export declare function sumTokenTotals(records: readonly TokenTotalsLike[]): TokenTotalsLike;
@@ -24,13 +24,20 @@
24
24
  * @module tokenSummary
25
25
  */
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.TOKEN_CONTEXT_LABEL = exports.TOKEN_TOTAL_LABEL = void 0;
27
+ exports.TOKEN_SESSION_TOTAL_LABEL = exports.TOKEN_SUBAGENTS_LABEL = exports.TOKEN_MAIN_THREAD_LABEL = exports.TOKEN_CONTEXT_LABEL = exports.TOKEN_TOTAL_LABEL = void 0;
28
28
  exports.summarizeTokens = summarizeTokens;
29
+ exports.formatTokenBreakdown = formatTokenBreakdown;
29
30
  exports.sumTokenTotals = sumTokenTotals;
30
31
  /** Column label for `TokenSummary.total` on any surface. */
31
32
  exports.TOKEN_TOTAL_LABEL = 'Total (incl. cache)';
32
33
  /** Column label for `TokenSummary.context` on any surface. */
33
34
  exports.TOKEN_CONTEXT_LABEL = 'Context';
35
+ /** Label for a session's own (main-thread) tokens when subagents are shown beside it. */
36
+ exports.TOKEN_MAIN_THREAD_LABEL = 'Main thread';
37
+ /** Label for the tokens a session's subagents used. */
38
+ exports.TOKEN_SUBAGENTS_LABEL = 'Subagents';
39
+ /** Label for main thread plus subagents. */
40
+ exports.TOKEN_SESSION_TOTAL_LABEL = 'Session total (incl. subagents)';
34
41
  function count(value) {
35
42
  return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
36
43
  }
@@ -54,19 +61,59 @@ function summarizeTokens(totals) {
54
61
  cacheRead,
55
62
  cacheWrite,
56
63
  cacheHitRatio: context > 0 ? cacheRead / context : null,
64
+ reasoning: count(totals.reasoningTokens),
65
+ billedOutsideBuckets: Math.max(0, total - fourBucket),
57
66
  };
58
67
  }
59
- /** Sum several token records bucket-by-bucket before summarizing. */
68
+ /**
69
+ * One-line breakdown whose parts add up to the headline:
70
+ * `1.2M total incl. cache (12k in · 1.1M cache read · 40k cache write · 30k out)`.
71
+ * Pass the surface's own number formatter; the default prints plain integers.
72
+ */
73
+ function formatTokenBreakdown(summary, fmt = (n) => String(n)) {
74
+ const parts = [
75
+ `${fmt(summary.input)} in`,
76
+ `${fmt(summary.cacheRead)} cache read`,
77
+ `${fmt(summary.cacheWrite)} cache write`,
78
+ `${fmt(summary.output)} out`,
79
+ ];
80
+ if (summary.billedOutsideBuckets > 0) {
81
+ parts.push(`${fmt(summary.billedOutsideBuckets)} reasoning`);
82
+ }
83
+ return `${fmt(summary.total)} total incl. cache (${parts.join(' · ')})`;
84
+ }
85
+ /**
86
+ * Sum several token records bucket-by-bucket before summarizing. `totalTokens`
87
+ * is summed only when every record carries one, so provider-aware totals
88
+ * survive the sum instead of falling back to the four-bucket arithmetic.
89
+ */
60
90
  function sumTokenTotals(records) {
61
91
  let inputTokens = 0;
62
92
  let outputTokens = 0;
63
93
  let cacheWriteTokens = 0;
64
94
  let cacheReadTokens = 0;
95
+ let reasoningTokens = 0;
96
+ let totalTokens = 0;
97
+ let allHaveTotal = records.length > 0;
65
98
  for (const record of records) {
66
99
  inputTokens += count(record.inputTokens);
67
100
  outputTokens += count(record.outputTokens);
68
101
  cacheWriteTokens += count(record.cacheWriteTokens);
69
102
  cacheReadTokens += count(record.cacheReadTokens);
103
+ reasoningTokens += count(record.reasoningTokens);
104
+ if (typeof record.totalTokens === 'number' && Number.isFinite(record.totalTokens)) {
105
+ totalTokens += count(record.totalTokens);
106
+ }
107
+ else {
108
+ allHaveTotal = false;
109
+ }
70
110
  }
71
- return { inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
111
+ return {
112
+ inputTokens,
113
+ outputTokens,
114
+ cacheWriteTokens,
115
+ cacheReadTokens,
116
+ reasoningTokens,
117
+ ...(allHaveTotal ? { totalTokens } : {}),
118
+ };
72
119
  }
@@ -287,4 +287,8 @@ export interface CodexDbThread {
287
287
  git_branch?: string;
288
288
  git_commit?: string;
289
289
  forked_from_id?: string;
290
+ /** `cli`, `exec`, … or a JSON object string for spawned subagents. */
291
+ source?: string;
292
+ agent_nickname?: string | null;
293
+ agent_role?: string | null;
290
294
  }
@@ -45,6 +45,13 @@ export interface SessionMessage {
45
45
  usage?: MessageUsage;
46
46
  /** Authoritative disjoint usage projection, when normalized by a provider parser. */
47
47
  normalizedUsage?: import('../usageNormalization').NormalizedUsage;
48
+ /**
49
+ * `'correction'` marks usage that tops up a call already counted (a later
50
+ * split line of the same response reported more tokens). Add it to token and
51
+ * cost totals, but do not count another call or context sample. Absent means
52
+ * a regular call.
53
+ */
54
+ usageKind?: 'call' | 'correction';
48
55
  /** Message content (may be string or structured content) */
49
56
  content?: unknown;
50
57
  /** Provider stop reason (`end_turn`, `tool_use`, …) when the transcript carries one. */
@@ -222,6 +229,12 @@ export interface TokenUsage {
222
229
  reportedCost?: number;
223
230
  /** Number of reasoning/thinking tokens */
224
231
  reasoningTokens?: number;
232
+ /**
233
+ * `'correction'` tops up a call already counted (a later split line of the
234
+ * same response reported more tokens): add it to totals, but do not count a
235
+ * call or treat it as a context-size sample.
236
+ */
237
+ usageKind?: 'call' | 'correction';
225
238
  }
226
239
  /**
227
240
  * Tool invocation record.
@@ -299,6 +312,7 @@ export interface TimelineEvent {
299
312
  toolName?: string;
300
313
  toolUseId?: string;
301
314
  isError?: boolean;
315
+ /** Tokens billed for this response, cache included (see `summarizeTokens`). */
302
316
  tokenCount?: number;
303
317
  fullText?: string;
304
318
  /** Context tokens before compaction */
@@ -395,6 +409,14 @@ export interface SubagentStats {
395
409
  inputTokens: number;
396
410
  /** Total output tokens generated by this subagent */
397
411
  outputTokens: number;
412
+ /** Tokens read from the prompt cache by this subagent */
413
+ cacheReadTokens?: number;
414
+ /** Tokens written to the prompt cache by this subagent */
415
+ cacheWriteTokens?: number;
416
+ /** Reasoning tokens reported by this subagent (display only) */
417
+ reasoningTokens?: number;
418
+ /** Every billed token, cache included — the same vocabulary as `summarizeTokens().total` */
419
+ totalTokens?: number;
398
420
  /** Timestamp when subagent started (first event) */
399
421
  startTime?: Date;
400
422
  /** Timestamp when subagent ended (last event) */