@saccolabs/pi-claude-cli 0.4.16 → 0.5.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.
- package/README.md +32 -0
- package/package.json +1 -1
- package/src/autocompact.ts +99 -0
- package/src/process-manager.ts +11 -0
package/README.md
CHANGED
|
@@ -95,6 +95,38 @@ allowlists). Model access and your subscription login are unaffected.
|
|
|
95
95
|
Related knobs: `PI_CLAUDE_CLI_TIMEOUT_MS` overrides the 300s inactivity
|
|
96
96
|
timeout (CLI-side tools can be silent on stdout for minutes).
|
|
97
97
|
|
|
98
|
+
### Auto-compact window
|
|
99
|
+
|
|
100
|
+
The provider resumes **one** CLI session for a pi session's whole life, and
|
|
101
|
+
nothing else ever shrinks it. On 1M-context models the CLI's own auto-compact
|
|
102
|
+
default lets that session ratchet toward a million tokens — measured across 26
|
|
103
|
+
real sessions, contexts reached 480k+, the average request carried 202k
|
|
104
|
+
tokens, and every request re-reads the full context. So the provider passes
|
|
105
|
+
`--autocompact 200000` **by default**: Claude Code compacts the session itself
|
|
106
|
+
when its context nears 200k, keeping the cached system-prompt prefix and full
|
|
107
|
+
transcript fidelity.
|
|
108
|
+
|
|
109
|
+
`PI_CLAUDE_CLI_AUTOCOMPACT` configures it (read per spawn, like the flags
|
|
110
|
+
above):
|
|
111
|
+
|
|
112
|
+
| Value | Behaviour |
|
|
113
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------- |
|
|
114
|
+
| _(unset)_ | `--autocompact 200000` — the 200k budget these models run under everywhere the 1M beta is off. |
|
|
115
|
+
| `400k`, `400000`, `400` | Any window from 100k to 1M; bare numbers are thousands (CLI shorthand). |
|
|
116
|
+
| `auto` | `--autocompact auto` — the CLI's own default (≈ the model's full window). |
|
|
117
|
+
| `off` | Omit the flag entirely (use on CLIs that predate `--autocompact`). |
|
|
118
|
+
|
|
119
|
+
The value is a token **count**, not a percentage: cache read/write bill per
|
|
120
|
+
token and every request re-reads the whole context, so the sane budget is the
|
|
121
|
+
same on a 200k model and a 1M one. Invalid values warn and fall back to the
|
|
122
|
+
default instead of reaching the CLI, which rejects them by refusing to start.
|
|
123
|
+
|
|
124
|
+
Note for pre-existing sessions: the first resumed turn of a session already
|
|
125
|
+
past the window compacts immediately — one summarization pass, then the
|
|
126
|
+
session continues small. That is the remediation, not a bug. pi's own
|
|
127
|
+
compaction is separate (it rewrites pi's transcript, never the CLI session's)
|
|
128
|
+
and with this cap it should rarely trigger.
|
|
129
|
+
|
|
98
130
|
### Which system prompt
|
|
99
131
|
|
|
100
132
|
`PI_CLAUDE_CLI_SYSTEM_PROMPT` chooses whose system prompt the subprocess
|
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-compact window resolution (PI_CLAUDE_CLI_AUTOCOMPACT).
|
|
3
|
+
*
|
|
4
|
+
* Claude Code compacts a session automatically when its context approaches
|
|
5
|
+
* the auto-compact window (`--autocompact <auto|tokens>`, claude 2.1.x). On
|
|
6
|
+
* 1M-context models the CLI's own default lets a long-lived session ratchet
|
|
7
|
+
* toward a million tokens, and this provider resumes ONE CLI session for a
|
|
8
|
+
* pi session's whole life — nothing else ever shrinks it. Measured across
|
|
9
|
+
* 26 real pidex sessions (2026-08-30): contexts ratcheted to 480k+, the
|
|
10
|
+
* average request carried 202k tokens, and cache reads alone were ~53% of
|
|
11
|
+
* total spend. Sessions that stayed near 100–150k did the same work at a
|
|
12
|
+
* fraction of the cost.
|
|
13
|
+
*
|
|
14
|
+
* So the provider caps the window at 200k tokens BY DEFAULT — the budget
|
|
15
|
+
* these models run under everywhere the 1M beta is not enabled — and lets
|
|
16
|
+
* the host raise, lower, or disable it:
|
|
17
|
+
*
|
|
18
|
+
* PI_CLAUDE_CLI_AUTOCOMPACT=200k cap at 200k (the default)
|
|
19
|
+
* PI_CLAUDE_CLI_AUTOCOMPACT=400000 plain token counts work too
|
|
20
|
+
* PI_CLAUDE_CLI_AUTOCOMPACT=400 bare numbers are thousands (CLI shorthand)
|
|
21
|
+
* PI_CLAUDE_CLI_AUTOCOMPACT=auto the CLI's own default behaviour
|
|
22
|
+
* PI_CLAUDE_CLI_AUTOCOMPACT=off omit the flag entirely (also: 0, none,
|
|
23
|
+
* disable, disabled — for CLIs that
|
|
24
|
+
* predate --autocompact)
|
|
25
|
+
*
|
|
26
|
+
* The value is a token COUNT, not a percentage of the model's window. The
|
|
27
|
+
* costs this guards against are absolute — cache read/write bill per token,
|
|
28
|
+
* and every request re-reads the whole context — so "half the window" means
|
|
29
|
+
* something completely different on a 200k model than on a 1M one, while
|
|
30
|
+
* 200k tokens costs the same everywhere.
|
|
31
|
+
*
|
|
32
|
+
* An invalid or out-of-range value falls back to the default with a warning
|
|
33
|
+
* rather than being passed through: the CLI rejects bad values by refusing
|
|
34
|
+
* to start, which would kill every turn of every session over a typo.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** Default auto-compact window, in tokens. */
|
|
38
|
+
export const DEFAULT_AUTOCOMPACT_TOKENS = 200_000;
|
|
39
|
+
|
|
40
|
+
/** The CLI accepts 100k–1M (claude 2.1.231: "It must be 'auto', or between 100k and 1M"). */
|
|
41
|
+
const MIN_TOKENS = 100_000;
|
|
42
|
+
const MAX_TOKENS = 1_000_000;
|
|
43
|
+
|
|
44
|
+
const OFF_VALUES = new Set([
|
|
45
|
+
"off",
|
|
46
|
+
"0",
|
|
47
|
+
"none",
|
|
48
|
+
"disable",
|
|
49
|
+
"disabled",
|
|
50
|
+
"false",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse a user-supplied window size into a token count.
|
|
55
|
+
* Mirrors the CLI's accepted forms: `500k`, `1M`, `200000`, and bare `200`
|
|
56
|
+
* meaning thousands. Returns undefined when unparseable.
|
|
57
|
+
*/
|
|
58
|
+
export function parseAutocompactTokens(raw: string): number | undefined {
|
|
59
|
+
const m = /^(\d+(?:\.\d+)?)\s*([km])?$/i.exec(raw.trim());
|
|
60
|
+
if (!m) return undefined;
|
|
61
|
+
const n = Number(m[1]);
|
|
62
|
+
if (!Number.isFinite(n) || n <= 0) return undefined;
|
|
63
|
+
const suffix = (m[2] ?? "").toLowerCase();
|
|
64
|
+
if (suffix === "k") return Math.round(n * 1_000);
|
|
65
|
+
if (suffix === "m") return Math.round(n * 1_000_000);
|
|
66
|
+
// Bare number: the CLI reads `200` as 200k shorthand; anything that already
|
|
67
|
+
// looks like a token count (>= 100k) is taken literally.
|
|
68
|
+
return n < MIN_TOKENS ? Math.round(n * 1_000) : Math.round(n);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the `--autocompact` argument from the environment.
|
|
73
|
+
*
|
|
74
|
+
* Returns the string to pass to the flag, or undefined to omit the flag
|
|
75
|
+
* entirely (explicit off). Unset resolves to the 200k default; `auto` is
|
|
76
|
+
* passed through so the CLI applies its own default; invalid values warn
|
|
77
|
+
* and fall back to the default.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveAutocompact(
|
|
80
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
81
|
+
): string | undefined {
|
|
82
|
+
const raw = (env.PI_CLAUDE_CLI_AUTOCOMPACT ?? "").trim();
|
|
83
|
+
if (raw === "") return String(DEFAULT_AUTOCOMPACT_TOKENS);
|
|
84
|
+
|
|
85
|
+
const lowered = raw.toLowerCase();
|
|
86
|
+
if (OFF_VALUES.has(lowered)) return undefined;
|
|
87
|
+
if (lowered === "auto") return "auto";
|
|
88
|
+
|
|
89
|
+
const tokens = parseAutocompactTokens(raw);
|
|
90
|
+
if (tokens === undefined || tokens < MIN_TOKENS || tokens > MAX_TOKENS) {
|
|
91
|
+
console.warn(
|
|
92
|
+
`[pi-claude-cli] PI_CLAUDE_CLI_AUTOCOMPACT=${JSON.stringify(raw)} is not ` +
|
|
93
|
+
`'auto', 'off', or a window between 100k and 1M — using the default ` +
|
|
94
|
+
`${DEFAULT_AUTOCOMPACT_TOKENS} tokens`,
|
|
95
|
+
);
|
|
96
|
+
return String(DEFAULT_AUTOCOMPACT_TOKENS);
|
|
97
|
+
}
|
|
98
|
+
return String(tokens);
|
|
99
|
+
}
|
package/src/process-manager.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DEFAULT_SYSTEM_PROMPT_MODE,
|
|
17
17
|
type SystemPromptMode,
|
|
18
18
|
} from "./system-prompt-mode.js";
|
|
19
|
+
import { resolveAutocompact } from "./autocompact.js";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Spawn a Claude CLI subprocess with all required flags for stream-json communication.
|
|
@@ -143,6 +144,16 @@ export function spawnClaude(
|
|
|
143
144
|
args.push("--mcp-config", options.mcpConfigPath);
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
// Auto-compact window (PI_CLAUDE_CLI_AUTOCOMPACT, default 200k — see
|
|
148
|
+
// src/autocompact.ts). The CLI does not keep flags across --resume, so
|
|
149
|
+
// like the system prompt this goes on every spawn. Resolved per spawn so
|
|
150
|
+
// a host can change the setting without restarting pi; the flag is
|
|
151
|
+
// config, not context, so changing it never invalidates the prompt cache.
|
|
152
|
+
const autocompact = resolveAutocompact();
|
|
153
|
+
if (autocompact !== undefined) {
|
|
154
|
+
args.push("--autocompact", autocompact);
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
const proc = spawn("claude", args, {
|
|
147
158
|
stdio: ["pipe", "pipe", "pipe"],
|
|
148
159
|
cwd: options?.cwd ?? process.cwd(),
|