@saccolabs/pi-claude-cli 0.4.10 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/event-bridge.ts +52 -8
- package/src/thinking-config.ts +21 -36
- package/src/types.ts +24 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Requires the `claude` binary on your login-shell PATH (`npm install -g @anthropi
|
|
|
48
48
|
- Native tool execution: the CLI runs its own tools; guards are injected as Claude Code PreToolUse hooks via `PI_CLAUDE_CLI_SETTINGS`
|
|
49
49
|
- Reports account rate-limit state (window, reset, overage) to the front-end
|
|
50
50
|
on the `claude-rate-limit` status key — never mixed into turn content
|
|
51
|
-
- Configurable thinking effort across the full ladder (low to max) for
|
|
51
|
+
- Configurable thinking effort across the full ladder (low to max), mapped 1:1 for every model: the level the host asks for is the level the CLI gets
|
|
52
52
|
- Cross-platform subprocess management (Windows, macOS, Linux)
|
|
53
53
|
- Inactivity timeout and process registry for cleanup
|
|
54
54
|
|
package/package.json
CHANGED
package/src/event-bridge.ts
CHANGED
|
@@ -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
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
621
|
+
totals.input_tokens ?? cumulativeUsage.input_tokens;
|
|
578
622
|
cumulativeUsage.output_tokens =
|
|
579
|
-
|
|
623
|
+
totals.output_tokens ?? cumulativeUsage.output_tokens;
|
|
580
624
|
cumulativeUsage.cache_read_input_tokens =
|
|
581
|
-
|
|
625
|
+
totals.cache_read_input_tokens ??
|
|
582
626
|
cumulativeUsage.cache_read_input_tokens;
|
|
583
627
|
cumulativeUsage.cache_creation_input_tokens =
|
|
584
|
-
|
|
628
|
+
totals.cache_creation_input_tokens ??
|
|
585
629
|
cumulativeUsage.cache_creation_input_tokens;
|
|
586
630
|
cycleUsage = {};
|
|
587
631
|
recomputeUsage();
|
package/src/thinking-config.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Thinking effort configuration for mapping pi's ThinkingLevel to Claude CLI --effort flags.
|
|
3
3
|
*
|
|
4
4
|
* Maps pi's reasoning levels (minimal/low/medium/high/xhigh/max) to the CLI's effort
|
|
5
|
-
* levels (low/medium/high/xhigh/max).
|
|
6
|
-
*
|
|
5
|
+
* levels (low/medium/high/xhigh/max). Every model passes through 1:1 apart from
|
|
6
|
+
* `minimal`, which the CLI has no rung for and which floors at `low`.
|
|
7
7
|
*
|
|
8
8
|
* IMPORTANT: The CLI does NOT support --thinking-budget. Only --effort is supported.
|
|
9
9
|
*/
|
|
@@ -14,12 +14,23 @@ import type { ThinkingLevel, ThinkingBudgets } from "@earendil-works/pi-ai";
|
|
|
14
14
|
export type CliEffortLevel = "low" | "medium" | "high" | "xhigh" | "max";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* pi ThinkingLevel -> CLI effort, 1:1 for every rung the CLI has.
|
|
18
|
+
*
|
|
19
|
+
* The CLI accepts the full ladder (low/medium/high/xhigh/max) on every current
|
|
20
|
+
* model — verified with claude-fable-5 and claude-sonnet-5, and on 2026-08-27
|
|
21
|
+
* with claude-opus-5, where `--effort high`, `xhigh` and `max` were each
|
|
22
|
+
* accepted and recorded distinctly in the session transcript's `effort` field.
|
|
23
|
+
*
|
|
24
|
+
* Opus used to be shifted up a rung here (medium→high, high→max) to compensate
|
|
25
|
+
* for a cap that no longer exists. That made `high` unrequestable on opus and
|
|
26
|
+
* was not a private detail: Claude Code skills size their own sub-agent fan-out
|
|
27
|
+
* from this flag, so a host asking for `high` silently got the widest tier the
|
|
28
|
+
* skill offered. See https://github.com/agustinsacco/pi-claude-cli/issues/22.
|
|
29
|
+
*
|
|
30
|
+
* `minimal` has no CLI rung and floors at `low`. That is a floor, not a shift:
|
|
31
|
+
* it maps down, and no level maps above what the host asked for.
|
|
21
32
|
*/
|
|
22
|
-
const
|
|
33
|
+
const EFFORT_MAP: Record<ThinkingLevel, CliEffortLevel> = {
|
|
23
34
|
minimal: "low",
|
|
24
35
|
low: "low",
|
|
25
36
|
medium: "medium",
|
|
@@ -28,30 +39,6 @@ const STANDARD_EFFORT_MAP: Record<ThinkingLevel, CliEffortLevel> = {
|
|
|
28
39
|
max: "max",
|
|
29
40
|
};
|
|
30
41
|
|
|
31
|
-
/**
|
|
32
|
-
* Opus model mapping: shifted up for elevated reasoning.
|
|
33
|
-
* Opus models get max capability at high/xhigh/max levels.
|
|
34
|
-
*/
|
|
35
|
-
const OPUS_EFFORT_MAP: Record<ThinkingLevel, CliEffortLevel> = {
|
|
36
|
-
minimal: "low",
|
|
37
|
-
low: "low",
|
|
38
|
-
medium: "high", // shifted: standard high
|
|
39
|
-
high: "max", // shifted: maximum capability
|
|
40
|
-
xhigh: "max", // Opus gets max
|
|
41
|
-
max: "max",
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Detect whether a model ID refers to an Opus model.
|
|
46
|
-
* Uses includes('opus') for forward-compatibility with future Opus versions.
|
|
47
|
-
*
|
|
48
|
-
* @param modelId - The model identifier string
|
|
49
|
-
* @returns true if the model is an Opus variant
|
|
50
|
-
*/
|
|
51
|
-
export function isOpusModel(modelId: string): boolean {
|
|
52
|
-
return modelId.includes("opus");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
42
|
/**
|
|
56
43
|
* Map pi's ThinkingLevel to a CLI effort string.
|
|
57
44
|
*
|
|
@@ -61,13 +48,13 @@ export function isOpusModel(modelId: string): boolean {
|
|
|
61
48
|
* not token budgets.
|
|
62
49
|
*
|
|
63
50
|
* @param reasoning - Pi's thinking level (undefined = omit flag)
|
|
64
|
-
* @param
|
|
51
|
+
* @param _modelId - Unused; kept so callers need not change. Every model maps alike.
|
|
65
52
|
* @param thinkingBudgets - Custom budgets (logged as unsupported, not applied)
|
|
66
53
|
* @returns CLI effort level string, or undefined if flag should be omitted
|
|
67
54
|
*/
|
|
68
55
|
export function mapThinkingEffort(
|
|
69
56
|
reasoning?: ThinkingLevel,
|
|
70
|
-
|
|
57
|
+
_modelId?: string,
|
|
71
58
|
thinkingBudgets?: ThinkingBudgets,
|
|
72
59
|
): CliEffortLevel | undefined {
|
|
73
60
|
if (reasoning === undefined) {
|
|
@@ -81,7 +68,5 @@ export function mapThinkingEffort(
|
|
|
81
68
|
);
|
|
82
69
|
}
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
const map = isOpus ? OPUS_EFFORT_MAP : STANDARD_EFFORT_MAP;
|
|
86
|
-
return map[reasoning];
|
|
71
|
+
return EFFORT_MAP[reasoning];
|
|
87
72
|
}
|
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
|
|
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
|
}
|