pi-openai-toolkit 0.14.13 → 0.15.1

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/docs/internals.md CHANGED
@@ -1,128 +1,139 @@
1
- # Toolkit Internals
2
-
3
- Developer-facing reference for [pi-openai-toolkit](../README.md). The README keeps this protocol and implementation detail out of the installation path. If you are changing wire behavior, fixtures, or the compaction strategy code, start here.
4
-
5
- ---
6
-
7
- ### Table of Contents
8
-
9
- - [Remote Context protocol](#remote-context-protocol)
10
- - [Rollover lifecycle](#rollover-lifecycle)
11
- - [Remote Compaction v2 wire contract](#remote-compaction-v2-wire-contract)
12
- - [Auto Mode TUI review renderer](#auto-mode-tui-review-renderer)
13
- - [Artifacts and debugging](#artifacts-and-debugging)
14
- - [Provenance](#provenance)
15
-
16
- ---
17
-
18
- ### Remote Context protocol
19
-
20
- Internal alpha endpoints, adapted from `@howaboua/pi-codex-conversion`:
21
-
22
- ```text
23
- POST {base}/alpha/history/v2/list_windows | list_items | read_item | search_contents
24
- POST {base}/alpha/notes/v2/list_files_by_prefix | read_file | search_contents | append_to_file | write_file
25
- POST {base}/alpha/notes/v2/thread_hint
26
- ```
27
-
28
- The native route uses `{base} = <backend-api>/codex` with `Authorization: Bearer <oauth token>`, `ChatGPT-Account-ID`, and Codex CLI headers. The gateway route uses the configured `/v1` base with the API key configured for that provider, `originator: codex_cli_rs`, `X-Codex-Affinity-Scope`, and `X-Codex-Model` carrying the bare model ID. Inherited OAuth headers, cookies, and account IDs are stripped before the request leaves the process. Both routes set `Session-Id` and `X-Client-Request-Id` to the bounded Pi session ID, plus `x-openai-tool-output-truncation-policy` and `x-openai-encrypted-tool-arguments` for endpoints with encrypted parameters. Encrypted results come back as `encrypted_output` and are re-encoded into later requests. These are alpha contracts, so the upstream backend may change them at any time.
29
-
30
- Search text and note bodies are sent under the encrypted-argument policy. Live model requests carry `x-codex-window-id` and `x-codex-turn-metadata` request headers derived from the active window identity.
31
-
32
- Coverage is never inferred from model names. A native session uses provider `openai-codex` with API `openai-codex-responses` for any model ID. A gateway session uses API `openai-responses` and must carry an exact `provider/model` key listed in `compaction.gatewayContextModels`; the list ships empty and there is no built-in gateway SKU. See [src/context-management/codex-provider.ts](../src/context-management/codex-provider.ts). The toolkit registers no provider or model with Pi. It resolves credentials and base URLs through Pi's `ModelRegistry`.
33
-
34
- ---
35
-
36
- ### Rollover lifecycle
37
-
38
- The `new_context` happy path, end to end:
39
-
40
- ```mermaid
41
- sequenceDiagram
42
- accTitle: Remote Context rollover
43
- accDescr: A Pi model checkpoints notes, requests a local new_context boundary, and sends the next request with the new window identity.
44
- participant Model
45
- participant Toolkit
46
- participant Pi
47
- participant Backend
48
- Model->>Toolkit: notes append_to_file (checkpoint)
49
- Toolkit->>Backend: alpha notes request (encrypted args)
50
- Backend-->>Toolkit: encrypted_output
51
- Model->>Toolkit: new_context
52
- Toolkit->>Toolkit: verify successful notes write in this window
53
- Toolkit-->>Model: checkpoint receipt with the successful note path
54
- Toolkit-->>Pi: window marker appended, previous window trim scheduled
55
- Note over Model: Context switch is complete; read the receipt once, then resume the active task
56
- Model->>Backend: next request with the new x-codex-window-id
57
- Pi->>Toolkit: session_before_compact
58
- Toolkit-->>Pi: no-summary boundary consuming the scheduled trim
59
- ```
60
-
61
- State invariants the lifecycle code must keep honest:
62
-
63
- - The checkpoint gate verifies a successful `notes` `append_to_file` or `write_file` against the persisted session branch after the latest window boundary, so it survives restarts and forks. A response with `ok: false` or `success: false` is not successful, even when HTTP returns 200.
64
- - The gate uses the live session/branch identity from `ctx.sessionManager`; cached manager identity must not hide a valid current-window pair, and old or foreign-session evidence must remain rejected.
65
- - Every rollover carries the most recent successful checkpoint path in its handoff message. The optional `thread_hint` is supplemental; a hint failure must not erase the local receipt.
66
- - A successful rollover closes the pre-rollover checkpoint phase. The first turn in the new window reads the receipt once and resumes the active user task; it must not immediately create another checkpoint or call `new_context` as part of that handoff. A later checkpoint is only for a later rollover, and `canRolloverFromCurrentWindow()` enforces it: a window entered by a rollover has to produce a tool result from a tool that is not one of `new_context`, `get_context_remaining`, `history`, or `notes` before it can roll over again.
67
- - Pi 0.86 sends the system prompt and the entire tool loadout as `system` messages inside the transcript: `Agent#createContextSnapshot()` passes messages only, and the Responses transport rebuilds `params.tools` from the leading system message. Trimming everything before a window boundary therefore deletes the model's only tool declaration, and Pi never re-declares it because the persisted transcript still looks unchanged. `preserveSystemHead()` re-anchors the merged pre-boundary state as the leading message of every trimmed window.
68
- - When even that rebuild would lose declared tools, the trim is refused and the full message list is sent: a window that still carries the previous turns is usable, a window with no tools silently degrades into a chat the model can only narrate. Each refusal is reported once per window and lost-tool set through `takeProjectionDiagnostics()`, which writes a `context.projection.tool_loadout_shrink` compaction artifact and warns in the UI.
69
- - The manager also remembers the head it last anchored. When a boundary compaction retires the transcript entry that declared the tools, the branch itself no longer contains a system message, and the remembered head is re-anchored instead of sending a tool-less window. The cache is request-time only, refreshed by every projection, and cleared by `reset()`.
70
- - `new_context` has no force escape hatch: a persisted successful notes checkpoint in the current window is always required before rollover. The model must wait for the notes result to be persisted before retrying.
71
- - After a marker is accepted for sending, the manager keeps a session/window-anchored pending guard until that exact target marker is persisted or the session changes. A duplicate `new_context` during this persistence gap returns `started: false` and sends no second marker; projected/in-memory messages do not retire the guard.
72
- - Budget checks are skipped until the current window has produced its own assistant usage. Acting on the previous window's usage anchor would burn the once-per-window reminder on a false alarm.
73
- - The scheduled trim is consumed exactly once, synchronously, by the first compaction attempt whose boundary window ID matches. Every other compaction path, including a threshold without a scheduled rollover, manual `/compact`, and overflow, is cancelled.
74
- - A window boundary is persisted as a `codex-context-window` custom message. `session_start` replays boundaries from the branch and rebuilds identity after forks.
75
- - Retiring windows is a projection-level act: the session branch keeps every retired window until a compaction consumes the scheduled trim. `evaluateWindowBulk()` measures that gap and compares it with the model about to receive it, `decideBulkCliffAction()` maps the report onto the `leaveManagedMode` policy (`warn` default, `compact`), and `decideBulkCloseOut()` only allows the automatic compaction where a compaction can actually finish: never in print mode (the process exits with the turn and would kill the summary), and at turn end only while a rollover trim is queued, because the window policy cancels every other compaction. Each window and model is surfaced once, and every decision writes a `window-bulk.<action>` compaction artifact with its trigger.
76
- - The close-out runs from `model_select` and `agent_settled`, never from `before_agent_start`: `ExtensionContext.compact()` aborts the running turn, and a hook inside turn processing is exactly the busy lane it must not fight.
77
- - A configured native-fallback summary model is checked against the size of the request Pi is about to send it (`estimateSummarizationRequest`). A narrower summary model than the active one - 272k summarizing a 400k session - returns `model-window-too-small` before authentication or the model call, so the current model gets the first attempt instead of a terminated stream.
78
-
79
- ---
80
-
81
- ### Remote Compaction v2 wire contract
82
-
83
- v2 is what an uncovered session gets. Any model that Remote Context declines, such as a gateway model absent from `gatewayContextModels`, can still compact through v2 when its API appears in `responsesApis`. The two strategies never compete for one session.
84
-
85
- A `compaction_trigger` item appended to the live streaming request yields one output item of `type: "compaction"` with non-empty `encrypted_content`, stored in `CompactionEntry.details.compactedWindow`. On later requests the opaque checkpoint is replayed ahead of live turns, with no text summary. Replay fails closed: if the summary anchor cannot be located, the request is aborted with a notification and a content-free failure artifact. The sentinel-only payload is never sent.
86
-
87
- A v2 response with a missing or empty checkpoint is never stored, and the `nativeFallback` tier is skipped. Pi's own threshold drives the next attempt, which may use `remoteCompactModel` when configured. `remoteCompactModel` must resolve to the same effective base URL as the active model.
88
-
89
- Pi 0.85.1's `session_before_compact` event supplies preparation and branch data, while the direct Remote V2 client can bypass the provider `context` hook chain. To preserve compatibility, an omitted `compaction.remoteV2ContextSource` keeps the original `"legacy"` behavior: first compaction uses `buildSessionContext()` and falls back to `SessionBeforeCompactEvent.preparation`, while recursion uses the opaque window plus the raw branch tail.
90
-
91
- Setting `compaction.remoteV2ContextSource: "pi-context-hook"` opts into a narrow runtime bridge around Pi's public `ExtensionRunner.createContext()`. It adds a non-enumerable `ctx.projectContextForCompaction(messages)` method backed by `ExtensionRunner.emitContext()`. In this opt-in mode, Remote V2 first reads `buildSessionContext()` and then runs the ordered projection. If the bridge, session context, or recursive summary anchor is unavailable, the extension cancels rather than sending an unprojected history. The legacy mode is an intentional compatibility trade-off and does not claim parity with provider-visible context hooks.
92
-
93
- New checkpoints record `inputProvenance: "pi-context-hook-v1"` or `"legacy-raw-context-v1"`, and replay/recursion reject missing or mode-mismatched markers without searching past the latest compaction. Retained `role: "custom"` messages are optional during replay because they may be changed or removed by context hooks; required user, assistant, and complete tool-call/result content remains ordered and fail-closed.
94
-
95
- ---
96
-
97
- ### Auto Mode TUI review renderer
98
-
99
- Auto Mode owns the review decision, but Pi owns the built-in tool-block component. Pi 0.85.1 exports `ToolExecutionComponent` without a public decorator interface, so `src/auto-mode/tool-review-tui.ts` installs a narrow, idempotent compatibility patch on its `render()` method. The patch only reads the component's existing tool-call ID and appends one bounded, single-line status after the normal block output; it never changes tool execution, event ordering, or provider payloads.
100
-
101
- The extension updates an ephemeral per-call state through the same `tool_call` lifecycle that performs the review:
102
-
103
- - `skipped` when the tool is outside the configured Auto Mode gate;
104
- - `reviewing` while the reviewer model is running;
105
- - `awaiting-user` when an unavailable review reaches interactive confirmation;
106
- - `allowed`, `denied`, or `blocked` after the final decision.
107
-
108
- The state is intentionally not persisted to the session. It is bounded to recent calls and cleared at `session_start`, so reloading a session does not invent historical approval claims. The renderer uses Pi's current theme when available, truncates untrusted rationale text, and preserves the original render output when no state exists.
109
-
110
- This is a version-coupled adapter, not a stable Pi extension contract. It is feature-detected at registration time and marked with a global symbol so extension reloads do not wrap the class repeatedly. If the exported component shape changes or becomes non-writable, the adapter becomes a no-op, emits one warning in TUI mode, and the existing footer/working-message review indicators remain the fallback.
111
-
112
- ---
113
-
114
- ### Artifacts and debugging
115
-
116
- With `debug: true`, the toolkit writes lifecycle and compaction artifacts under `artifactRoot` (default `~/.pi/agent/artifacts/pi-openai-toolkit/compaction`):
117
-
118
- - Each `session_start` writes a lifecycle artifact whose `activation` field records `active` or the exact inactive reason, such as `unsupported-model`, `unsupported-api`, `missing-api-key`, `auth-resolution-failed`, or `tool-name-conflict`.
119
- - `logProviderPayloads` additionally writes provider request metadata and payloads, while `logCompactResponses` writes compact status and structural summaries with complete response and body fields critically redacted. Keep both off unless inspecting a specific request.
120
- - Artifacts always redact Authorization credentials, API keys and tokens, Codex account IDs, and opaque `encrypted_content` or `encrypted_output`, even when `redactSensitiveData` is `false`.
121
-
122
- ---
123
-
124
- ### Provenance
125
-
126
- The Remote Context protocol was reverse-engineered from `@howaboua/pi-codex-conversion@3.0.29` (source commit `7021ae48e8efe36a3becc5830d529696ff798e5e`). The Astra compatibility layer is adapted from Oh My Pi 18.1.8 (MIT). Web Search behavior was adapted from [pi-openai-web-search](https://github.com/code-yeongyu/pi-openai-web-search) (commit `3964338`). Attribution details are in [NOTICE](../NOTICE).
127
-
128
- Protocol fixtures live in [src/context-management/](../src/context-management/) and must be updated together with any upstream alpha endpoint change. New wire behavior requires fixture-based tests.
1
+ # Toolkit Internals
2
+
3
+ Developer-facing reference for [pi-openai-toolkit](../README.md). The README keeps this protocol and implementation detail out of the installation path. If you are changing wire behavior, fixtures, or the compaction strategy code, start here.
4
+
5
+ ---
6
+
7
+ ### Table of Contents
8
+
9
+ - [Configuration resolution](#configuration-resolution)
10
+ - [Remote Context protocol](#remote-context-protocol)
11
+ - [Rollover lifecycle](#rollover-lifecycle)
12
+ - [Remote Compaction v2 wire contract](#remote-compaction-v2-wire-contract)
13
+ - [Auto Mode TUI review renderer](#auto-mode-tui-review-renderer)
14
+ - [Artifacts and debugging](#artifacts-and-debugging)
15
+ - [Provenance](#provenance)
16
+
17
+ ---
18
+
19
+ ### Configuration resolution
20
+
21
+ The single global document is loaded through [src/config.ts](../src/config.ts). [src/config/legacy.ts](../src/config/legacy.ts) owns unversioned compatibility; [src/config/v2.ts](../src/config/v2.ts) validates v2 scopes, resolves exact policy, and records leaf origins. [src/config/policy.ts](../src/config/policy.ts) defines effective values independently of persisted session data. See the [configuration reference](configuration.md) for supported fields and migration limits.
22
+
23
+ Feature entrypoints resolve once per public operation and pass that immutable snapshot through awaited helpers. A separately named compaction producer is resolved from the same decoded document for its own identity. Subsequent callbacks reload; there is no cross-event provider transaction, project lookup, singleton cache, or config state writer. Internal legacy-shaped engine adapters preserve compaction/search algorithms without exposing v2 parsing to consumers. Cross-feature gateway policy belongs to the resolver, not the context enable switch.
24
+
25
+ Selected invalid policy blocks dependent operations; unrelated model errors remain reportable without replacing the selected model. An engaged auto gate stays blocking when its policy becomes invalid. Image choices are checked before auth, uploads, or paid requests. The human `/toolkit-config` command inspects effective values, validation, or a migration candidate without action APIs or network access. Migration is preview-only; source bytes and unknown/dormant content stay untouched.
26
+
27
+ ---
28
+
29
+ ### Remote Context protocol
30
+
31
+ Internal alpha endpoints, adapted from `@howaboua/pi-codex-conversion`:
32
+
33
+ ```text
34
+ POST {base}/alpha/history/v2/list_windows | list_items | read_item | search_contents
35
+ POST {base}/alpha/notes/v2/list_files_by_prefix | read_file | search_contents | append_to_file | write_file
36
+ POST {base}/alpha/notes/v2/thread_hint
37
+ ```
38
+
39
+ The native route uses `{base} = <backend-api>/codex` with `Authorization: Bearer <oauth token>`, `ChatGPT-Account-ID`, and Codex CLI headers. The gateway route uses the configured `/v1` base with the API key configured for that provider, `originator: codex_cli_rs`, `X-Codex-Affinity-Scope`, and `X-Codex-Model` carrying the bare model ID. Inherited OAuth headers, cookies, and account IDs are stripped before the request leaves the process. Both routes set `Session-Id` and `X-Client-Request-Id` to the bounded Pi session ID, plus `x-openai-tool-output-truncation-policy` and `x-openai-encrypted-tool-arguments` for endpoints with encrypted parameters. Encrypted results come back as `encrypted_output` and are re-encoded into later requests. These are alpha contracts, so the upstream backend may change them at any time.
40
+
41
+ Search text and note bodies are sent under the encrypted-argument policy. Live model requests carry `x-codex-window-id` and `x-codex-turn-metadata` request headers derived from the active window identity.
42
+
43
+ Coverage is never inferred from model names. A native session uses provider `openai-codex` with API `openai-codex-responses` for any model ID. A gateway session uses API `openai-responses` and must carry an exact `provider/model` key whose `models[exact].compatibility.transport` is `"codex-gateway"` (legacy `compaction.gatewayContextModels`); no gateway is opted in by default. See [src/context-management/codex-provider.ts](../src/context-management/codex-provider.ts). The toolkit registers no provider or model with Pi. It resolves credentials and base URLs through Pi's `ModelRegistry`.
44
+
45
+ ---
46
+
47
+ ### Rollover lifecycle
48
+
49
+ The `new_context` happy path, end to end:
50
+
51
+ ```mermaid
52
+ sequenceDiagram
53
+ accTitle: Remote Context rollover
54
+ accDescr: A Pi model checkpoints notes, requests a local new_context boundary, and sends the next request with the new window identity.
55
+ participant Model
56
+ participant Toolkit
57
+ participant Pi
58
+ participant Backend
59
+ Model->>Toolkit: notes append_to_file (checkpoint)
60
+ Toolkit->>Backend: alpha notes request (encrypted args)
61
+ Backend-->>Toolkit: encrypted_output
62
+ Model->>Toolkit: new_context
63
+ Toolkit->>Toolkit: verify successful notes write in this window
64
+ Toolkit-->>Model: checkpoint receipt with the successful note path
65
+ Toolkit-->>Pi: window marker appended, previous window trim scheduled
66
+ Note over Model: Context switch is complete; read the receipt once, then resume the active task
67
+ Model->>Backend: next request with the new x-codex-window-id
68
+ Pi->>Toolkit: session_before_compact
69
+ Toolkit-->>Pi: no-summary boundary consuming the scheduled trim
70
+ ```
71
+
72
+ State invariants the lifecycle code must keep honest:
73
+
74
+ - The checkpoint gate verifies a successful `notes` `append_to_file` or `write_file` against the persisted session branch after the latest window boundary, so it survives restarts and forks. A response with `ok: false` or `success: false` is not successful, even when HTTP returns 200.
75
+ - The gate uses the live session/branch identity from `ctx.sessionManager`; cached manager identity must not hide a valid current-window pair, and old or foreign-session evidence must remain rejected.
76
+ - Every rollover carries the most recent successful checkpoint path in its handoff message. The optional `thread_hint` is supplemental; a hint failure must not erase the local receipt.
77
+ - A successful rollover closes the pre-rollover checkpoint phase. The first turn in the new window reads the receipt once and resumes the active user task; it must not immediately create another checkpoint or call `new_context` as part of that handoff. A later checkpoint is only for a later rollover, and `canRolloverFromCurrentWindow()` enforces it: a window entered by a rollover has to produce a tool result from a tool that is not one of `new_context`, `get_context_remaining`, `history`, or `notes` before it can roll over again.
78
+ - Pi 0.86 sends the system prompt and the entire tool loadout as `system` messages inside the transcript: `Agent#createContextSnapshot()` passes messages only, and the Responses transport rebuilds `params.tools` from the leading system message. Trimming everything before a window boundary therefore deletes the model's only tool declaration, and Pi never re-declares it because the persisted transcript still looks unchanged. `preserveSystemHead()` re-anchors the merged pre-boundary state as the leading message of every trimmed window.
79
+ - When even that rebuild would lose declared tools, the trim is refused and the full message list is sent: a window that still carries the previous turns is usable, a window with no tools silently degrades into a chat the model can only narrate. Each refusal is reported once per window and lost-tool set through `takeProjectionDiagnostics()`, which writes a `context.projection.tool_loadout_shrink` compaction artifact and warns in the UI.
80
+ - The manager also remembers the head it last anchored. When a boundary compaction retires the transcript entry that declared the tools, the branch itself no longer contains a system message, and the remembered head is re-anchored instead of sending a tool-less window. The cache is request-time only, refreshed by every projection, and cleared by `reset()`.
81
+ - `new_context` has no force escape hatch: a persisted successful notes checkpoint in the current window is always required before rollover. The model must wait for the notes result to be persisted before retrying.
82
+ - After a marker is accepted for sending, the manager keeps a session/window-anchored pending guard until that exact target marker is persisted or the session changes. A duplicate `new_context` during this persistence gap returns `started: false` and sends no second marker; projected/in-memory messages do not retire the guard.
83
+ - Budget checks are skipped until the current window has produced its own assistant usage. Acting on the previous window's usage anchor would burn the once-per-window reminder on a false alarm.
84
+ - The scheduled trim is consumed exactly once, synchronously, by the first compaction attempt whose boundary window ID matches. Every other compaction path, including a threshold without a scheduled rollover, manual `/compact`, and overflow, is cancelled.
85
+ - A window boundary is persisted as a `codex-context-window` custom message. `session_start` replays boundaries from the branch and rebuilds identity after forks.
86
+ - Retiring windows is a projection-level act: the session branch keeps every retired window until a compaction consumes the scheduled trim. `evaluateWindowBulk()` measures that gap and compares it with the model about to receive it, `decideBulkCliffAction()` maps the report onto the `leaveManagedMode` policy (`warn` default, `compact`), and `decideBulkCloseOut()` only allows the automatic compaction where a compaction can actually finish: never in print mode (the process exits with the turn and would kill the summary), and at turn end only while a rollover trim is queued, because the window policy cancels every other compaction. Each window and model is surfaced once, and every decision writes a `window-bulk.<action>` compaction artifact with its trigger.
87
+ - The close-out runs from `model_select` and `agent_settled`, never from `before_agent_start`: `ExtensionContext.compact()` aborts the running turn, and a hook inside turn processing is exactly the busy lane it must not fight.
88
+ - A configured native-fallback summary model is checked against the size of the request Pi is about to send it (`estimateSummarizationRequest`). A narrower summary model than the active one - 272k summarizing a 400k session - returns `model-window-too-small` before authentication or the model call, so the current model gets the first attempt instead of a terminated stream.
89
+
90
+ ---
91
+
92
+ ### Remote Compaction v2 wire contract
93
+
94
+ v2 is what an uncovered session gets. Any model that Remote Context declines, such as a gateway without an exact transport opt-in, can still compact through v2 when its API appears in `context.remoteCompaction.apis`. The two strategies never compete for one session.
95
+
96
+ A `compaction_trigger` item appended to the live streaming request yields one output item of `type: "compaction"` with non-empty `encrypted_content`, stored in `CompactionEntry.details.compactedWindow`. On later requests the opaque checkpoint is replayed ahead of live turns, with no text summary. Replay fails closed: if the summary anchor cannot be located, the request is aborted with a notification and a content-free failure artifact. The sentinel-only payload is never sent.
97
+
98
+ A v2 response with a missing or empty checkpoint is never stored, and the `nativeFallback` tier is skipped. Pi's own threshold drives the next attempt, which may use `remoteCompactModel` when configured. `remoteCompactModel` must resolve to the same effective base URL as the active model.
99
+
100
+ Pi 0.85.1's `session_before_compact` event supplies preparation and branch data, while the direct Remote V2 client can bypass the provider `context` hook chain. To preserve compatibility, an omitted `context.remoteCompaction.inputSource` keeps the original `"legacy"` behavior: first compaction uses `buildSessionContext()` and falls back to `SessionBeforeCompactEvent.preparation`, while recursion uses the opaque window plus the raw branch tail.
101
+
102
+ Setting `context.remoteCompaction.inputSource: "pi-context-hook"` opts into a narrow runtime bridge around Pi's public `ExtensionRunner.createContext()`. It adds a non-enumerable `ctx.projectContextForCompaction(messages)` method backed by `ExtensionRunner.emitContext()`. In this opt-in mode, Remote V2 first reads `buildSessionContext()` and then runs the ordered projection. If the bridge, session context, or recursive summary anchor is unavailable, the extension cancels rather than sending an unprojected history. The legacy mode is an intentional compatibility trade-off and does not claim parity with provider-visible context hooks.
103
+
104
+ New checkpoints record `inputProvenance: "pi-context-hook-v1"` or `"legacy-raw-context-v1"`, and replay/recursion reject missing or mode-mismatched markers without searching past the latest compaction. Retained `role: "custom"` messages are optional during replay because they may be changed or removed by context hooks; required user, assistant, and complete tool-call/result content remains ordered and fail-closed.
105
+
106
+ ---
107
+
108
+ ### Auto Mode TUI review renderer
109
+
110
+ Auto Mode owns the review decision, but Pi owns the built-in tool-block component. Pi 0.85.1 exports `ToolExecutionComponent` without a public decorator interface, so `src/auto-mode/tool-review-tui.ts` installs a narrow, idempotent compatibility patch on its `render()` method. The patch only reads the component's existing tool-call ID and appends one bounded, single-line status after the normal block output; it never changes tool execution, event ordering, or provider payloads.
111
+
112
+ The extension updates an ephemeral per-call state through the same `tool_call` lifecycle that performs the review:
113
+
114
+ - `skipped` when the tool is outside the configured Auto Mode gate;
115
+ - `reviewing` while the reviewer model is running;
116
+ - `awaiting-user` when an unavailable review reaches interactive confirmation;
117
+ - `allowed`, `denied`, or `blocked` after the final decision.
118
+
119
+ The state is intentionally not persisted to the session. It is bounded to recent calls and cleared at `session_start`, so reloading a session does not invent historical approval claims. The renderer uses Pi's current theme when available, truncates untrusted rationale text, and preserves the original render output when no state exists.
120
+
121
+ This is a version-coupled adapter, not a stable Pi extension contract. It is feature-detected at registration time and marked with a global symbol so extension reloads do not wrap the class repeatedly. If the exported component shape changes or becomes non-writable, the adapter becomes a no-op, emits one warning in TUI mode, and the existing footer/working-message review indicators remain the fallback.
122
+
123
+ ---
124
+
125
+ ### Artifacts and debugging
126
+
127
+ With `diagnostics.level: "debug"`, the toolkit writes lifecycle and compaction artifacts under `diagnostics.artifactRoot` (default `~/.pi/agent/artifacts/pi-openai-toolkit/compaction`):
128
+
129
+ - Each `session_start` writes a lifecycle artifact whose `activation` field records `active` or the exact inactive reason, such as `unsupported-model`, `unsupported-api`, `missing-api-key`, `auth-resolution-failed`, or `tool-name-conflict`.
130
+ - `diagnostics.captureRequests` independently writes provider request metadata and payloads, while `diagnostics.captureResponses` writes compact status and structural summaries with complete response and body fields critically redacted. Keep both off unless inspecting a specific request.
131
+ - Artifacts always redact Authorization credentials, API keys and tokens, Codex account IDs, and opaque `encrypted_content` or `encrypted_output`, even when `diagnostics.redactSensitiveData` is `false`.
132
+
133
+ ---
134
+
135
+ ### Provenance
136
+
137
+ The Remote Context protocol was reverse-engineered from `@howaboua/pi-codex-conversion@3.0.29` (source commit `7021ae48e8efe36a3becc5830d529696ff798e5e`). The Astra compatibility layer is adapted from Oh My Pi 18.1.8 (MIT). Web Search behavior was adapted from [pi-openai-web-search](https://github.com/code-yeongyu/pi-openai-web-search) (commit `3964338`). Attribution details are in [NOTICE](../NOTICE).
138
+
139
+ Protocol fixtures live in [src/context-management/](../src/context-management/) and must be updated together with any upstream alpha endpoint change. New wire behavior requires fixture-based tests.
package/package.json CHANGED
@@ -1,127 +1,135 @@
1
- {
2
- "name": "pi-openai-toolkit",
3
- "version": "0.14.13",
4
- "private": false,
5
- "type": "module",
6
- "engines": {
7
- "node": ">=22.19.0"
8
- },
9
- "description": "OpenAI toolkit for Pi: Codex Remote Context windows, remote compaction v2, routed Web Search, image gen, auto mode.",
10
- "author": "awoaCrim and contributors",
11
- "license": "MIT",
12
- "repository": {
13
- "type": "git",
14
- "url": "git+https://github.com/awoaCrim/pi-openai-toolkit.git"
15
- },
16
- "bugs": {
17
- "url": "https://github.com/awoaCrim/pi-openai-toolkit/issues"
18
- },
19
- "homepage": "https://github.com/awoaCrim/pi-openai-toolkit#readme",
20
- "publishConfig": {
21
- "access": "public",
22
- "registry": "https://registry.npmjs.org/"
23
- },
24
- "keywords": [
25
- "pi-package",
26
- "pi-extension",
27
- "openai",
28
- "compaction",
29
- "web-search",
30
- "image-generation",
31
- "auto-mode",
32
- "codex",
33
- "responses"
34
- ],
35
- "pi": {
36
- "extensions": [
37
- "./src/extension-runtime.ts",
38
- "./src/web-search/extension.ts",
39
- "./src/image-generation/extension.ts",
40
- "./src/auto-mode/extension.ts",
41
- "./src/codex-astra/extension.ts"
42
- ]
43
- },
44
- "files": [
45
- "src/compact-client.ts",
46
- "src/compaction-projection.ts",
47
- "src/pi-context-hook.ts",
48
- "src/config.ts",
49
- "src/debug.ts",
50
- "src/details-store.ts",
51
- "src/deferred-tool-carryover.ts",
52
- "src/extension-runtime.ts",
53
- "src/context-management/types.ts",
54
- "src/context-management/messages.ts",
55
- "src/context-management/window-budget.ts",
56
- "src/context-management/window-request.ts",
57
- "src/context-management/window-bulk.ts",
58
- "src/context-management/window-manager.ts",
59
- "src/context-management/codex-provider.ts",
60
- "src/context-management/history-notes.ts",
61
- "src/context-management/tools.ts",
62
- "src/context-management/namespace-tools.ts",
63
- "src/context-management/tool-contract.ts",
64
- "src/image-generation/artifacts.ts",
65
- "src/image-generation/client.ts",
66
- "src/image-generation/eligibility.ts",
67
- "src/image-generation/extension.ts",
68
- "src/image-generation/protocol.ts",
69
- "src/image-generation/references.ts",
70
- "src/image-generation/render.ts",
71
- "src/image-generation/service.ts",
72
- "src/image-generation/types.ts",
73
- "src/auto-mode/extension.ts",
74
- "src/auto-mode/tool-review-tui.ts",
75
- "src/auto-mode/policy.ts",
76
- "src/auto-mode/prompt.ts",
77
- "src/auto-mode/transcript.ts",
78
- "src/auto-mode/reviewer.ts",
79
- "src/auto-mode/classifier.ts",
80
- "src/auto-mode/types.ts",
81
- "src/codex-astra/effort-planner.ts",
82
- "src/codex-astra/extension.ts",
83
- "src/model-scope.ts",
84
- "src/native-fallback.ts",
85
- "src/payload-rewrite.ts",
86
- "src/provider-headers.ts",
87
- "src/responses-headers.ts",
88
- "src/remote-v2-client.ts",
89
- "src/request-context-cache.ts",
90
- "src/runtime.ts",
91
- "src/serializer.ts",
92
- "src/supported-environment.ts",
93
- "src/types.ts",
94
- "src/web-search/alpha-client.ts",
95
- "src/web-search/extension.ts",
96
- "src/web-search/payload.ts",
97
- "src/web-search/prompt.ts",
98
- "src/web-search/types.ts",
99
- "docs/internals.md",
100
- "README.md",
101
- "README.zh.md",
102
- "LICENSE",
103
- "NOTICE",
104
- "tsconfig.check.json"
105
- ],
106
- "scripts": {
107
- "test": "bun test",
108
- "test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov && node ./scripts/check-lcov.mjs ./coverage/lcov.info",
109
- "test:pi": "bun test ./test/pi-smoke.test.ts",
110
- "typecheck": "tsc -p tsconfig.check.json"
111
- },
112
- "peerDependencies": {
113
- "@earendil-works/pi-agent-core": ">=0.85.1",
114
- "@earendil-works/pi-ai": ">=0.85.1",
115
- "@earendil-works/pi-coding-agent": ">=0.85.1",
116
- "@earendil-works/pi-tui": ">=0.85.1"
117
- },
118
- "devDependencies": {
119
- "@earendil-works/pi-agent-core": "0.86.0",
120
- "@earendil-works/pi-ai": "0.86.0",
121
- "@earendil-works/pi-coding-agent": "0.86.0",
122
- "@earendil-works/pi-tui": "0.86.0",
123
- "@types/node": "24.12.4",
124
- "bun": "1.2.23",
125
- "typescript": "5.9.3"
126
- }
127
- }
1
+ {
2
+ "name": "pi-openai-toolkit",
3
+ "version": "0.15.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.19.0"
8
+ },
9
+ "description": "OpenAI toolkit for Pi: Codex Remote Context windows, remote compaction v2, routed Web Search, image gen, auto mode.",
10
+ "author": "awoaCrim and contributors",
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/awoaCrim/pi-openai-toolkit.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/awoaCrim/pi-openai-toolkit/issues"
18
+ },
19
+ "homepage": "https://github.com/awoaCrim/pi-openai-toolkit#readme",
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org/"
23
+ },
24
+ "keywords": [
25
+ "pi-package",
26
+ "pi-extension",
27
+ "openai",
28
+ "compaction",
29
+ "web-search",
30
+ "image-generation",
31
+ "auto-mode",
32
+ "codex",
33
+ "responses"
34
+ ],
35
+ "pi": {
36
+ "extensions": [
37
+ "./src/extension-runtime.ts",
38
+ "./src/web-search/extension.ts",
39
+ "./src/image-generation/extension.ts",
40
+ "./src/auto-mode/extension.ts",
41
+ "./src/codex-astra/extension.ts"
42
+ ]
43
+ },
44
+ "files": [
45
+ "src/compact-client.ts",
46
+ "src/compaction-projection.ts",
47
+ "src/pi-context-hook.ts",
48
+ "src/config.ts",
49
+ "src/config-command.ts",
50
+ "src/config/policy.ts",
51
+ "src/config/legacy.ts",
52
+ "src/config/v2.ts",
53
+ "src/config/migration.ts",
54
+ "src/config/notifications.ts",
55
+ "config.schema.json",
56
+ "src/debug.ts",
57
+ "src/details-store.ts",
58
+ "src/deferred-tool-carryover.ts",
59
+ "src/extension-runtime.ts",
60
+ "src/context-management/types.ts",
61
+ "src/context-management/messages.ts",
62
+ "src/context-management/window-budget.ts",
63
+ "src/context-management/window-request.ts",
64
+ "src/context-management/window-bulk.ts",
65
+ "src/context-management/window-manager.ts",
66
+ "src/context-management/codex-provider.ts",
67
+ "src/context-management/history-notes.ts",
68
+ "src/context-management/tools.ts",
69
+ "src/context-management/namespace-tools.ts",
70
+ "src/context-management/tool-contract.ts",
71
+ "src/image-generation/artifacts.ts",
72
+ "src/image-generation/client.ts",
73
+ "src/image-generation/eligibility.ts",
74
+ "src/image-generation/extension.ts",
75
+ "src/image-generation/protocol.ts",
76
+ "src/image-generation/references.ts",
77
+ "src/image-generation/render.ts",
78
+ "src/image-generation/service.ts",
79
+ "src/image-generation/types.ts",
80
+ "src/auto-mode/extension.ts",
81
+ "src/auto-mode/tool-review-tui.ts",
82
+ "src/auto-mode/policy.ts",
83
+ "src/auto-mode/prompt.ts",
84
+ "src/auto-mode/transcript.ts",
85
+ "src/auto-mode/reviewer.ts",
86
+ "src/auto-mode/classifier.ts",
87
+ "src/auto-mode/types.ts",
88
+ "src/codex-astra/effort-planner.ts",
89
+ "src/codex-astra/extension.ts",
90
+ "src/model-scope.ts",
91
+ "src/native-fallback.ts",
92
+ "src/payload-rewrite.ts",
93
+ "src/provider-headers.ts",
94
+ "src/responses-headers.ts",
95
+ "src/remote-v2-client.ts",
96
+ "src/request-context-cache.ts",
97
+ "src/runtime.ts",
98
+ "src/serializer.ts",
99
+ "src/supported-environment.ts",
100
+ "src/types.ts",
101
+ "src/web-search/alpha-client.ts",
102
+ "src/web-search/extension.ts",
103
+ "src/web-search/payload.ts",
104
+ "src/web-search/prompt.ts",
105
+ "src/web-search/types.ts",
106
+ "docs/internals.md",
107
+ "docs/configuration.md",
108
+ "README.md",
109
+ "README.zh.md",
110
+ "LICENSE",
111
+ "NOTICE",
112
+ "tsconfig.check.json"
113
+ ],
114
+ "scripts": {
115
+ "test": "bun test",
116
+ "test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov && node ./scripts/check-lcov.mjs ./coverage/lcov.info",
117
+ "test:pi": "bun test ./test/pi-smoke.test.ts",
118
+ "typecheck": "tsc -p tsconfig.check.json"
119
+ },
120
+ "peerDependencies": {
121
+ "@earendil-works/pi-agent-core": ">=0.85.1",
122
+ "@earendil-works/pi-ai": ">=0.85.1",
123
+ "@earendil-works/pi-coding-agent": ">=0.85.1",
124
+ "@earendil-works/pi-tui": ">=0.85.1"
125
+ },
126
+ "devDependencies": {
127
+ "@earendil-works/pi-agent-core": "0.86.0",
128
+ "@earendil-works/pi-ai": "0.86.0",
129
+ "@earendil-works/pi-coding-agent": "0.86.0",
130
+ "@earendil-works/pi-tui": "0.86.0",
131
+ "@types/node": "24.12.4",
132
+ "bun": "1.2.23",
133
+ "typescript": "5.9.3"
134
+ }
135
+ }