@nijaru/pi-provider-compaction 0.0.7 → 0.0.8

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
@@ -200,6 +200,8 @@ function installObserver(pi, ctx, observe) {
200
200
  const id = model.provider;
201
201
  const native = registry.getRegisteredNativeProvider(id);
202
202
  if (native) {
203
+ if (typeof native.getModels !== "function" || !native.getModels().some((entry) => entry.api === model.api))
204
+ return;
203
205
  const isInstalled = () => registry.getRegisteredNativeProvider(id) === wrapper;
204
206
  const guarded = (...args) => {
205
207
  if (!isInstalled())
@@ -1228,6 +1230,15 @@ async function collectProviderUsage(stream, signal) {
1228
1230
  throw new Error("Native compaction provider stream ended without usage");
1229
1231
  return usage;
1230
1232
  }
1233
+
1234
+ class NativeCompactionValidationError extends Error {
1235
+ usage;
1236
+ constructor(message, usage) {
1237
+ super(message);
1238
+ this.usage = usage;
1239
+ this.name = "NativeCompactionValidationError";
1240
+ }
1241
+ }
1231
1242
  async function requestResponsesCompact(request) {
1232
1243
  let preparedPayload;
1233
1244
  let compacted;
@@ -1254,9 +1265,13 @@ async function requestResponsesCompact(request) {
1254
1265
  if (!response.ok)
1255
1266
  return response;
1256
1267
  const parsed = await readJsonBounded(response, request.signal);
1257
- if (!isObject(parsed))
1258
- throw new Error("Responses Compact returned a non-object response");
1259
- compacted = { response: parsed, output: validateCompactedOutput(parsed) };
1268
+ try {
1269
+ if (!isObject(parsed))
1270
+ throw new Error("Responses Compact returned a non-object response");
1271
+ compacted = { response: parsed, output: validateCompactedOutput(parsed) };
1272
+ } catch (error) {
1273
+ throw new NativeCompactionValidationError(error instanceof Error ? error.message : String(error));
1274
+ }
1260
1275
  return syntheticCompletion(parsed, preparedPayload);
1261
1276
  } catch (error) {
1262
1277
  bridgeError = error;
@@ -1268,6 +1283,7 @@ async function requestResponsesCompact(request) {
1268
1283
  headers: request.headers,
1269
1284
  env: request.env,
1270
1285
  signal: request.signal,
1286
+ ...request.serviceTier === undefined ? {} : { serviceTier: request.serviceTier },
1271
1287
  transport: "sse",
1272
1288
  cacheRetention: "none",
1273
1289
  maxRetries: 0,
@@ -1286,6 +1302,8 @@ async function requestResponsesCompact(request) {
1286
1302
  throw bridgeError;
1287
1303
  throw error;
1288
1304
  });
1305
+ if (bridgeError instanceof NativeCompactionValidationError)
1306
+ throw new NativeCompactionValidationError(bridgeError.message, usage);
1289
1307
  if (bridgeError)
1290
1308
  throw bridgeError;
1291
1309
  if (!compacted || dispatches !== 1)
@@ -1430,6 +1448,7 @@ async function requestRemoteV2(request) {
1430
1448
  headers: request.headers,
1431
1449
  env: request.env,
1432
1450
  signal: request.signal,
1451
+ ...request.serviceTier === undefined ? {} : { serviceTier: request.serviceTier },
1433
1452
  transport: "sse",
1434
1453
  cacheRetention: "none",
1435
1454
  maxRetries: 0,
@@ -1457,7 +1476,11 @@ async function requestRemoteV2(request) {
1457
1476
  }
1458
1477
  async function requestProviderCompaction(request) {
1459
1478
  const result = await (request.protocol === "remote-v2" ? requestRemoteV2(request) : requestResponsesCompact(request));
1460
- return { ...result, output: validateCompactedOutput(result) };
1479
+ try {
1480
+ return { ...result, output: validateCompactedOutput(result) };
1481
+ } catch (error) {
1482
+ throw new NativeCompactionValidationError(error instanceof Error ? error.message : String(error), result.usage);
1483
+ }
1461
1484
  }
1462
1485
 
1463
1486
  // replay.ts
@@ -1776,7 +1799,8 @@ Last attempt: ${compactionStatus}`, "info");
1776
1799
  return;
1777
1800
  }
1778
1801
  if (!options.onPayload) {
1779
- captureRejected("no payload callback");
1802
+ if (captureStatus !== "eligible prepared request captured")
1803
+ captureRejected("no payload callback");
1780
1804
  return;
1781
1805
  }
1782
1806
  if (generic(owner)) {
@@ -1795,7 +1819,7 @@ Last attempt: ${compactionStatus}`, "info");
1795
1819
  if (generation !== ticket || ctx !== owner || options.signal?.aborted || !supportedRouteOptions(model, options))
1796
1820
  return false;
1797
1821
  try {
1798
- return digest(owner.sessionManager.getBranch()) === branchHash;
1822
+ return unchanged(owner.sessionManager.getBranch(), branch.length, branchHash);
1799
1823
  } catch {
1800
1824
  retire();
1801
1825
  return false;
@@ -1827,8 +1851,9 @@ Last attempt: ${compactionStatus}`, "info");
1827
1851
  const routeHash = route(model, options);
1828
1852
  const policyHash = digest(requestPolicy(model, prepared));
1829
1853
  const latest = latestCompaction(branch);
1854
+ const payloadModel = isObject(payload) && typeof payload.model === "string" ? payload.model : undefined;
1830
1855
  let result = payload;
1831
- if (active && latest?.id === active.entryId && latest.summary === active.summary && digest(latest.details) === active.detailsHash && unchanged(branch, active.branchLength, active.branchHash) && active.routeHash === routeHash && active.policyHash === policyHash) {
1856
+ if (active && latest?.id === active.entryId && latest.summary === active.summary && active.payloadModel === payloadModel && unchanged(branch, active.branchLength, active.branchHash) && active.routeHash === routeHash && active.policyHash === policyHash) {
1832
1857
  const details = readNativeCompactionDetails(latest.details);
1833
1858
  const projection = owner.sessionManager.buildSessionProjection();
1834
1859
  const contribution = projection.entries.find((entry) => entry.sourceEntry.id === latest.id);
@@ -1851,7 +1876,7 @@ Last attempt: ${compactionStatus}`, "info");
1851
1876
  prepared,
1852
1877
  model: structuredClone(model),
1853
1878
  provider,
1854
- options: { apiKey: options.apiKey, headers: structuredClone(options.headers), env: structuredClone(options.env), fetch: options.fetch },
1879
+ options: { apiKey: options.apiKey, headers: structuredClone(options.headers), env: structuredClone(options.env), fetch: options.fetch, ...typeof options.serviceTier === "string" ? { serviceTier: options.serviceTier } : {} },
1855
1880
  branchLength: branch.length,
1856
1881
  branchHash,
1857
1882
  routeHash,
@@ -1905,6 +1930,7 @@ Last attempt: ${compactionStatus}`, "info");
1905
1930
  removeInvalidation();
1906
1931
  });
1907
1932
  pi.on("session_before_compact", async (event, context) => {
1933
+ compactionStatus = "preparing compaction";
1908
1934
  const capture = snapshot;
1909
1935
  snapshot = undefined;
1910
1936
  pending = undefined;
@@ -1938,7 +1964,7 @@ Last attempt: ${compactionStatus}`, "info");
1938
1964
  const input = selectCoveredPrefix(capture.model, capture.prepared, prefix, fallback);
1939
1965
  if (!input)
1940
1966
  return;
1941
- const current = () => ticket === generation && !event.signal.aborted && supportedRouteOptions(capture.model, capture.options) && !generic(context) && context.modelRegistry.getProvider(capture.model.provider) === capture.provider && digest(context.sessionManager.getBranch()) === branchHash;
1967
+ const current = () => ticket === generation && !event.signal.aborted && supportedRouteOptions(capture.model, capture.options) && !generic(context) && context.modelRegistry.getProvider(capture.model.provider) === capture.provider && unchanged(context.sessionManager.getBranch(), branch.length, branchHash);
1942
1968
  const auth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
1943
1969
  if (!current())
1944
1970
  return { cancel: true };
@@ -1951,7 +1977,10 @@ Last attempt: ${compactionStatus}`, "info");
1951
1977
  try {
1952
1978
  portable = await compact(event.preparation, capture.model, auth.apiKey, auth.headers, event.customInstructions, event.signal, context.thinkingLevel, capture.provider.streamSimple.bind(capture.provider), auth.env, undefined, undefined, capture.sessionId);
1953
1979
  } catch {
1954
- return !current() ? { cancel: true } : undefined;
1980
+ if (!current())
1981
+ return { cancel: true };
1982
+ fallback("portable summary generation failed");
1983
+ return;
1955
1984
  }
1956
1985
  if (!current() || !portable.summary.trim() || portable.summary === NATIVE_COMPACTION_SUMMARY)
1957
1986
  return { cancel: true };
@@ -1975,7 +2004,8 @@ Last attempt: ${compactionStatus}`, "info");
1975
2004
  headers: capture.options.headers,
1976
2005
  env: capture.options.env,
1977
2006
  signal: event.signal,
1978
- fetch: capture.options.fetch
2007
+ fetch: capture.options.fetch,
2008
+ ...capture.options.serviceTier === undefined ? {} : { serviceTier: capture.options.serviceTier }
1979
2009
  });
1980
2010
  if (!current())
1981
2011
  return { cancel: true };
@@ -2002,11 +2032,13 @@ Last attempt: ${compactionStatus}`, "info");
2002
2032
  };
2003
2033
  pending = { details, capture, summary: portable.summary };
2004
2034
  return { compaction: { ...portable, usage: combineUsage(native.usage, portable.usage), details } };
2005
- } catch {
2035
+ } catch (error) {
2006
2036
  if (!current())
2007
2037
  return { cancel: true };
2008
2038
  fallback("native acquisition or validation failed");
2009
- return { compaction: portable };
2039
+ return {
2040
+ compaction: error instanceof NativeCompactionValidationError && error.usage !== undefined ? { ...portable, usage: combineUsage(error.usage, portable.usage) } : portable
2041
+ };
2010
2042
  }
2011
2043
  });
2012
2044
  pi.on("session_compact", (event, context) => {
@@ -2024,9 +2056,18 @@ Last attempt: ${compactionStatus}`, "info");
2024
2056
  return;
