@saccolabs/pi-claude-cli 0.4.10 → 0.4.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saccolabs/pi-claude-cli",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Pi coding agent extension that routes LLM calls through the Claude Code CLI",
5
5
  "main": "index.ts",
6
6
  "keywords": [
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ClaudeApiEvent,
3
3
  ClaudeAssistantEnvelope,
4
+ ClaudeModelUsage,
4
5
  ClaudeResultMessage,
5
6
  ClaudeUsage,
6
7
  TrackedContentBlock,
@@ -151,6 +152,40 @@ export function createEventBridge(
151
152
  /** Tool ids already surfaced as markers (envelope arrives once per block). */
152
153
  const markedToolIds = new Set<string>();
153
154
 
155
+ /**
156
+ * Fold per-model spend into one `ClaudeUsage`.
157
+ *
158
+ * Every entry counts, including models the session did not pick: a
159
+ * sub-agent may run a different model, and the auto-titler always does.
160
+ * Returns undefined when the CLI sent no `modelUsage` (older versions), so
161
+ * the caller can fall back to the main-agent-only `usage`.
162
+ *
163
+ * The folded tokens are priced at the SESSION's model rates by
164
+ * `calculateCost`. That is exact for sub-agents, which inherit the session
165
+ * model, and slightly off for a cheaper helper model — a known skew worth
166
+ * far less than the tokens it stops hiding.
167
+ */
168
+ function sumModelUsage(
169
+ modelUsage: Record<string, ClaudeModelUsage> | undefined,
170
+ ): ClaudeUsage | undefined {
171
+ if (!modelUsage) return undefined;
172
+ const entries = Object.values(modelUsage);
173
+ if (entries.length === 0) return undefined;
174
+ const total = {
175
+ input_tokens: 0,
176
+ output_tokens: 0,
177
+ cache_read_input_tokens: 0,
178
+ cache_creation_input_tokens: 0,
179
+ };
180
+ for (const entry of entries) {
181
+ total.input_tokens += entry.inputTokens ?? 0;
182
+ total.output_tokens += entry.outputTokens ?? 0;
183
+ total.cache_read_input_tokens += entry.cacheReadInputTokens ?? 0;
184
+ total.cache_creation_input_tokens += entry.cacheCreationInputTokens ?? 0;
185
+ }
186
+ return total;
187
+ }
188
+
154
189
  /** Prompt size of one cycle: everything the model read, excluding output. */
155
190
  function contextOf(usage: ClaudeUsage): number {
156
191
  return (
@@ -569,19 +604,28 @@ export function createEventBridge(
569
604
  }
570
605
 
571
606
  function applyResult(result: ClaudeResultMessage): void {
572
- // Authoritative cumulative usage for the whole episode (verified to
573
- // equal the per-cycle sums on captured streams; trusted over them).
574
- const usage = result.usage;
575
- if (usage) {
607
+ // Authoritative spend for the whole episode. `modelUsage` is preferred
608
+ // over `usage` because `usage` is the MAIN AGENT ONLY: sub-agents run
609
+ // inside the CLI and never appear in the parent stream, so their tokens
610
+ // often the majority — were simply missing from what the host billed.
611
+ // Same for helper models like the haiku auto-titler.
612
+ //
613
+ // Measured 2026-08-27: a lane's turn reported $2.34 while seven
614
+ // sub-agents spent 28.6M cache-read tokens on top of it, a 10x
615
+ // under-report. On a captured single-sub-agent episode `modelUsage`
616
+ // summed to exactly main + sub-agent (102,641 cache-read / 56,920
617
+ // cache-write), which is why it is trusted here.
618
+ const totals = sumModelUsage(result.modelUsage) ?? result.usage;
619
+ if (totals) {
576
620
  cumulativeUsage.input_tokens =
577
- usage.input_tokens ?? cumulativeUsage.input_tokens;
621
+ totals.input_tokens ?? cumulativeUsage.input_tokens;
578
622
  cumulativeUsage.output_tokens =
579
- usage.output_tokens ?? cumulativeUsage.output_tokens;
623
+ totals.output_tokens ?? cumulativeUsage.output_tokens;
580
624
  cumulativeUsage.cache_read_input_tokens =
581
- usage.cache_read_input_tokens ??
625
+ totals.cache_read_input_tokens ??
582
626
  cumulativeUsage.cache_read_input_tokens;
583
627
  cumulativeUsage.cache_creation_input_tokens =
584
- usage.cache_creation_input_tokens ??
628
+ totals.cache_creation_input_tokens ??
585
629
  cumulativeUsage.cache_creation_input_tokens;
586
630
  cycleUsage = {};
587
631
  recomputeUsage();
package/src/types.ts CHANGED
@@ -7,14 +7,37 @@ export interface ClaudeStreamEventMessage {
7
7
  event: ClaudeApiEvent;
8
8
  }
9
9
 
10
+ /**
11
+ * Per-model spend for the episode, keyed by model id.
12
+ *
13
+ * This is the ONLY place the CLI accounts for work done outside the main
14
+ * agent's own transcript: sub-agents (which run inside the CLI and never
15
+ * appear in the parent stream) and helper models such as the haiku
16
+ * auto-titler. `result.usage` covers the main agent alone.
17
+ *
18
+ * Verified 2026-08-27 on a captured episode with one synchronous sub-agent:
19
+ * main agent 74,562 cache-read / 26,808 cache-write, sub-agent 28,079 /
20
+ * 30,112, and `modelUsage` reported exactly the two summed — 102,641 /
21
+ * 56,920.
22
+ */
23
+ export interface ClaudeModelUsage {
24
+ inputTokens?: number;
25
+ outputTokens?: number;
26
+ cacheReadInputTokens?: number;
27
+ cacheCreationInputTokens?: number;
28
+ costUSD?: number;
29
+ }
30
+
10
31
  export interface ClaudeResultMessage {
11
32
  type: "result";
12
33
  subtype: "success" | "error";
13
34
  result?: string;
14
35
  error?: string;
15
36
  session_id?: string;
16
- /** Cumulative usage across every cycle of the episode (authoritative). */
37
+ /** Cumulative usage for the MAIN agent across every cycle of the episode. */
17
38
  usage?: ClaudeUsage;
39
+ /** Per-model spend, including sub-agents and helper models. */
40
+ modelUsage?: Record<string, ClaudeModelUsage>;
18
41
  num_turns?: number;
19
42
  total_cost_usd?: number;
20
43
  }