@nijaru/pi-provider-compaction 0.0.9 → 0.0.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/dist/index.js CHANGED
@@ -16,7 +16,6 @@ var COMPACTION_MODEL_FLAG = "compaction-model";
16
16
  var PROJECT_POLICY = "compaction-policy.json";
17
17
  var AGENT_DIR_OVERRIDE = "PI_COMPACTOR_AGENT_DIR";
18
18
  var MAX_POLICY_BYTES = 64 * 1024;
19
- var MAX_MODEL_SELECTORS = 8;
20
19
  var MAX_SELECTOR_LENGTH = 512;
21
20
  function normalizeSelector(value) {
22
21
  if (typeof value !== "string")
@@ -26,23 +25,14 @@ function normalizeSelector(value) {
26
25
  return;
27
26
  return selector;
28
27
  }
29
- function parsePolicySelectors(value) {
28
+ function policyHasSelectors(value) {
30
29
  if (typeof value !== "object" || value === null || Array.isArray(value))
31
30
  return;
32
31
  if (!("models" in value) || !Array.isArray(value.models))
33
32
  return;
34
- const selectors = [];
35
- for (const model of value.models) {
36
- const selector = normalizeSelector(model);
37
- if (selector === undefined || selectors.includes(selector))
38
- continue;
39
- selectors.push(selector);
40
- if (selectors.length === MAX_MODEL_SELECTORS)
41
- break;
42
- }
43
- return selectors;
33
+ return value.models.some((model) => normalizeSelector(model) !== undefined);
44
34
  }
45
- function readPolicySelectors(configPath) {
35
+ function readPolicySelection(configPath) {
46
36
  let fd;
47
37
  try {
48
38
  fd = openSync(configPath, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
@@ -59,7 +49,7 @@ function readPolicySelectors(configPath) {
59
49
  }
60
50
  if (bytesRead > MAX_POLICY_BYTES)
61
51
  return;
62
- return parsePolicySelectors(JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")));
52
+ return policyHasSelectors(JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")));
63
53
  } catch {
64
54
  return;
65
55
  } finally {
@@ -141,9 +131,9 @@ function resolveCompactionModelPolicy(pi, ctx, argv = process.argv.slice(2)) {
141
131
  }
142
132
  candidates.push({ path: join(agentDir, PROJECT_POLICY), source: "agent-policy" });
143
133
  for (const candidate of candidates) {
144
- const selectors = readPolicySelectors(candidate.path);
145
- if (selectors !== undefined) {
146
- return selectors.length > 0 ? { hasSelectors: true, source: candidate.source } : { hasSelectors: false };
134
+ const hasSelectors = readPolicySelection(candidate.path);
135
+ if (hasSelectors !== undefined) {
136
+ return hasSelectors ? { hasSelectors: true, source: candidate.source } : { hasSelectors: false };
147
137
  }
148
138
  }
149
139
  return { hasSelectors: false };
@@ -1170,7 +1160,7 @@ async function collectProviderUsage(stream, signal) {
1170
1160
  if (signal.aborted)
1171
1161
  throw new DOMException("Compaction aborted", "AbortError");
1172
1162
  if (event.type === "error")
1173
- throw new Error(event.error.errorMessage ?? "Native compaction provider request failed");
1163
+ throw new NativeCompactionValidationError("Native compaction provider stream failed", event.error.usage);
1174
1164
  if (event.type === "done")
1175
1165
  usage = event.message.usage;
1176
1166
  }
@@ -1216,14 +1206,15 @@ async function requestResponsesCompact(request) {
1216
1206
  if (!response.ok)
1217
1207
  return response;
1218
1208
  const parsed = await readJsonBounded(response, request.signal);
1209
+ if (!isObject(parsed))
1210
+ throw new Error("Responses Compact returned a non-object response");
1211
+ const completion = syntheticCompletion(parsed, preparedPayload);
1219
1212
  try {
1220
- if (!isObject(parsed))
1221
- throw new Error("Responses Compact returned a non-object response");
1222
- compacted = { response: parsed, output: validateCompactedOutput(parsed) };
1213
+ compacted = validateCompactedOutput(parsed);
1223
1214
  } catch (error) {
1224
- throw new NativeCompactionValidationError(error instanceof Error ? error.message : String(error));
1215
+ bridgeError = new NativeCompactionValidationError(error instanceof Error ? error.message : String(error));
1225
1216
  }
1226
- return syntheticCompletion(parsed, preparedPayload);
1217
+ return completion;
1227
1218
  } catch (error) {
1228
1219
  bridgeError = error;
1229
1220
  return errorResponse(error);
@@ -1238,6 +1229,7 @@ async function requestResponsesCompact(request) {
1238
1229
  transport: "sse",
1239
1230
  cacheRetention: "none",
1240
1231
  maxRetries: 0,
1232
+ sessionId: request.sessionId,
1241
1233
  fetch: bridgeFetch,
1242
1234
  onPayload: (payload) => {
1243
1235
  assertCurrent(request);
@@ -1251,7 +1243,7 @@ async function requestResponsesCompact(request) {
1251
1243
  });
1252
1244
  const usage = await collectProviderUsage(stream, request.signal).catch((error) => {
1253
1245
  if (bridgeError)
1254
- throw bridgeError;
1246
+ throw new NativeCompactionValidationError("Native compaction transport or validation failed", error instanceof NativeCompactionValidationError ? error.usage : undefined);
1255
1247
  throw error;
1256
1248
  });
1257
1249
  if (bridgeError instanceof NativeCompactionValidationError)
@@ -1260,7 +1252,7 @@ async function requestResponsesCompact(request) {
1260
1252
  throw bridgeError;
1261
1253
  if (!compacted || dispatches !== 1)
1262
1254
  throw new Error("Responses Compact did not complete exactly one request");
1263
- return { output: compacted.output, usage };
1255
+ return { output: compacted, usage };
1264
1256
  }
1265
1257
  async function collectCompactionSse(stream, signal) {
1266
1258
  const reader = stream.getReader();
@@ -1405,6 +1397,7 @@ async function requestRemoteV2(request) {
1405
1397
  transport: "sse",
1406
1398
  cacheRetention: "none",
1407
1399
  maxRetries: 0,
1400
+ sessionId: request.sessionId,
1408
1401
  fetch: inspectedFetch,
1409
1402
  onPayload: (payload) => {
1410
1403
  assertCurrent(request);
@@ -1419,10 +1412,10 @@ async function requestRemoteV2(request) {
1419
1412
  try {
1420
1413
  const usage = await collectProviderUsage(stream, request.signal);
1421
1414
  if (!sentInput || inspections.length !== 1)
1422
- throw new Error(`Remote V2 observed ${inspections.length} successful provider responses; expected exactly one`);
1415
+ throw new NativeCompactionValidationError(`Remote V2 observed ${inspections.length} successful provider responses; expected exactly one`, usage);
1423
1416
  const inspection = await inspections[0];
1424
1417
  if ("error" in inspection)
1425
- throw inspection.error;
1418
+ throw new NativeCompactionValidationError("Remote V2 checkpoint validation failed", usage);
1426
1419
  return { output: buildRemoteV2History(sentInput, inspection.result.item), usage };
1427
1420
  } finally {
1428
1421
  controller.abort();
@@ -1825,17 +1818,26 @@ async function cachedSummary(request, context, preparation, instructions, signal
1825
1818
  }
1826
1819
 
1827
1820
  // provider.ts
1821
+ import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
1822
+ var USAGE_OWNERS = Symbol.for("@nijaru/pi-provider-compaction/usage-owners");
1823
+ var usageGlobal = globalThis;
1824
+ var usageOwners = usageGlobal[USAGE_OWNERS] ??= new WeakMap;
1825
+ function bindUsageHeaders(headers, sessionId) {
1826
+ usageOwners.set(headers, sessionId);
1827
+ }
1828
1828
  var STREAM_OBSERVER = Symbol.for("@nijaru/pi-provider-compaction/stream-observer");
1829
1829
  function observedStream(model, context, options, observe, dispatch) {
1830
+ const headerOwner = options?.headers ? usageOwners.get(options.headers) : undefined;
1831
+ const scope = { sessionId: headerOwner ?? options?.sessionId };
1830
1832
  let handler;
1831
1833
  try {
1832
- handler = observe(model, context, options ?? {});
1834
+ handler = observe(model, context, options ?? {}, scope);
1833
1835
  } catch {
1834
1836
  return dispatch(options);
1835
1837
  }
1836
1838
  if (!handler)
1837
1839
  return dispatch(options);
1838
- const stream = dispatch({
1840
+ const run = () => dispatch({
1839
1841
  ...options,
1840
1842
  ...handler.cacheRetention === undefined ? {} : { cacheRetention: handler.cacheRetention },
1841
1843
  onPayload: async (payload, requestModel) => {
@@ -1848,14 +1850,64 @@ function observedStream(model, context, options, observe, dispatch) {
1848
1850
  }
1849
1851
  }
1850
1852
  });
1853
+ const stream = run();
1854
+ if (handler.priceUsage)
1855
+ return pricedStream(stream, model, handler);
1851
1856
  if (handler.complete)
1852
1857
  stream.result().then(handler.complete).catch(() => {});
1853
1858
  return stream;
1854
1859
  }
1860
+ function pricedStream(stream, model, handler) {
1861
+ const out = createAssistantMessageEventStream();
1862
+ (async () => {
1863
+ let final;
1864
+ let partial;
1865
+ try {
1866
+ for await (const event of stream) {
1867
+ if ("partial" in event)
1868
+ partial = event.partial;
1869
+ if (event.type === "done" || event.type === "error") {
1870
+ final = event.type === "done" ? event.message : event.error;
1871
+ try {
1872
+ handler.priceUsage?.(final);
1873
+ } catch {}
1874
+ try {
1875
+ handler.complete?.(final);
1876
+ } catch {}
1877
+ }
1878
+ out.push(event);
1879
+ }
1880
+ if (final) {
1881
+ out.end(final);
1882
+ return;
1883
+ }
1884
+ } catch {}
1885
+ const message = {
1886
+ ...partial ?? {
1887
+ role: "assistant",
1888
+ api: model.api,
1889
+ provider: model.provider,
1890
+ model: model.id,
1891
+ content: [],
1892
+ timestamp: Date.now(),
1893
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
1894
+ },
1895
+ stopReason: "error",
1896
+ errorMessage: "Provider stream ended before a terminal event"
1897
+ };
1898
+ try {
1899
+ handler.priceUsage?.(message);
1900
+ } catch {}
1901
+ out.push({ type: "error", reason: "error", error: message });
1902
+ out.end(message);
1903
+ })();
1904
+ return out;
1905
+ }
1855
1906
  function observerLeases(provider, owned, restore) {
1856
1907
  const leases = new Map;
1857
1908
  let closed = false;
1858
- const active = (token) => !closed && leases.get(token)?.current() === true && owned();
1909
+ const live = (token) => !closed && leases.has(token);
1910
+ const active = (token) => live(token) && leases.get(token)?.current() === true && owned();
1859
1911
  const release = () => {
1860
1912
  if (closed)
1861
1913
  return;
@@ -1874,6 +1926,10 @@ function observerLeases(provider, owned, restore) {
1874
1926
  continue;
1875
1927
  handlers.push(Object.assign((payload) => active(token) ? handler(payload) : payload, {
1876
1928
  cacheRetention: handler.cacheRetention,
1929
+ priceUsage: handler.priceUsage ? (message) => {
1930
+ if (live(token))
1931
+ handler.priceUsage(message);
1932
+ } : undefined,
1877
1933
  complete: handler.complete ? (message) => {
1878
1934
  if (active(token))
1879
1935
  handler.complete(message);
@@ -1894,6 +1950,13 @@ function observerLeases(provider, owned, restore) {
1894
1950
  return payload;
1895
1951
  }, {
1896
1952
  cacheRetention: [...handlers].reverse().find((handler) => handler.cacheRetention !== undefined)?.cacheRetention,
1953
+ priceUsage: handlers.some((handler) => handler.priceUsage) ? (message) => {
1954
+ for (const handler of handlers) {
1955
+ try {
1956
+ handler.priceUsage?.(message);
1957
+ } catch {}
1958
+ }
1959
+ } : undefined,
1897
1960
  complete: handlers.some((handler) => handler.complete) ? (message) => {
1898
1961
  for (const handler of handlers) {
1899
1962
  try {
@@ -1918,7 +1981,7 @@ function observerLeases(provider, owned, restore) {
1918
1981
  }
1919
1982
  };
1920
1983
  }
1921
- var LEASE_TABLES = Symbol.for("@nijaru/pi-provider-compaction/observer-leases-v1");
1984
+ var LEASE_TABLES = Symbol.for("@nijaru/pi-provider-compaction/observer-leases-v2");
1922
1985
  var shared = globalThis;
1923
1986
  var { effectiveLeases, effectiveRestores, nativeLeases, legacyLeases } = shared[LEASE_TABLES] ??= {
1924
1987
  effectiveLeases: new WeakMap,
@@ -1994,13 +2057,21 @@ function installObserver(pi, ctx, observe) {
1994
2057
  return;
1995
2058
  const registry = ctx.modelRegistry;
1996
2059
  const id = model.provider;
2060
+ const observeApi = (requestModel, ...args) => requestModel.api === model.api ? observe(requestModel, ...args) : undefined;
2061
+ const tagged = (lease) => lease ? Object.assign(lease, { api: model.api }) : undefined;
2062
+ const effective = () => {
2063
+ const base = registry.getProvider(id);
2064
+ if (!base?.getModels().some((entry) => entry.api === model.api))
2065
+ return;
2066
+ return tagged(leaseEffectiveProvider(base, () => registry.getProvider(id), observeApi));
2067
+ };
1997
2068
  const native = registry.getRegisteredNativeProvider(id);
1998
2069
  if (native) {
1999
2070
  if (typeof native.getModels !== "function" || !native.getModels().some((entry) => entry.api === model.api))
2000
- return;
2071
+ return effective();
2001
2072
  const existing = nativeLeases.get(native);
2002
2073
  if (existing)
2003
- return existing.add(observe);
2074
+ return tagged(existing.add(observeApi));
2004
2075
  const leases = observerLeases(id, () => registry.getRegisteredNativeProvider(id) === wrapper, () => {
2005
2076
  if (registry.getRegisteredNativeProvider(id) === wrapper)
2006
2077
  registry.registerProvider(native);
@@ -2009,15 +2080,15 @@ function installObserver(pi, ctx, observe) {
2009
2080
  const wrapper = decorateProvider(native, leases.observe);
2010
2081
  nativeLeases.set(wrapper, leases);
2011
2082
  pi.registerProvider(wrapper);
2012
- return leases.add(observe);
2083
+ return tagged(leases.add(observeApi));
2013
2084
  }
2014
2085
  const original = registry.getRegisteredProviderConfig(id);
2015
2086
  if (!original?.streamSimple || original.refreshModels || original.oauth?.modifyModels || original.api !== model.api) {
2016
- return leaseEffectiveProvider(registry.getProvider(id), () => registry.getProvider(id), observe);
2087
+ return effective();
2017
2088
  }
2018
2089
  const existing = legacyLeases.get(original.streamSimple);
2019
2090
  if (existing)
2020
- return existing.add(observe);
2091
+ return tagged(existing.add(observeApi));
2021
2092
  const delegate = original.streamSimple.bind(original);
2022
2093
  const owned = () => registry.getRegisteredProviderConfig(id)?.streamSimple === wrapper && !registry.getRegisteredNativeProvider(id);
2023
2094
  const leases = observerLeases(id, owned, () => {
@@ -2032,7 +2103,62 @@ function installObserver(pi, ctx, observe) {
2032
2103
  Object.defineProperty(wrapper, STREAM_OBSERVER, { value: { delegate: original.streamSimple, release: leases.release } });
2033
2104
  legacyLeases.set(wrapper, leases);
2034
2105
  pi.registerProvider(id, { ...original, streamSimple: wrapper });
2035
- return leases.add(observe);
2106
+ return tagged(leases.add(observeApi));
2107
+ }
2108
+
2109
+ // usage-observer.ts
2110
+ var REQUEST = "pi-provider-compaction:observe-usage";
2111
+ var READY = "pi-provider-compaction:usage-observer-ready";
2112
+ function serveUsageObservers(pi, current) {
2113
+ const leases = new Set;
2114
+ const remove = pi.events.on(REQUEST, (value) => {
2115
+ const request = value;
2116
+ const owner = current();
2117
+ if (!owner || !request || request.handled || !request.context || !request.model || typeof request.priceFor !== "function" || typeof request.respond !== "function")
2118
+ return;
2119
+ try {
2120
+ if (request.context.modelRegistry !== owner.modelRegistry || request.context.sessionManager.getSessionId() !== owner.sessionManager.getSessionId())
2121
+ return;
2122
+ request.handled = true;
2123
+ const sessionId = owner.sessionManager.getSessionId();
2124
+ const installed = installObserver(pi, { model: request.model, modelRegistry: owner.modelRegistry }, (model, _context, _options, scope) => {
2125
+ const price = request.priceFor({ provider: model.provider, id: model.id });
2126
+ return Object.assign((payload) => payload, { priceUsage: (message) => {
2127
+ if (scope.sessionId === sessionId)
2128
+ price(message.timestamp, message.usage);
2129
+ } });
2130
+ });
2131
+ if (!installed) {
2132
+ request.respond(undefined);
2133
+ return;
2134
+ }
2135
+ const lease = {
2136
+ provider: installed.provider,
2137
+ api: installed.api,
2138
+ isInstalled: () => installed.isInstalled(),
2139
+ remove() {
2140
+ leases.delete(lease);
2141
+ installed.remove();
2142
+ }
2143
+ };
2144
+ leases.add(lease);
2145
+ request.respond(lease);
2146
+ } catch {}
2147
+ });
2148
+ return {
2149
+ ready(context) {
2150
+ pi.events.emit(READY, { context });
2151
+ },
2152
+ clear() {
2153
+ for (const lease of leases)
2154
+ lease.remove();
2155
+ },
2156
+ dispose() {
2157
+ remove();
2158
+ for (const lease of leases)
2159
+ lease.remove();
2160
+ }
2161
+ };
2036
2162
  }
2037
2163
 
2038
2164
  // index.ts
@@ -2136,9 +2262,10 @@ function pi_provider_compaction_default(pi) {
2136
2262
  const routeDigest = (value) => createHmac("sha256", secret).update(JSON.stringify(value)).digest("hex");
2137
2263
  let runtimeId = randomUUID2();
2138
2264
  let ctx;
2265
+ let usageObservers = serveUsageObservers(pi, () => ctx);
2139
2266
  let snapshot;
2140
2267
  let cache;
2141
- let summarizing = false;
2268
+ let compactionRequest = false;
2142
2269
  let expiryAttempt;
2143
2270
  let lastOutcome;
2144
2271
  let active;
@@ -2147,30 +2274,10 @@ function pi_provider_compaction_default(pi) {
2147
2274
  let unregisterCleanup;
2148
2275
  let generation = 0;
2149
2276
  let requestSequence = 0;
2150
- let captureStatus = "no request observed";
2151
- let compactionStatus = "not attempted in this runtime";
2152
2277
  let fallbackReason;
2153
- const captureRejected = (reason) => {
2154
- captureStatus = reason;
2155
- };
2156
2278
  const fallback = (reason) => {
2157
2279
  fallbackReason = reason;
2158
- compactionStatus = `portable: ${reason}`;
2159
2280
  };
2160
- pi.registerCommand("provider-compaction-status", {
2161
- description: "Show cache/native eligibility and measured compaction usage",
2162
- handler: async (_args, context) => {
2163
- const state = cacheState(context);
2164
- const remaining = state.expiresAt === undefined ? "unknown lifetime" : `estimated ${Math.max(0, Math.ceil((state.expiresAt - Date.now()) / 1000))}s remaining (model declaration; best effort)`;
2165
- const usage = lastOutcome?.usage;
2166
- context.ui.notify(`Provider compaction
2167
- Observer: ${observer?.isInstalled() ? "installed" : "unavailable for current registration"}
2168
- Capture: ${captureStatus}
2169
- Cache: ${state.reason}; ${remaining}
2170
- Last attempt: ${compactionStatus}${lastOutcome ? `
2171
- Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usage.input}, cache read ${usage.cacheRead}, write ${usage.cacheWrite}, output ${usage.output}; catalog cost $${usage.cost.total.toFixed(6)}` : "; usage unavailable"}${lastOutcome.reason ? `; ${lastOutcome.reason}` : ""}` : ""}`, "info");
2172
- }
2173
- });
2174
2281
  const invalidate = () => {
2175
2282
  generation++;
2176
2283
  runtimeId = randomUUID2();
@@ -2179,9 +2286,9 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2179
2286
  active = undefined;
2180
2287
  pending = undefined;
2181
2288
  expiryAttempt = undefined;
2182
- captureStatus = "invalidated by lifecycle/history/policy change";
2183
2289
  };
2184
2290
  const retire = () => {
2291
+ usageObservers.clear();
2185
2292
  invalidate();
2186
2293
  const installed = observer;
2187
2294
  observer = undefined;
@@ -2191,6 +2298,10 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2191
2298
  installed?.remove();
2192
2299
  lastOutcome = undefined;
2193
2300
  };
2301
+ pi.on("before_provider_headers", (event, context) => {
2302
+ bindUsageHeaders(event.headers, context.sessionManager.getSessionId());
2303
+ usageObservers.ready(context);
2304
+ });
2194
2305
  const generic = (context) => resolveCompactionModelPolicy(pi, context).hasSelectors;
2195
2306
  const route = (model, options) => routeDigest({ model, apiKey: options.apiKey, headers: options.headers, env: options.env });
2196
2307
  const unchanged = (branch, length, hash) => branch.length >= length && digest(branch.slice(0, length)) === hash && !branch.slice(length).some((entry) => entry.type === "context_edit");
@@ -2262,25 +2373,14 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2262
2373
  }
2263
2374
  if (options.sessionId !== manager.getSessionId())
2264
2375
  return;
2265
- if (summarizing)
2266
- return;
2267
- if (!supportsNativeCompaction(model) && !supportsCachedSummary(model)) {
2268
- captureRejected("unsupported API");
2376
+ if (compactionRequest)
2269
2377
  return;
2270
- }
2271
- if (!options.onPayload) {
2272
- if (captureStatus !== "eligible prepared request captured")
2273
- captureRejected("no payload callback");
2378
+ if (!supportsNativeCompaction(model) && !supportsCachedSummary(model))
2274
2379
  return;
2275
- }
2276
- if (generic(owner)) {
2277
- captureRejected("generic compaction model takes precedence");
2380
+ if (!options.onPayload || generic(owner))
2278
2381
  return;
2279
- }
2280
- if (!supportedRouteOptions(model, options)) {
2281
- captureRejected("unsupported route overrides");
2382
+ if (!supportedRouteOptions(model, options))
2282
2383
  return;
2283
- }
2284
2384
  const ticket = generation;
2285
2385
  const startedAt = Date.now();
2286
2386
  const sequence = ++requestSequence;
@@ -2311,14 +2411,11 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2311
2411
  if (!auth.ok || generic(owner) || auth.apiKey !== options.apiKey || (auth.baseUrl ?? model.baseUrl) !== model.baseUrl || !equal(normalizedHeaders(auth.headers), normalizedHeaders(options.headers)) || !equal(auth.env ?? {}, options.env ?? {})) {
2312
2412
  active = undefined;
2313
2413
  cache = undefined;
2314
- captureRejected("resolved auth/route differs from prepared request, or generic policy changed");
2315
2414
  return payload;
2316
2415
  }
2317
2416
  const provider = owner.modelRegistry.getProvider(model.provider);
2318
- if (!provider) {
2319
- captureRejected("provider unavailable");
2417
+ if (!provider)
2320
2418
  return payload;
2321
- }
2322
2419
  const routeHash = route(model, options);
2323
2420
  if (config.cachedSummaries && supportsCachedSummary(model)) {
2324
2421
  const warming = options.maxTokens === 1 && cache && cache.authHash === routeDigest(auth) && sameCachedPrompt(cache.payload, payload) && equal(cache.context, context);
@@ -2337,11 +2434,9 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2337
2434
  if (!warming)
2338
2435
  cache = undefined;
2339
2436
  }
2340
- if (!supportsNativeCompaction(model)) {
2341
- captureStatus = candidate ? "eligible cached request captured" : "cached request ineligible";
2437
+ if (!supportsNativeCompaction(model))
2342
2438
  return payload;
2343
- }
2344
- const prepared = capturePreparedRequest(model, context, canonical, payload, captureRejected);
2439
+ const prepared = capturePreparedRequest(model, context, canonical, payload);
2345
2440
  if (!prepared) {
2346
2441
  active = undefined;
2347
2442
  return payload;
@@ -2370,7 +2465,6 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2370
2465
  active = undefined;
2371
2466
  } else
2372
2467
  active = undefined;
2373
- captureStatus = "eligible prepared request captured";
2374
2468
  snapshot = {
2375
2469
  prepared,
2376
2470
  model: structuredClone(model),
@@ -2398,7 +2492,7 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2398
2492
  };
2399
2493
  const install = (context) => {
2400
2494
  ctx = context;
2401
- if (observer?.provider === context.model?.provider && observer?.isInstalled())
2495
+ if (observer?.provider === context.model?.provider && observer?.api === context.model?.api && observer?.isInstalled())
2402
2496
  return;
2403
2497
  if (observer) {
2404
2498
  invalidate();
@@ -2408,15 +2502,19 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2408
2502
  if (!supportsNativeCompaction(context.model) && !supportsCachedSummary(context.model))
2409
2503
  return;
2410
2504
  observer = installObserver(pi, context, observe);
2411
- captureStatus = observer ? "awaiting prepared request" : "unsupported provider registration";
2412
2505
  };
2413
2506
  pi.on("session_start", (_event, context) => {
2414
2507
  retire();
2508
+ usageObservers.dispose();
2509
+ usageObservers = serveUsageObservers(pi, () => ctx);
2415
2510
  install(context);
2511
+ usageObservers.ready(context);
2416
2512
  const sessionId = context.sessionManager.getSessionId();
2417
2513
  unregisterCleanup = registerSessionResourceCleanup((disposedId) => {
2418
- if (disposedId === undefined || disposedId === sessionId)
2514
+ if (disposedId === undefined || disposedId === sessionId) {
2419
2515
  retire();
2516
+ usageObservers.dispose();
2517
+ }
2420
2518
  });
2421
2519
  const latest = latestCompaction(context.sessionManager.getBranch());
2422
2520
  if (latest?.summary === NATIVE_COMPACTION_SUMMARY)
@@ -2424,23 +2522,28 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2424
2522
  });
2425
2523
  pi.on("before_agent_start", (_event, context) => {
2426
2524
  install(context);
2525
+ usageObservers.ready(context);
2427
2526
  });
2428
2527
  pi.on("model_select", (_event, context) => {
2429
2528
  invalidate();
2430
2529
  install(context);
2530
+ usageObservers.ready(context);
2431
2531
  });
2432
2532
  pi.on("session_tree", () => {
2433
2533
  invalidate();
2434
2534
  });
2435
- pi.on("session_shutdown", retire);
2535
+ pi.on("session_shutdown", () => {
2536
+ retire();
2537
+ usageObservers.dispose();
2538
+ });
2436
2539
  const removeInvalidation = pi.events.on("pi-provider-compaction:invalidate", invalidate);
2437
2540
  pi.on("session_shutdown", () => {
2438
2541
  if (typeof removeInvalidation === "function")
2439
2542
  removeInvalidation();
2440
2543
  });
2441
2544
  pi.on("session_before_compact", async (event, context) => {
2442
- const began = Date.now();
2443
- compactionStatus = "preparing compaction";
2545
+ const began = Date.now(), ticket = generation;
2546
+ const sessionId = context.sessionManager.getSessionId();
2444
2547
  fallbackReason = undefined;
2445
2548
  const attempt = expiryAttempt;
2446
2549
  const capture = snapshot;
@@ -2449,7 +2552,12 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2449
2552
  cache = undefined;
2450
2553
  pending = undefined;
2451
2554
  const cancelled = (usage, reason = "compaction ownership or input changed") => {
2452
- lastOutcome = { path: attempt ? "expiry cancelled" : "cancelled", elapsedMs: Date.now() - began, usage, reason };
2555
+ if (usage && ticket === generation && ctx) {
2556
+ try {
2557
+ if (ctx.sessionManager.getSessionId() === sessionId)
2558
+ pi.appendEntry("pi-provider-compaction:diagnostic", { status: `${attempt ? "expiry cancelled" : "cancelled"}: ${reason}`, usage });
2559
+ } catch {}
2560
+ }
2453
2561
  return { cancel: true };
2454
2562
  };
2455
2563
  const reject = (reason) => {
@@ -2463,7 +2571,6 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2463
2571
  }
2464
2572
  if (!capture && !cached || !context.model || generic(context) || event.signal.aborted)
2465
2573
  return reject("no eligible snapshot, explicit model override, or aborted attempt");
2466
- const ticket = generation;
2467
2574
  const branch = context.sessionManager.getBranch();
2468
2575
  const branchHash = digest(branch);
2469
2576
  const source = cached ?? capture;
@@ -2488,8 +2595,7 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2488
2595
  let summaryPath = "standard";
2489
2596
  let summaryReason;
2490
2597
  if (cached) {
2491
- compactionStatus = "generating cached portable summary";
2492
- summarizing = true;
2598
+ compactionRequest = true;
2493
2599
  try {
2494
2600
  const result = await cachedSummary(cached, context, event.preparation, event.customInstructions, event.signal, current);
2495
2601
  portable = result.result;
@@ -2498,7 +2604,7 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2498
2604
  if (portable)
2499
2605
  summaryPath = "cached";
2500
2606
  } finally {
2501
- summarizing = false;
2607
+ compactionRequest = false;
2502
2608
  }
2503
2609
  if (!current())
2504
2610
  return cancelled(attemptedUsage);
@@ -2508,7 +2614,6 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2508
2614
  return cancelled(attemptedUsage, summaryReason ?? "warm cache unavailable");
2509
2615
  if (!input && !attemptedUsage)
2510
2616
  return reject(summaryReason ?? fallbackReason ?? "native coverage unavailable");
2511
- compactionStatus = "generating portable summary";
2512
2617
  try {
2513
2618
  portable = await compact(event.preparation, source.model, auth.apiKey, auth.headers, event.customInstructions, event.signal, context.thinkingLevel, source.provider.streamSimple.bind(source.provider), auth.env, undefined, undefined, source.sessionId);
2514
2619
  portable = withFileTracking(portable, event.preparation);
@@ -2527,7 +2632,6 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2527
2632
  return { compaction: result };
2528
2633
  };
2529
2634
  if (!capture || !protocol || !input) {
2530
- compactionStatus = `${summaryPath} portable summary prepared`;
2531
2635
  return outcome(summaryPath, portable, summaryReason);
2532
2636
  }
2533
2637
  try {
@@ -2538,28 +2642,35 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2538
2642
  fallback("auth changed after portable generation");
2539
2643
  return outcome(summaryPath, portable, "native auth changed");
2540
2644
  }
2541
- compactionStatus = "requesting native checkpoint";
2542
- const native = await requestProviderCompaction({
2543
- provider: capture.provider,
2544
- model: capture.model,
2545
- context: capture.prepared.context,
2546
- input,
2547
- preparedPayload: capture.prepared.payload,
2548
- protocol,
2549
- apiKey: capture.options.apiKey,
2550
- headers: capture.options.headers,
2551
- env: capture.options.env,
2552
- signal: event.signal,
2553
- isCurrent: current,
2554
- fetch: capture.options.fetch,
2555
- ...capture.options.serviceTier === undefined ? {} : { serviceTier: capture.options.serviceTier }
2556
- });
2645
+ let native;
2646
+ compactionRequest = true;
2647
+ try {
2648
+ native = await requestProviderCompaction({
2649
+ provider: capture.provider,
2650
+ model: capture.model,
2651
+ context: capture.prepared.context,
2652
+ input,
2653
+ preparedPayload: capture.prepared.payload,
2654
+ protocol,
2655
+ apiKey: capture.options.apiKey,
2656
+ headers: capture.options.headers,
2657
+ env: capture.options.env,
2658
+ signal: event.signal,
2659
+ isCurrent: current,
2660
+ fetch: capture.options.fetch,
2661
+ sessionId: source.sessionId,
2662
+ ...capture.options.serviceTier === undefined ? {} : { serviceTier: capture.options.serviceTier }
2663
+ });
2664
+ } finally {
2665
+ compactionRequest = false;
2666
+ }
2557
2667
  if (!current())
2558
2668
  return cancelled(combineUsage(native.usage, portable.usage));
2559
2669
  const finalAuth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
2560
2670
  if (!current())
2561
2671
  return cancelled(combineUsage(native.usage, portable.usage));
2562
2672
  if (!finalAuth.ok || routeDigest(finalAuth) !== capture.authHash) {
2673
+ portable.usage = combineUsage(native.usage, portable.usage);
2563
2674
  fallback("auth changed after native generation");
2564
2675
  return outcome(summaryPath, portable, "native auth changed");
2565
2676
  }
@@ -2592,19 +2703,12 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2592
2703
  pi.on("session_compact", (event, context) => {
2593
2704
  const recorded = isObject(event.compactionEntry.details) ? event.compactionEntry.details.providerCompaction : undefined;
2594
2705
  const owned = isObject(recorded) && equal(recorded, lastOutcome);
2595
- if (readNativeCompactionDetails(event.compactionEntry.details)?.version === 3)
2596
- compactionStatus = "native checkpoint committed";
2597
- else if (fallbackReason)
2598
- compactionStatus = `portable: ${fallbackReason}`;
2599
- else if (owned)
2600
- compactionStatus = `${lastOutcome.path} portable summary committed`;
2601
- else
2706
+ if (!owned && !fallbackReason && readNativeCompactionDetails(event.compactionEntry.details)?.version !== 3)
2602
2707
  fallback("another handler supplied the committed result");
2603
2708
  if (owned) {
2604
2709
  if (configuration().outcomeNotifications && lastOutcome)
2605
- 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}; catalog cost $${lastOutcome.usage.cost.total.toFixed(6)}` : ""}`, "info");
2606
- } else
2607
- lastOutcome = { path: "Pi/other handler", elapsedMs: 0, usage: event.compactionEntry.usage, reason: compactionStatus };
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
+ }
2608
2712
  if (fallbackReason)
2609
2713
  pi.appendEntry("pi-provider-compaction:diagnostic", { status: `portable: ${fallbackReason}` });
2610
2714
  active = undefined;
@@ -2633,7 +2737,6 @@ Outcome: ${lastOutcome.path}, ${lastOutcome.elapsedMs}ms${usage ? `; input ${usa
2633
2737
  pending = undefined;
2634
2738
  snapshot = undefined;
2635
2739
  cache = undefined;
2636
- compactionStatus = "compaction failed or cancelled";
2637
2740
  });
2638
2741
  }
2639
2742
  export {