@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 +5 -5
- package/dist/index.js +19 -10
- package/package.json +7 -6
- package/policy.ts +2 -2
- package/replay.ts +2 -1
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.
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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" ?
|
|
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.
|
|
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": "
|
|
48
|
-
"@earendil-works/pi-agent-core": "
|
|
49
|
-
"@earendil-works/pi-coding-agent": "
|
|
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
|
|
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" ?
|
|
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"),
|