@nijaru/pi-provider-compaction 0.0.4 → 0.0.5

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,14 +1,14 @@
1
1
  # pi-provider-compaction
2
2
 
3
- Experimental provider-native compaction for [Pi](https://github.com/earendil-works/pi), with a meaningful portable summary saved alongside each native checkpoint. Requires Pi 0.87.0.
3
+ Experimental provider-native compaction for [Pi](https://github.com/earendil-works/pi), with a meaningful portable summary saved alongside each native checkpoint. Minimum runtime: Pi 0.87.0. Development checks target Pi 1.2.0.
4
4
 
5
- **The unpublished redesign is not production-validated. Do not enable it globally yet.** Real runner/adapter fixtures and a Codex live smoke test pass; broader backend and deployment validation remains incomplete.
5
+ **This experimental design is not production-validated. Do not enable it globally yet.** Real runner/adapter fixtures and a Codex live smoke test pass; broader backend and deployment validation remains incomplete.
6
6
 
7
7
  [pi-compactor](https://github.com/nijaru/pi-compactor) owns compaction timing and continuation. This extension owns native request capture, prefix coverage, persistence and replay. It does not change `/fast` or account reporting.
8
8
 
9
9
  ## Supported integration
10
10
 
11
- Pi 0.87.0 has replacement provider registration, not composable middleware. This extension only installs request observation where it can preserve the existing provider contract:
11
+ Pi has replacement provider registration, not composable middleware. This extension only installs request observation where it can preserve the existing provider contract:
12
12
 
13
13
  - **Registered native base providers:** both streaming methods are decorated; authentication, catalogs, filtering, refresh and deferred methods stay with the original provider. `models.json` remains above that base.
14
14
  - **Existing static legacy `streamSimple` registrations:** the original callback and configuration are retained. This includes `pi-fast-mode`'s Codex registration. Legacy dynamic catalogs and OAuth model-projection callbacks are excluded.
@@ -71,9 +71,9 @@ bun run build
71
71
  pi --extension ./dist/index.js
72
72
  ```
73
73
 
74
- The manifest loads the built `dist/index.js`; source edits alone do not update it. Keep global installation disabled while testing. Publishing and global activation are separate approval steps.
74
+ 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.
75
75
 
76
- Tests include real Pi 0.87.0 runner/adapter fixtures for all three protocols, repeated prefix compaction, transformed suffix preservation, unchanged fallback, grammar tools, strict defaults, tool IDs, images and prompt updates. The optional installed-fast-mode fixture (`PI_FAST_MODE_FIXTURE_PATH`) also checks actual `pi-fast-mode` composition across `/reload`; run it with an isolated `PI_CODING_AGENT_DIR` containing an active, persistent fast-mode configuration.
76
+ Tests include real Pi 1.2.0 runner/adapter fixtures for all three protocols, repeated prefix compaction, transformed suffix preservation, unchanged fallback, grammar tools, strict defaults, tool IDs, images and prompt updates. The optional installed-fast-mode fixture (`PI_FAST_MODE_FIXTURE_PATH`) also checks actual `pi-fast-mode` composition across `/reload`; run it with an isolated `PI_CODING_AGENT_DIR` containing an active, persistent fast-mode configuration.
77
77
 
78
78
  Authenticated protocol smoke tests are separate and send three small requests:
79
79
 
package/dist/index.js CHANGED
@@ -273,7 +273,7 @@ function schemaAllowsNull(schema) {
273
273
  return true;
274
274
  return Array.isArray(schema.anyOf) && schema.anyOf.some((variant) => schemaAllowsNull(variant));
275
275
  }
276
- function makeJsonSchemaNodeStrict(schema) {
276
+ function makeJsonSchemaNodeStrict(schema, isUnsupportedKeyword) {
277
277
  if (!isJsonSchemaObject(schema)) {
278
278
  throw new UnsupportedStrictJsonSchemaError("boolean schemas are unsupported");
279
279
  }
@@ -282,6 +282,13 @@ function makeJsonSchemaNodeStrict(schema) {
282
282
  throw new UnsupportedStrictJsonSchemaError(`${key} schemas are unsupported`);
283
283
  }
284
284
  }
285
+ if (isUnsupportedKeyword) {
286
+ for (const [key, value] of Object.entries(schema)) {
287
+ if (isUnsupportedKeyword(key, value)) {
288
+ throw new UnsupportedStrictJsonSchemaError(`${key}: ${JSON.stringify(value)} is unsupported`);
289
+ }
290
+ }
291
+ }
285
292
  if (schema.anyOf !== undefined) {
286
293
  if (!Array.isArray(schema.anyOf) || schema.anyOf.length === 0) {
287
294
  throw new UnsupportedStrictJsonSchemaError("anyOf must contain at least one schema");
@@ -290,14 +297,14 @@ function makeJsonSchemaNodeStrict(schema) {
290
297
  if (isStructuredSchema(variant)) {
291
298
  throw new UnsupportedStrictJsonSchemaError("object and array unions are unsupported");
292
299
  }
293
- makeJsonSchemaNodeStrict(variant);
300
+ makeJsonSchemaNodeStrict(variant, isUnsupportedKeyword);
294
301
  }
295
302
  }
296
303
  if (schema.items !== undefined) {
297
304
  if (Array.isArray(schema.items)) {
298
305
  throw new UnsupportedStrictJsonSchemaError("tuple schemas are unsupported");
299
306
  }
300
- makeJsonSchemaNodeStrict(schema.items);
307
+ makeJsonSchemaNodeStrict(schema.items, isUnsupportedKeyword);
301
308
  }
302
309
  const isObjectSchema = schema.type === "object";
303
310
  if (schema.properties !== undefined && !isObjectSchema) {
@@ -321,7 +328,7 @@ function makeJsonSchemaNodeStrict(schema) {
321
328
  throw new UnsupportedStrictJsonSchemaError("required contains an unknown property");
322
329
  }
323
330
  for (const [key, property] of Object.entries(properties)) {
324
- makeJsonSchemaNodeStrict(property);
331
+ makeJsonSchemaNodeStrict(property, isUnsupportedKeyword);
325
332
  if (!required.has(key) && !schemaAllowsNull(property)) {
326
333
  properties[key] = { anyOf: [property, { type: "null" }] };
327
334
  }
@@ -329,12 +336,12 @@ function makeJsonSchemaNodeStrict(schema) {
329
336
  schema.required = propertyNames;
330
337
  schema.additionalProperties = false;
331
338
  }
332
- function makeStrictJsonSchema(schema) {
339
+ function makeStrictJsonSchema(schema, isUnsupportedKeyword) {
333
340
  const cloned = structuredClone(schema);
334
341
  if (!isJsonSchemaObject(cloned)) {
335
342
  throw new UnsupportedStrictJsonSchemaError("root schema must have type object");
336
343
  }
337
- makeJsonSchemaNodeStrict(cloned);
344
+ makeJsonSchemaNodeStrict(cloned, isUnsupportedKeyword);
338
345
  if (cloned.type !== "object") {
339
346
  throw new UnsupportedStrictJsonSchemaError("root schema must have type object");
340
347
  }
@@ -367,13 +374,13 @@ function inferGrammarInputProperty(tool) {
367
374
  }
368
375
  return inputProperty;
369
376
  }
370
- function resolveJsonSchemaStrictSampling(tool, supportsStrictMode) {
377
+ function resolveJsonSchemaStrictSampling(tool, supportsStrictMode, isUnsupportedKeyword) {
371
378
  const config = tool.constrainedSampling;
372
379
  if (!config || config.type !== "json_schema")
373
380
  return;
374
381
  if (supportsStrictMode) {
375
382
  try {
376
- makeStrictJsonSchema(tool.parameters);
383
+ makeStrictJsonSchema(tool.parameters, isUnsupportedKeyword);
377
384
  return true;
378
385
  } catch (error) {
379
386
  if (!(error instanceof UnsupportedStrictJsonSchemaError))
@@ -899,7 +906,8 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
899
906
  const [callId, itemIdRaw] = toolCall.id.split("|");
900
907
  const customInputProperty = options?.grammarToolInputProperties?.get(toolCall.name);
901
908
  let itemId = itemIdRaw;
902
- if (isDifferentModel && itemId?.startsWith("fc_") || customInputProperty === undefined && !itemId?.startsWith("fc_")) {
909
+ const itemIdPrefix = customInputProperty === undefined ? "fc_" : "ctc_";
910
+ if (isDifferentModel || !itemId?.startsWith(itemIdPrefix)) {
903
911
  itemId = undefined;
904
912
  }
905
913
  if (customInputProperty !== undefined) {
@@ -1436,6 +1444,7 @@ async function requestProviderCompaction(request) {
1436
1444
 
1437
1445
  // replay.ts
1438
1446
  var TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
1447
+ var AZURE_TOOL_CALL_PROVIDERS = new Set([...TOOL_CALL_PROVIDERS, "azure"]);
1439
1448
  var MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
1440
1449
  var SNAPSHOT_TTL_MS = 5 * 60000;
1441
1450
  function equal(left, right) {
@@ -1447,7 +1456,7 @@ function serializeInput(model, context, declarations = context) {
1447
1456
  const codex = model.api === "openai-codex-responses";
1448
1457
  const normalized = resolveTranscript2(context, flag("supportsMidConvoSystemMessages"));
1449
1458
  const tools = resolveTranscript2(declarations, flag("supportsMidConvoSystemMessages"));
1450
- return convertResponsesMessages(model, normalized, model.api === "azure-openai-responses" ? new Set([...TOOL_CALL_PROVIDERS, "azure-openai-responses"]) : TOOL_CALL_PROVIDERS, {
1459
+ return convertResponsesMessages(model, normalized, model.api === "azure-openai-responses" ? AZURE_TOOL_CALL_PROVIDERS : TOOL_CALL_PROVIDERS, {
1451
1460
  includeSystemPrompt: !codex,
1452
1461
  grammarToolInputProperties: createGrammarToolInputProperties(getDeclaredTools(tools.messages), flag("supportsOpenAIGrammarTools")),
1453
1462
  supportsMidConvoSystemMessages: flag("supportsMidConvoSystemMessages"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nijaru/pi-provider-compaction",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "description": "Provider-native context compaction adapters for Pi",
6
6
  "keywords": [
@@ -18,7 +18,7 @@
18
18
  "author": "Nick Naru <nick@nijaru.com>",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/nijaru/pi-provider-compaction.git"
21
+ "url": "git+https://github.com/nijaru/pi-provider-compaction.git"
22
22
  },
23
23
  "pi": {
24
24
  "extensions": [
@@ -31,7 +31,7 @@
31
31
  "test": "bun test",
32
32
  "lint": "oxlint build.ts index.ts protocol.ts policy.ts provider.ts replay.ts tests/",
33
33
  "typecheck": "tsc --noEmit",
34
- "check": "bun test && bun run lint && tsc --noEmit"
34
+ "check": "bun run build && bun test && bun run lint && tsc --noEmit"
35
35
  },
36
36
  "files": [
37
37
  "dist",
@@ -44,14 +44,15 @@
44
44
  "LICENSE"
45
45
  ],
46
46
  "devDependencies": {
47
- "@earendil-works/pi-ai": "0.87.0",
48
- "@earendil-works/pi-agent-core": "0.87.0",
49
- "@earendil-works/pi-coding-agent": "0.87.0",
47
+ "@earendil-works/pi-ai": "1.2.0",
48
+ "@earendil-works/pi-agent-core": "1.2.0",
49
+ "@earendil-works/pi-coding-agent": "1.2.0",
50
50
  "@types/bun": "latest",
51
51
  "oxlint": "^1.81.0",
52
52
  "typescript": "^6.0.3"
53
53
  },
54
54
  "peerDependencies": {
55
+ "@earendil-works/pi-ai": "*",
55
56
  "@earendil-works/pi-coding-agent": "*"
56
57
  }
57
58
  }
package/policy.ts CHANGED
@@ -15,8 +15,8 @@
15
15
  * trust; the agent-dir policy does not; a valid file with an empty `models`
16
16
  * list disables the generic model. This module must stay in sync with
17
17
  * `readModelSelectors` in pi-compactor's policy.ts (see
18
- * tests/policy-parity.test.ts, which checks the two against each other when
19
- * the sibling checkout is present).
18
+ * tests/policy.test.ts, which always checks the safety contracts and also
19
+ * compares both implementations when the sibling checkout is present).
20
20
  *
21
21
  * Flag-source note (verified against Pi 0.85.1): extension flag values are
22
22
  * populated solely from CLI unknown flags (parseArgs in cli/args.js feeds
package/replay.ts CHANGED
@@ -5,6 +5,7 @@ import { convertResponsesMessages } from "@earendil-works/pi-ai/api/openai-respo
5
5
  import { isObject, type JsonObject, type ResponseItem } from "./protocol";
6
6
 
7
7
  const TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
8
+ const AZURE_TOOL_CALL_PROVIDERS = new Set([...TOOL_CALL_PROVIDERS, "azure"]);
8
9
  export const MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
9
10
  export const SNAPSHOT_TTL_MS = 5 * 60_000;
10
11
 
@@ -19,7 +20,7 @@ export function serializeInput(model: Model<any>, context: TranscriptContext, de
19
20
  const codex = model.api === "openai-codex-responses";
20
21
  const normalized = resolveTranscript(context, flag("supportsMidConvoSystemMessages"));
21
22
  const tools = resolveTranscript(declarations, flag("supportsMidConvoSystemMessages"));
22
- return convertResponsesMessages(model, normalized, model.api === "azure-openai-responses" ? new Set([...TOOL_CALL_PROVIDERS, "azure-openai-responses"]) : TOOL_CALL_PROVIDERS, {
23
+ return convertResponsesMessages(model, normalized, model.api === "azure-openai-responses" ? AZURE_TOOL_CALL_PROVIDERS : TOOL_CALL_PROVIDERS, {
23
24
  includeSystemPrompt: !codex,
24
25
  grammarToolInputProperties: createGrammarToolInputProperties(getDeclaredTools(tools.messages), flag("supportsOpenAIGrammarTools")),
25
26
  supportsMidConvoSystemMessages: flag("supportsMidConvoSystemMessages"),