2025
2057
  const branch = context.sessionManager.getBranch();
2026
2058
  const coverage = candidate.details.coverage;
2027
- if (!coverage || event.compactionEntry.firstKeptEntryId !== coverage.firstKeptEntryId || event.compactionEntry.parentId !== coverage.sourceLeafId || branch.at(-1)?.id !== event.compactionEntry.id || digest(branch.slice(0, -1)) !== coverage.sourceHash)
2059
+ const index = branch.findIndex((entry) => entry.id === event.compactionEntry.id);
2060
+ if (!coverage || index < 0 || event.compactionEntry.firstKeptEntryId !== coverage.firstKeptEntryId || event.compactionEntry.parentId !== coverage.sourceLeafId || digest(branch.slice(0, index)) !== coverage.sourceHash)
2028
2061
  return;
2029
- active = { entryId: event.compactionEntry.id, detailsHash: digest(candidate.details), summary: candidate.summary, branchLength: branch.length, branchHash: digest(branch), routeHash: candidate.capture.routeHash, policyHash: candidate.capture.policyHash };
2062
+ active = {
2063
+ entryId: event.compactionEntry.id,
2064
+ summary: candidate.summary,
2065
+ branchLength: index + 1,
2066
+ branchHash: digest(branch.slice(0, index + 1)),
2067
+ routeHash: candidate.capture.routeHash,
2068
+ policyHash: candidate.capture.policyHash,
2069
+ ...typeof candidate.capture.prepared.payload.model === "string" ? { payloadModel: candidate.capture.prepared.payload.model } : {}
2070
+ };
2030
2071
  });
