pi-zip 0.2.0 → 0.2.1
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 -0
- package/package.json +1 -1
- package/src/index.ts +19 -2
- package/src/placeholder.ts +6 -3
- package/src/plan.ts +3 -2
- package/src/run.ts +8 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Known limits:
|
|
|
29
29
|
|
|
30
30
|
- **GLM** (automatic prefix cache that outlives its declared 5 minutes): v0.1 cost about 1.2× BC live. v0.2 learns the real cache lifetime and folds the previous turn after the declared TTL (offline 0.95–0.97× v0.1), but this was not verified live.
|
|
31
31
|
- **Long autonomous runs** (one prompt, hundreds of tool calls, e.g. sub-agents): roughly on par with BC, not better. Outputs are only folded inside a running turn once they are 60 requests old.
|
|
32
|
+
- **Tool allowlists** (`pi --tools read,bash`, and sub-agent launchers that pass one): Pi then hides `zip_recall`, so pi-zip folds only outputs the model can re-read (files, read-only commands) and the placeholder says to re-read. Add `zip_recall` to the list to get full folding.
|
|
32
33
|
- If you always answer within the cache lifetime, there is little to save, by design.
|
|
33
34
|
|
|
34
35
|
## What you see
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-zip",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Keeps long Pi sessions cheap without losing anything: folds old tool output only when the prompt cache has already gone cold, and every fold can be recalled byte for byte. Zero config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// pi-zip: keeps long Pi sessions cheap without losing anything. Wiring only; the logic lives in the sibling modules.
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { registerZipCommand } from "./notice.ts";
|
|
4
|
+
import { RECALL_TOOL } from "./placeholder.ts";
|
|
4
5
|
import { registerRecallTool } from "./recall.ts";
|
|
5
6
|
import { Zip } from "./run.ts";
|
|
6
7
|
|
|
@@ -9,8 +10,24 @@ export default function piZip(pi: ExtensionAPI) {
|
|
|
9
10
|
const zip = new Zip(pi);
|
|
10
11
|
registerRecallTool(pi, zip); // stays registered even when off: earlier folds must stay recallable, and a tool-list change would bust the cache
|
|
11
12
|
registerZipCommand(pi, zip);
|
|
12
|
-
pi
|
|
13
|
-
|
|
13
|
+
// A tool allowlist (`pi --tools read,bash`, sub-agent launchers) replaces the whole selection and Pi then does not even register
|
|
14
|
+
// zip_recall; folds of outputs the model could not get back would be lost, so only rereadable outputs fold then (plan rereadOnly).
|
|
15
|
+
const checkRecall = () => {
|
|
16
|
+
try {
|
|
17
|
+
const active = pi.getActiveTools?.();
|
|
18
|
+
if (Array.isArray(active)) zip.setRecallOk(active.includes(RECALL_TOOL));
|
|
19
|
+
} catch {
|
|
20
|
+
/* never in the way */
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
pi.on("session_start", (_e, ctx) => {
|
|
24
|
+
checkRecall();
|
|
25
|
+
return zip.sessionStart(ctx);
|
|
26
|
+
});
|
|
27
|
+
pi.on("before_agent_start", (_e, ctx) => {
|
|
28
|
+
checkRecall();
|
|
29
|
+
return zip.beforeAgentStart(ctx);
|
|
30
|
+
});
|
|
14
31
|
pi.on("context_with_system", (e, ctx) => zip.context(e, ctx)); // the complete transcript: system messages stay where Pi put them
|
|
15
32
|
pi.on("turn_end", (e, ctx) => zip.turnEnd(e, ctx));
|
|
16
33
|
pi.on("agent_before_settle", (e, ctx) => zip.settle(e, ctx));
|
package/src/placeholder.ts
CHANGED
|
@@ -108,6 +108,7 @@ export function outcomeHint(text: string, tool: string, isError?: boolean): stri
|
|
|
108
108
|
export interface PlaceholderMeta {
|
|
109
109
|
turn?: number; // user turn the output belongs to (1-based, as seen in the projection when it was folded)
|
|
110
110
|
isError?: boolean;
|
|
111
|
+
noRecall?: boolean; // zip_recall is not declared to the model (`--tools` allowlist): point at re-reading instead
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
/** Placeholder text, or null when the output is too short to be worth folding. The text is stored with the fold (context_edit),
|
|
@@ -121,15 +122,17 @@ export function makePlaceholder(text: string, tool: string, args: string, handle
|
|
|
121
122
|
return (
|
|
122
123
|
`${PH_MARK} · ${tool}${args ? " " + args : ""}${meta.turn ? ` · turn ${meta.turn}` : ""}${outcome ? ` · ${outcome}` : ""} · ${text.length} chars, ${lines} lines · handle ${handle}]\n` +
|
|
123
124
|
(keys.length ? `key lines kept (original line numbers; up to ${keep}):\n${body}\n` : "") +
|
|
124
|
-
|
|
125
|
+
(!meta.noRecall
|
|
126
|
+
? `Original kept byte for byte, recallable even after summaries or compaction: ${RECALL_TOOL}("${handle}") (optional grep/range) is instant, free, no side effects; prefer it to re-running or re-reading (output may differ). Do not guess its content.`
|
|
127
|
+
: `Original not shown (${RECALL_TOOL} is not enabled in this session): re-read the file or re-run the read-only command if you need it. Do not guess its content.`)
|
|
125
128
|
);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
/** Placeholder for a toolResult block (null for image results, entries without an id, or short outputs). */
|
|
129
|
-
export function makePlaceholderFor(b: Block, calls: Calls, keep = 8): string | null {
|
|
132
|
+
export function makePlaceholderFor(b: Block, calls: Calls, keep = 8, noRecall = false): string | null {
|
|
130
133
|
const content = b.msg.content;
|
|
131
134
|
if (Array.isArray(content) && content.some((c: Any) => c?.type !== "text")) return null;
|
|
132
135
|
if (!b.entryId) return null;
|
|
133
136
|
const call = calls.get(b.msg.toolCallId);
|
|
134
|
-
return makePlaceholder(textOf(content), call?.name ?? b.msg.toolName ?? "tool", call ? shortArgs(call.args) : "", handleFor(b.entryId), keep, { turn: b.userTurn, isError: !!b.msg.isError });
|
|
137
|
+
return makePlaceholder(textOf(content), call?.name ?? b.msg.toolName ?? "tool", call ? shortArgs(call.args) : "", handleFor(b.entryId), keep, { turn: b.userTurn, isError: !!b.msg.isError, noRecall });
|
|
135
138
|
}
|
package/src/plan.ts
CHANGED
|
@@ -282,6 +282,7 @@ export interface PlanOpts {
|
|
|
282
282
|
relax?: boolean; // default RELAX_PREV_TURN
|
|
283
283
|
minGain?: number;
|
|
284
284
|
inturnAge?: number; // default settings().inturnAge (PI_ZIP_INTURN_AGE, 60); 0 = outputs of the protected turns never fold on age
|
|
285
|
+
rereadOnly?: boolean; // zip_recall is not available to the model: fold only outputs that can be re-read (classify "rereadable")
|
|
285
286
|
pastTtl?: boolean; // a user return after the declared TTL: a warm plan may relax into the previous user turn too (RELAX_PREV_TURN)
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -366,7 +367,7 @@ export function planContext(entries: Any[], o: PlanOpts): PlanResult | null {
|
|
|
366
367
|
};
|
|
367
368
|
const addFold = (b: Block, trig: string): boolean => {
|
|
368
369
|
if (tokOverride.has(b.idx) || recalled.has(handleFor(b.entryId!))) return false; // F4: never refold what the model recalled
|
|
369
|
-
const ph = makePlaceholderFor(b, calls, keepLines);
|
|
370
|
+
const ph = makePlaceholderFor(b, calls, keepLines, !!o.rereadOnly);
|
|
370
371
|
if (ph === null) return false;
|
|
371
372
|
const phTok = tok4(ph);
|
|
372
373
|
if (!(phTok < 0.9 * b.tokens)) return false;
|
|
@@ -384,7 +385,7 @@ export function planContext(entries: Any[], o: PlanOpts): PlanResult | null {
|
|
|
384
385
|
const head = law.pr?.cls === "automatic" ? MIN_EXPECT / k : 0;
|
|
385
386
|
const start: number[] = [];
|
|
386
387
|
blocks.reduce((acc, b) => ((start[b.idx] = acc), acc + b.tokens), o.sys);
|
|
387
|
-
const foldable = (b: Block) => b.kind === "toolResult" && !b.edited && !!b.entryId && b.tokens > foldMin && !isRecall(b) && start[b.idx] >= head;
|
|
388
|
+
const foldable = (b: Block) => b.kind === "toolResult" && !b.edited && !!b.entryId && b.tokens > foldMin && !isRecall(b) && start[b.idx] >= head && (!o.rereadOnly || classify(b) === "rereadable");
|
|
388
389
|
const protectedTurn = (b: Block) => b.userTurn >= userTurns - PROTECT_USER_TURNS + 1;
|
|
389
390
|
const savings = () => folds.reduce((a, t) => a + t.entryTokens - t.phTokens, 0);
|
|
390
391
|
const cands = blocks.filter((b) => foldable(b) && !protectedTurn(b));
|
package/src/run.ts
CHANGED
|
@@ -92,6 +92,13 @@ export class Zip implements ZipControl {
|
|
|
92
92
|
|
|
93
93
|
active = () => !this.off && !this.conflict;
|
|
94
94
|
|
|
95
|
+
/** zip_recall declared to the model? Checked at session start and before every run (index.ts); a `--tools` allowlist hides it. */
|
|
96
|
+
recallOk = true;
|
|
97
|
+
setRecallOk(ok: boolean) {
|
|
98
|
+
if (ok !== this.recallOk) this.ledger({ type: "recall_available", ok });
|
|
99
|
+
this.recallOk = ok;
|
|
100
|
+
}
|
|
101
|
+
|
|
95
102
|
ledger(rec: Record<string, unknown>) {
|
|
96
103
|
const path = process.env.PI_ZIP_LEDGER; // test only
|
|
97
104
|
if (!path) return;
|
|
@@ -139,7 +146,7 @@ export class Zip implements ZipControl {
|
|
|
139
146
|
private opts(ctx: Any, pending: boolean, mode: "cold" | "warm", entries: Any[], pWarm = mode === "cold" ? 0 : 1): { o: PlanOpts; cal: Calibration } {
|
|
140
147
|
const sys = this.sysTokens(ctx);
|
|
141
148
|
const cal = calibrate(entries, sys);
|
|
142
|
-
return { cal, o: { mode, sys, k: cal.k, cwd: (ctx?.cwd as string) ?? process.cwd(), recalled: this.recalled, model: ctx?.model, promptPending: pending, reserve: this.reserve(ctx), steerIds: this.steerIds, law: this.law(ctx, pWarm), trace: [] } };
|
|
149
|
+
return { cal, o: { mode, sys, k: cal.k, cwd: (ctx?.cwd as string) ?? process.cwd(), recalled: this.recalled, model: ctx?.model, promptPending: pending, rereadOnly: !this.recallOk, reserve: this.reserve(ctx), steerIds: this.steerIds, law: this.law(ctx, pWarm), trace: [] } };
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
private law(ctx: Any, pWarm: number): Law {
|