@nijaru/pi-provider-compaction 0.0.11 → 0.0.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-provider-compaction
2
2
 
3
- Experimental cache-aware compaction for [Pi](https://github.com/earendil-works/pi), with portable summaries and optional provider-native checkpoints. Minimum runtime: Pi 0.87.0. Development checks target Pi 1.4.0.
3
+ Experimental cache-aware compaction for [Pi](https://github.com/earendil-works/pi), with portable summaries and optional provider-native checkpoints. Minimum runtime: Pi 0.87.0. Development checks target Pi 1.5.0.
4
4
 
5
5
  **Experimental; qualified backend probes are not production validation.** Real runner/adapter fixtures and live DeepSeek, MiMo and Codex probes pass, with the preservation and cache-hit qualifications below. Broader backend and deployment validation remains incomplete.
6
6
 
@@ -39,15 +39,14 @@ The summary request extends the captured logical prompt, rather than serializing
39
39
  ```json
40
40
  {
41
41
  "providerCompaction": {
42
- "cachedSummaries": true,
43
- "outcomeNotifications": false
42
+ "cachedSummaries": true
44
43
  }
45
44
  }
46
45
  ```
47
46
 
48
47
  These are the defaults on hosts exposing Pi's `getSettings` API; older hosts retain native compaction with cached summaries disabled. Optional `cacheRetention` accepts `"short"`, `"long"`, or `"none"`; omission inherits Pi/provider behavior. Retention is applied per request, not through process-wide environment changes. A longer tier can increase cache-write charges and only works where the adapter/backend supports it.
49
48
 
50
- A cache hit is best effort, not a promised saving. Countdown estimates use the model's declared `promptCache` lifetime for the active tier, measured conservatively from request dispatch. Missing lifetimes remain unknown: DeepSeek, for example, [documents best-effort eviction over hours to days](https://api-docs.deepseek.com/guides/kv_cache/), not a predictable expiry. Pi's successful warmer requests refresh the observed cycle and countdown.
49
+ A cache hit is best effort, not a promised saving. Expiry estimates use the model's declared `promptCache` lifetime for the active tier, measured conservatively from request dispatch. Missing lifetimes remain unknown: DeepSeek, for example, [documents best-effort eviction over hours to days](https://api-docs.deepseek.com/guides/kv_cache/), not a predictable expiry. Pi's successful warmer requests refresh the observed cycle and estimated expiry.
51
50
 
52
51
  With [pi-compactor's opt-in idle expiry policy](https://github.com/nijaru/pi-compactor#cache-aware-idle-compaction), compaction can run before an estimated expiry. Both extensions share a live ownership/input guard, checked before paid summary and native-checkpoint stages regardless of extension order. A failed or revoked cached-only attempt cancels instead of starting an ordinary summary. It does not start a continuation.
53
52
 
@@ -77,7 +76,7 @@ Legacy v1/v2 whole-history checkpoints remain inspectable but are never reinterp
77
76
 
78
77
  ## Diagnose portable fallback
79
78
 
80
- Committed compaction entries retain the selected path, elapsed time, reported usage, and estimated cost. Optional outcome notifications show those measurements, not hypothetical savings. Portable fallback appends a `pi-provider-compaction:diagnostic` session entry with a fixed reason. Cancelled attempts with reported usage retain that usage in the same diagnostic record; without a saved checkpoint, Pi's extension API cannot include it in compaction totals. Diagnostics stay out of model context and never include request content, credentials, or backend error text.
79
+ Committed compaction entries retain the selected path, elapsed time, reported usage, and estimated cost. Portable fallback appends a `pi-provider-compaction:diagnostic` session entry with a fixed reason. Cancelled attempts with reported usage retain that usage in the same diagnostic record; without a saved checkpoint, Pi's extension API cannot include it in compaction totals. Diagnostics stay out of model context and never include request content, credentials, or backend error text.
81
80
 
82
81
  These records describe attempts made while the extension was loaded; they cannot explain earlier compactions retroactively. A committed native checkpoint does not by itself prove that a later request replayed it.
83
82
 
@@ -102,7 +101,7 @@ pi --extension ./dist/index.js
102
101
 
103
102
  The manifest loads the built `dist/index.js`, including a bundled stateless Responses serializer validator; source or host dependency changes require a rebuild. `bun run check` rebuilds first and tests the artifact against Pi's actual host serializer, including function-call history replayed as grammar tools. Keep global installation disabled while testing. Publishing and global activation are separate approval steps.
104
103
 
105
- Tests include real Pi 1.4.0 runner/adapter fixtures for all three protocols, repeated prefix compaction, transformed suffix preservation, unchanged fallback, grammar tools, strict defaults, tool IDs, images, prompt updates, cross-model thinking and mid-conversation effort changes. Set `PI_COMPACTOR_FIXTURE_PATH` to a `pi-compactor` entrypoint to check SDK flag precedence and real idle-expiry transactions in both extension orders. Cached-summary fixtures also exercise built-in DeepSeek/Xiaomi dispatch, Anthropic Messages, late tool output, truncated-output fallback and measured usage. The optional installed-fast-mode fixture (`PI_FAST_MODE_FIXTURE_PATH`) checks actual `pi-fast-mode` composition across `/reload`; it creates an isolated fast-mode configuration. `PI_PEAK_HOURS_FIXTURE_PATH` adds request-wide pricing checks in both extension orders, including warming, branch summaries, mixed-API dispatch and composition with `/fast`.
104
+ Tests include real Pi 1.5.0 runner/adapter fixtures for all three protocols, repeated prefix compaction, transformed suffix preservation, unchanged fallback, grammar tools, strict defaults, tool IDs, images, prompt updates, cross-model thinking and mid-conversation effort changes. Set `PI_COMPACTOR_FIXTURE_PATH` to a `pi-compactor` entrypoint to check SDK flag precedence and real idle-expiry transactions in both extension orders. Cached-summary fixtures also exercise built-in DeepSeek/Xiaomi dispatch, Anthropic Messages, late tool output, truncated-output fallback and measured usage. The optional installed-fast-mode fixture (`PI_FAST_MODE_FIXTURE_PATH`) checks actual `pi-fast-mode` composition across `/reload`; it creates an isolated fast-mode configuration. `PI_PEAK_HOURS_FIXTURE_PATH` adds request-wide pricing checks in both extension orders, including warming, branch summaries, mixed-API dispatch and composition with `/fast`.
106
105
 
107
106
  Authenticated cached-summary smoke tests use synthetic history, including a value beyond the ordinary tool-result excerpt, and check continuation:
108
107
 
package/cached-summary.ts CHANGED
@@ -17,13 +17,12 @@ export interface CacheState {
17
17
  cachedTokens: number;
18
18
  inputTokens: number;
19
19
  }
20
- export interface CacheConfig { cachedSummaries: boolean; cacheRetention?: CacheRetention; outcomeNotifications: boolean }
20
+ export interface CacheConfig { cachedSummaries: boolean; cacheRetention?: CacheRetention }
21
21
  export function cacheConfig(settings: unknown): CacheConfig {
22
22
  const raw = isObject(settings) && isObject(settings.providerCompaction) ? settings.providerCompaction : {};
23
23
  return {
24
24
  cachedSummaries: raw.cachedSummaries !== false,
25
25
  cacheRetention: raw.cacheRetention === "long" || raw.cacheRetention === "short" || raw.cacheRetention === "none" ? raw.cacheRetention : undefined,
26
- outcomeNotifications: raw.outcomeNotifications === true,
27
26
  };
28
27
  }
29
28
  export function supportsCachedSummary(model: Model<any> | undefined): boolean {
package/dist/index.js CHANGED
@@ -1591,8 +1591,7 @@ function cacheConfig(settings) {
1591
1591
  const raw = isObject(settings) && isObject(settings.providerCompaction) ? settings.providerCompaction : {};
1592
1592
  return {
1593
1593
  cachedSummaries: raw.cachedSummaries !== false,
1594
- cacheRetention: raw.cacheRetention === "long" || raw.cacheRetention === "short" || raw.cacheRetention === "none" ? raw.cacheRetention : undefined,
1595
- outcomeNotifications: raw.outcomeNotifications === true
1594
+ cacheRetention: raw.cacheRetention === "long" || raw.cacheRetention === "short" || raw.cacheRetention === "none" ? raw.cacheRetention : undefined
1596
1595
  };
1597
1596
  }
1598
1597
  function supportsCachedSummary(model) {
@@ -2246,6 +2245,7 @@ function combineUsage(first, second) {
2246
2245
  result[key] = (first[key] ?? 0) + (second[key] ?? 0);
2247
2246
  for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"])
2248
2247
  result.cost[key] += second.cost[key];
2248
+ result.cost.totalFromProvider = first.cost.totalFromProvider === true && second.cost.totalFromProvider === true;
2249
2249
  return result;
2250
2250
  }
2251
2251
  function supportedRouteOptions(model, options) {
@@ -2257,7 +2257,7 @@ function supportedRouteOptions(model, options) {
2257
2257
  return !["AZURE_OPENAI_BASE_URL", "AZURE_OPENAI_RESOURCE_NAME", "AZURE_OPENAI_API_VERSION", "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"].some((key) => process.env[key] !== undefined && options.env?.[key] === undefined);
2258
2258
  }
2259
2259
  function pi_provider_compaction_default(pi) {
2260
- const configuration = () => typeof pi.getSettings === "function" ? cacheConfig(pi.getSettings()) : { cachedSummaries: false, outcomeNotifications: false, cacheRetention: undefined };
2260
+ const configuration = () => typeof pi.getSettings === "function" ? cacheConfig(pi.getSettings()) : { cachedSummaries: false, cacheRetention: undefined };
2261
2261
  const secret = randomBytes(32);
2262
2262
  const routeDigest = (value) => createHmac("sha256", secret).update(JSON.stringify(value)).digest("hex");
2263
2263
  let runtimeId = randomUUID2();
@@ -2705,10 +2705,6 @@ function pi_provider_compaction_default(pi) {
2705
2705
  const owned = isObject(recorded) && equal(recorded, lastOutcome);
2706
2706
  if (!owned && !fallbackReason && readNativeCompactionDetails(event.compactionEntry.details)?.version !== 3)
2707
2707
  fallback("another handler supplied the committed result");
2708
- if (owned) {
2709
- if (configuration().outcomeNotifications && lastOutcome)
2710
- context.ui.notify(`Compacted via ${lastOutcome.path} in ${(lastOutcome.elapsedMs / 1000).toFixed(1)}s${lastOutcome.usage ? `; cache read ${lastOutcome.usage.cacheRead}, write ${lastOutcome.usage.cacheWrite}; estimated cost $${lastOutcome.usage.cost.total.toFixed(6)}` : ""}`, "info");
2711
- }
2712
2708
  if (fallbackReason)
2713
2709
  pi.appendEntry("pi-provider-compaction:diagnostic", { status: `portable: ${fallbackReason}` });
2714
2710
  active = undefined;
package/index.ts CHANGED
@@ -112,6 +112,7 @@ function combineUsage(first: Usage | undefined, second: Usage | undefined): Usag
112
112
  for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"] as const) result[key] += second[key];
113
113
  for (const key of ["reasoning", "cacheWrite1h"] as const) if (first[key] !== undefined || second[key] !== undefined) result[key] = (first[key] ?? 0) + (second[key] ?? 0);
114
114
  for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"] as const) result.cost[key] += second.cost[key];
115
+ result.cost.totalFromProvider = first.cost.totalFromProvider === true && second.cost.totalFromProvider === true;
115
116
  return result;
116
117
  }
117
118
 
@@ -157,7 +158,7 @@ export default function (pi: ExtensionAPI): void {
157
158
  // hook/privacy policy, even if the visible summary text happens to match.
158
159
  // Older hosts in the native adapter's supported range lack getSettings.
159
160
  // Keep their existing native path; cache preferences require that host API.
160
- const configuration = () => typeof pi.getSettings === "function" ? cacheConfig(pi.getSettings()) : { cachedSummaries: false, outcomeNotifications: false, cacheRetention: undefined };
161
+ const configuration = () => typeof pi.getSettings === "function" ? cacheConfig(pi.getSettings()) : { cachedSummaries: false, cacheRetention: undefined };
161
162
  const secret = randomBytes(32);
162
163
  const routeDigest = (value: unknown) => createHmac("sha256", secret).update(JSON.stringify(value)).digest("hex");
163
164
  let runtimeId = randomUUID();
@@ -493,9 +494,6 @@ export default function (pi: ExtensionAPI): void {
493
494
  const recorded = isObject(event.compactionEntry.details) ? event.compactionEntry.details.providerCompaction : undefined;
494
495
  const owned = isObject(recorded) && equal(recorded, lastOutcome);
495
496
  if (!owned && !fallbackReason && readNativeCompactionDetails(event.compactionEntry.details)?.version !== 3) fallback("another handler supplied the committed result");
496
- if (owned) {
497
- if (configuration().outcomeNotifications && lastOutcome) context.ui.notify(`Compacted via ${lastOutcome.path} in ${(lastOutcome.elapsedMs / 1000).toFixed(1)}s${lastOutcome.usage ? `; cache read ${lastOutcome.usage.cacheRead}, write ${lastOutcome.usage.cacheWrite}; estimated cost $${lastOutcome.usage.cost.total.toFixed(6)}` : ""}`, "info");
498
- }
499
497
  // Only static diagnostics are persisted, never request/auth data or error text.
500
498
  if (fallbackReason) pi.appendEntry("pi-provider-compaction:diagnostic", { status: `portable: ${fallbackReason}` });
501
499
  active = undefined; snapshot = undefined; cache = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nijaru/pi-provider-compaction",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "description": "Provider-native context compaction adapters for Pi",
6
6
  "keywords": [
@@ -46,11 +46,12 @@
46
46
  "LICENSE"
47
47
  ],
48
48
  "devDependencies": {
49
- "@earendil-works/pi-ai": "1.4.0",
50
- "@earendil-works/pi-agent-core": "1.4.0",
51
- "@earendil-works/pi-coding-agent": "1.4.0",
49
+ "@earendil-works/pi-ai": "1.5.0",
50
+ "@earendil-works/pi-agent-core": "1.5.0",
51
+ "@earendil-works/pi-coding-agent": "1.5.0",
52
52
  "@types/bun": "latest",
53
53
  "oxlint": "^1.81.0",
54
+ "typebox": "^1.3.0",
54
55
  "typescript": "^6.0.3"
55
56
  },
56
57
  "peerDependencies": {