2031
2072
  pi.on("session_compact_failed", () => {
2032
2073
  pending = undefined;
package/index.ts CHANGED
@@ -5,7 +5,7 @@ import { compact, convertToLlm, type ExtensionAPI, type ExtensionContext, type S
5
5
  import { resolveCompactionModelPolicy } from "./policy";
6
6
  import { installObserver, type InstalledObserver, type ObserveRequest } from "./provider";
7
7
  import { capturePreparedRequest, equal, requestPolicy, selectCoveredPrefix, substituteSummary, summarySlot, type PreparedRequest } from "./replay";
8
- import { isObject, requestProviderCompaction, resolveNativeProtocol, supportsResponsesApi, validateCompactedOutput, type NativeProtocol, type ResponseItem } from "./protocol";
8
+ import { isObject, NativeCompactionValidationError, requestProviderCompaction, resolveNativeProtocol, supportsResponsesApi, validateCompactedOutput, type NativeProtocol, type ResponseItem } from "./protocol";
9
9
 
10
10
  export const NATIVE_COMPACTION_TYPE = "pi-provider-compaction/openai-responses";
11
11
  export const NATIVE_COMPACTION_VERSION = 3;
@@ -124,7 +124,7 @@ interface Captured {
124
124
  prepared: PreparedRequest;
125
125
  model: Model<any>;
126
126
  provider: Provider;
127
- options: ProviderStreamOptions | SimpleStreamOptions;
127
+ options: (ProviderStreamOptions | SimpleStreamOptions) & { serviceTier?: string };
128
128
  branchLength: number;
129
129
  branchHash: string;
130
130
  routeHash: string;
@@ -134,12 +134,13 @@ interface Captured {
134
134
  }
135
135
  interface Active {
136
136
  entryId: string;
137
- detailsHash: string;
138
137
  summary: string;
139
138
  branchLength: number;
140
139
  branchHash: string;
141
140
  routeHash: string;
142
141
  policyHash: string;
142
+ /** Model field of the prepared payload; a request hook rewrite invalidates native state. */
143
+ payloadModel?: string;
143
144
  }
144
145
 
145
146
  export default function (pi: ExtensionAPI): void {
@@ -190,7 +191,12 @@ export default function (pi: ExtensionAPI): void {
190
191
  }
191
192
  if (options.sessionId !== manager.getSessionId()) return;
192
193
  if (!supportsNativeCompaction(model)) { captureRejected("unsupported API"); return; }
193
- if (!options.onPayload) { captureRejected("no payload callback"); return; }
194
+ if (!options.onPayload) {
195
+ // The portable-summary request re-enters this observer without a payload
196
+ // callback. Do not overwrite the main request's capture diagnostic.
197
+ if (captureStatus !== "eligible prepared request captured") captureRejected("no payload callback");
198
+ return;
199
+ }
194
200
  if (generic(owner)) { captureRejected("generic compaction model takes precedence"); return; }
195
201
  // The Azure adapter also accepts API-specific overrides and process-env
196
202
  // fallbacks. Those must not silently select a route absent from our evidence.
@@ -201,7 +207,7 @@ export default function (pi: ExtensionAPI): void {
201
207
  const canonical = transcript(manager.buildSessionProjection().messages);
202
208
  const current = () => {
203
209
  if (generation !== ticket || ctx !== owner || options.signal?.aborted || !supportedRouteOptions(model, options)) return false;
204
- try { return digest(owner.sessionManager.getBranch()) === branchHash; }
210
+ try { return unchanged(owner.sessionManager.getBranch(), branch.length, branchHash); }
205
211
  catch { retire(); return false; }
206
212
  };
207
213
  return async (payload) => {
@@ -225,8 +231,9 @@ export default function (pi: ExtensionAPI): void {
225
231
  const routeHash = route(model, options);
226
232
  const policyHash = digest(requestPolicy(model, prepared));
227
233
  const latest = latestCompaction(branch);
234
+ const payloadModel = isObject(payload) && typeof payload.model === "string" ? payload.model : undefined;
228
235
  let result = payload;
229
- if (active && latest?.id === active.entryId && latest.summary === active.summary && digest(latest.details) === active.detailsHash && unchanged(branch, active.branchLength, active.branchHash) && active.routeHash === routeHash && active.policyHash === policyHash) {
236
+ if (active && latest?.id === active.entryId && latest.summary === active.summary && active.payloadModel === payloadModel && unchanged(branch, active.branchLength, active.branchHash) && active.routeHash === routeHash && active.policyHash === policyHash) {
230
237
  const details = readNativeCompactionDetails(latest.details);
231
238
  const projection = owner.sessionManager.buildSessionProjection();
232
239
  const contribution = projection.entries.find((entry) => entry.sourceEntry.id === latest.id);
@@ -244,7 +251,7 @@ export default function (pi: ExtensionAPI): void {
244
251
  captureStatus = "eligible prepared request captured";
245
252
  snapshot = {
246
253
  prepared, model: structuredClone(model), provider,
247
- options: { apiKey: options.apiKey, headers: structuredClone(options.headers), env: structuredClone(options.env), fetch: options.fetch },
254
+ options: { apiKey: options.apiKey, headers: structuredClone(options.headers), env: structuredClone(options.env), fetch: options.fetch, ...(typeof (options as { serviceTier?: unknown }).serviceTier === "string" ? { serviceTier: (options as { serviceTier: string }).serviceTier } : {}) },
248
255
  branchLength: branch.length, branchHash, routeHash, authHash: routeDigest(auth), policyHash,
249
256
  sessionId: owner.sessionManager.getSessionId(),
250
257
  };
@@ -281,6 +288,7 @@ export default function (pi: ExtensionAPI): void {
281
288
  pi.on("session_shutdown", () => { if (typeof removeInvalidation === "function") removeInvalidation(); });
282
289
 
283
290
  pi.on("session_before_compact", async (event, context) => {
291
+ compactionStatus = "preparing compaction";
284
292
  const capture = snapshot;
285
293
  snapshot = undefined; pending = undefined;
286
294
  if (!capture) { fallback(`no eligible snapshot (${captureStatus})`); return; }
@@ -297,7 +305,7 @@ export default function (pi: ExtensionAPI): void {
297
305
  const prefix = transcript(projection.entries.slice(0, boundary).flatMap((entry) => entry.messages));
298
306
  const input = selectCoveredPrefix(capture.model, capture.prepared, prefix, fallback);
299
307
  if (!input) return;
300
- const current = () => ticket === generation && !event.signal.aborted && supportedRouteOptions(capture.model, capture.options) && !generic(context) && context.modelRegistry.getProvider(capture.model.provider) === capture.provider && digest(context.sessionManager.getBranch()) === branchHash;
308
+ const current = () => ticket === generation && !event.signal.aborted && supportedRouteOptions(capture.model, capture.options) && !generic(context) && context.modelRegistry.getProvider(capture.model.provider) === capture.provider && unchanged(context.sessionManager.getBranch(), branch.length, branchHash);
301
309
  const auth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
302
310
  if (!current()) return { cancel: true };
303
311
  if (!auth.ok || routeDigest(auth) !== capture.authHash) { fallback("auth changed since capture"); return; }
@@ -307,7 +315,9 @@ export default function (pi: ExtensionAPI): void {
307
315
  portable = await compact(event.preparation, capture.model, auth.apiKey, auth.headers as Record<string, string> | undefined, event.customInstructions, event.signal, context.thinkingLevel, capture.provider.streamSimple.bind(capture.provider), auth.env, undefined, undefined, capture.sessionId);
308
316
  } catch {
309
317
  // No result exists to return; let Pi own its ordinary failure/fallback behavior.
310
- return !current() ? { cancel: true } : undefined;
318
+ if (!current()) return { cancel: true };
319
+ fallback("portable summary generation failed");
320
+ return undefined;
311
321
  }
312
322
  if (!current() || !portable.summary.trim() || portable.summary === NATIVE_COMPACTION_SUMMARY) return { cancel: true };
313
323
  try {
@@ -320,6 +330,7 @@ export default function (pi: ExtensionAPI): void {
320
330
  input, preparedPayload: capture.prepared.payload, protocol,
321
331
  apiKey: capture.options.apiKey, headers: capture.options.headers, env: capture.options.env,
322
332
  signal: event.signal, fetch: capture.options.fetch,
333
+ ...(capture.options.serviceTier === undefined ? {} : { serviceTier: capture.options.serviceTier }),
323
334
  });
324
335
  if (!current()) return { cancel: true };
325
336
  const finalAuth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
@@ -334,12 +345,18 @@ export default function (pi: ExtensionAPI): void {
334
345
  };
335
346
  pending = { details, capture, summary: portable.summary };
336
347
  return { compaction: { ...portable, usage: combineUsage(native.usage, portable.usage), details } };
337
- } catch {
348
+ } catch (error) {
338
349
  if (!current()) return { cancel: true };
339
350
  // Do not expose exception text: provider errors may contain request data.
340
351
  fallback("native acquisition or validation failed");
341
- // The portable request already succeeded. Never pay for a duplicate summary.
342
- return { compaction: portable };
352
+ // The portable request already succeeded. Never pay for a duplicate summary,
353
+ // and keep the native call's usage when the endpoint answered but produced
354
+ // an unusable window.
355
+ return {
356
+ compaction: error instanceof NativeCompactionValidationError && error.usage !== undefined
357
+ ? { ...portable, usage: combineUsage(error.usage, portable.usage) }
358
+ : portable,
359
+ };
343
360
  }
344
361
  });
345
362
  pi.on("session_compact", (event, context) => {
@@ -352,8 +369,17 @@ export default function (pi: ExtensionAPI): void {
352
369
  if (!candidate || !equal(event.compactionEntry.details, candidate.details) || event.compactionEntry.summary !== candidate.summary) return;
353
370
  const branch = context.sessionManager.getBranch();
354
371
  const coverage = candidate.details.coverage;
355
- if (!coverage || event.compactionEntry.firstKeptEntryId !== coverage.firstKeptEntryId || event.compactionEntry.parentId !== coverage.sourceLeafId || branch.at(-1)?.id !== event.compactionEntry.id || digest(branch.slice(0, -1)) !== coverage.sourceHash) return;
356
- active = { entryId: event.compactionEntry.id, detailsHash: digest(candidate.details), summary: candidate.summary, branchLength: branch.length, branchHash: digest(branch), routeHash: candidate.capture.routeHash, policyHash: candidate.capture.policyHash };
372
+ const index = branch.findIndex((entry) => entry.id === event.compactionEntry.id);
373
+ if (!coverage || index < 0 || event.compactionEntry.firstKeptEntryId !== coverage.firstKeptEntryId || event.compactionEntry.parentId !== coverage.sourceLeafId || digest(branch.slice(0, index)) !== coverage.sourceHash) return;
374
+ active = {
375
+ entryId: event.compactionEntry.id,
376
+ summary: candidate.summary,
377
+ branchLength: index + 1,
378
+ branchHash: digest(branch.slice(0, index + 1)),
379
+ routeHash: candidate.capture.routeHash,
380
+ policyHash: candidate.capture.policyHash,
381
+ ...(typeof candidate.capture.prepared.payload.model === "string" ? { payloadModel: candidate.capture.prepared.payload.model } : {}),
382
+ };
357
383
  });
358
384
  pi.on("session_compact_failed", () => { pending = undefined; snapshot = undefined; compactionStatus = "compaction failed or cancelled"; });
359
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nijaru/pi-provider-compaction",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "description": "Provider-native context compaction adapters for Pi",
6
6
  "keywords": [
package/protocol.ts CHANGED
@@ -225,10 +225,20 @@ interface TransportRequest {
225
225
  apiKey?: string;
226
226
  headers?: ProviderHeaders;
227
227
  env?: Record<string, string>;
228
+ /** Cost-relevant request option captured with the original request. */
229
+ serviceTier?: string;
228
230
  signal: AbortSignal;
229
231
  fetch?: typeof globalThis.fetch;
230
232
  }
231
233
 
234
+ /** The provider answered and was billed, but its window cannot be committed. */
235
+ export class NativeCompactionValidationError extends Error {
236
+ constructor(message: string, readonly usage?: Usage) {
237
+ super(message);
238
+ this.name = "NativeCompactionValidationError";
239
+ }
240
+ }
241
+
232
242
  export interface TransportResponse {
233
243
  output: ResponseItem[];
234
244
  usage: Usage;
@@ -258,8 +268,12 @@ async function requestResponsesCompact(request: TransportRequest): Promise<Trans
258
268
  });
259
269
  if (!response.ok) return response;
260
270
  const parsed = await readJsonBounded(response, request.signal);
261
- if (!isObject(parsed)) throw new Error("Responses Compact returned a non-object response");
262
- compacted = { response: parsed, output: validateCompactedOutput(parsed) };
271
+ try {
272
+ if (!isObject(parsed)) throw new Error("Responses Compact returned a non-object response");
273
+ compacted = { response: parsed, output: validateCompactedOutput(parsed) };
274
+ } catch (error) {
275
+ throw new NativeCompactionValidationError(error instanceof Error ? error.message : String(error));
276
+ }
263
277
  return syntheticCompletion(parsed, preparedPayload);
264
278
  } catch (error) {
265
279
  bridgeError = error;
@@ -272,6 +286,7 @@ async function requestResponsesCompact(request: TransportRequest): Promise<Trans
272
286
  headers: request.headers,
273
287
  env: request.env,
274
288
  signal: request.signal,
289
+ ...(request.serviceTier === undefined ? {} : { serviceTier: request.serviceTier }),
275
290
  transport: "sse",
276
291
  cacheRetention: "none",
277
292
  maxRetries: 0,
@@ -288,6 +303,7 @@ async function requestResponsesCompact(request: TransportRequest): Promise<Trans
288
303
  if (bridgeError) throw bridgeError;
289
304
  throw error;
290
305
  });
306
+ if (bridgeError instanceof NativeCompactionValidationError) throw new NativeCompactionValidationError(bridgeError.message, usage);
291
307
  if (bridgeError) throw bridgeError;
292
308
  if (!compacted || dispatches !== 1) throw new Error("Responses Compact did not complete exactly one request");
293
309
  return { output: compacted.output, usage };
@@ -410,6 +426,7 @@ async function requestRemoteV2(request: TransportRequest): Promise<TransportResp
410
426
  headers: request.headers,
411
427
  env: request.env,
412
428
  signal: request.signal,
429
+ ...(request.serviceTier === undefined ? {} : { serviceTier: request.serviceTier }),
413
430
  transport: "sse",
414
431
  cacheRetention: "none",
415
432
  maxRetries: 0,
@@ -436,5 +453,9 @@ export async function requestProviderCompaction(
436
453
  request: TransportRequest & { protocol: NativeProtocol },
437
454
  ): Promise<TransportResponse> {
438
455
  const result = await (request.protocol === "remote-v2" ? requestRemoteV2(request) : requestResponsesCompact(request));
439
- return { ...result, output: validateCompactedOutput(result) };
456
+ try {
457
+ return { ...result, output: validateCompactedOutput(result) };
458
+ } catch (error) {
459
+ throw new NativeCompactionValidationError(error instanceof Error ? error.message : String(error), result.usage);
460
+ }
440
461
  }
package/provider.ts CHANGED
@@ -59,6 +59,11 @@ export function installObserver(pi: ExtensionAPI, ctx: ExtensionContext, observe
59
59
  const id = model.provider;
60
60
  const native = registry.getRegisteredNativeProvider(id);
61
61
  if (native) {
62
+ // Pi composes a native base only when its catalog serves the active API.
63
+ // Otherwise the request goes to the global API provider and bypasses this
64
+ // wrapper; claiming an observer there would report native compaction as
65
+ // installed while never capturing anything.
66
+ if (typeof native.getModels !== "function" || !native.getModels().some((entry) => entry.api === model.api)) return;
62
67
  const isInstalled = () => registry.getRegisteredNativeProvider(id) === wrapper;
63
68
  const guarded: ObserveRequest = (...args) => {
64
69
  if (!isInstalled()) return;