pi-openai-codex-compat 0.0.2 → 0.0.4

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.
Files changed (29) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +86 -38
  3. package/extensions/openai-codex-compat/apply-patch-diff-render.ts +6 -2
  4. package/extensions/openai-codex-compat/apply-patch-engine.ts +89 -5
  5. package/extensions/openai-codex-compat/apply-patch.ts +4 -5
  6. package/extensions/openai-codex-compat/codex-cache-diagnostics.ts +97 -0
  7. package/extensions/openai-codex-compat/codex-cache-key.ts +9 -0
  8. package/extensions/openai-codex-compat/codex-installation.ts +51 -0
  9. package/extensions/openai-codex-compat/codex-metadata.ts +139 -0
  10. package/extensions/openai-codex-compat/codex-protocol.ts +4 -2
  11. package/extensions/openai-codex-compat/codex-provider.ts +708 -128
  12. package/extensions/openai-codex-compat/codex-stream.ts +137 -40
  13. package/extensions/openai-codex-compat/codex-thread-lineage.ts +156 -0
  14. package/extensions/openai-codex-compat/codex-transport.ts +1795 -199
  15. package/extensions/openai-codex-compat/compaction-checkpoint.ts +2 -2
  16. package/extensions/openai-codex-compat/config.ts +15 -2
  17. package/extensions/openai-codex-compat/image-generation-schema.ts +37 -0
  18. package/extensions/openai-codex-compat/image-generation.ts +25 -48
  19. package/extensions/openai-codex-compat/index.ts +13 -0
  20. package/extensions/openai-codex-compat/namespaced-tools.ts +2 -0
  21. package/extensions/openai-codex-compat/output-limit-continuation.ts +151 -0
  22. package/extensions/openai-codex-compat/provider-error.ts +79 -0
  23. package/extensions/openai-codex-compat/remote-compaction.ts +13 -0
  24. package/extensions/openai-codex-compat/request-options.ts +2 -2
  25. package/extensions/openai-codex-compat/responses-lite.ts +147 -0
  26. package/extensions/openai-codex-compat/responses-replay.ts +0 -7
  27. package/extensions/openai-codex-compat/settings-pane.ts +11 -0
  28. package/extensions/openai-codex-compat/web-run.ts +7 -0
  29. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,92 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.4 - 2026-08-07
6
+
7
+ ### Added
8
+
9
+ - Persist and send the official Codex installation/window request identity used for backend affinity, advancing the window after successful compaction.
10
+ - Persist structured Codex transport-recovery diagnostics on assistant messages for rejected or locally bypassed WebSocket continuations and WebSocket-to-SSE recovery, including request modes, cache-affinity preservation, and exact response ids.
11
+ - Prewarm only the static instruction/tool prefix with Responses v2 `generate: false`, generate the dynamic conversation from its continuation, and persist whether prewarm established continuation state.
12
+ - Send Pi-derived Codex session, thread, turn, and request-kind metadata through `client_metadata` and compatible request headers, with one turn id shared across each Pi agent run.
13
+ - Capture server-issued Codex turn state once per agent run, replay it across WebSocket retries, SSE requests, and WebSocket-to-SSE fallback, and include the exact value in transport diagnostics.
14
+ - Persist cache diagnostics with exact session, account, cache, turn, response, and routing-state identifiers alongside transport selection, full/delta request sizes, static-prefix fingerprints, and reported cache read/write usage.
15
+ - Record the exact baseline and current history items when local WebSocket continuation validation rejects a changed prefix.
16
+ - Persist hidden thread markers for finalized `/tree` branches, send branch-specific Codex thread/fork/window lineage while retaining the root prompt-cache identity, and reset WebSocket continuation state when switching threads.
17
+ - Add a default-off `responsesLite` setting, environment override, and `/codex-settings` control for switching supported GPT-5.6 models between ordinary Responses and Responses Lite.
18
+ - Use Codex's Responses Lite input envelope, metadata, request header, tool grouping, and all-turn reasoning context for GPT-5.6 Sol, Terra, and Luna.
19
+
20
+ ### Changed
21
+
22
+ - Align Responses Lite with current Codex default-tool namespaces, HTTP/WebSocket metadata placement, prewarm identity, and per-request WebSocket timing metadata.
23
+ - Align ordinary function declarations with Codex's explicit `strict: false` and ignore Pi's generic temperature option because Codex Responses has no temperature request field.
24
+ - Send the official thread-scoped `x-client-request-id` on Responses HTTP/SSE requests and require all prompt-cache affinity identifiers to match before diagnostics report alignment.
25
+ - Continue provider-owned sampling when `response.completed.end_turn` is false, and resample retryable `response.failed` and `response.incomplete` events from the completed output history already received.
26
+ - Send official nested Responses Compaction v2 metadata, classifying manual, threshold, provider-boundary, and overflow compaction by trigger, reason, phase, implementation, and strategy.
27
+ - Align first-party Codex Responses, WebSocket, and compaction requests with the official model-and-tier routing hint.
28
+ - Match official Codex request continuity more closely by always serializing the ordinary `tools` array, omitting the legacy SSE beta header, ignoring response-only stream options and internal item metadata during WebSocket delta comparison, retaining healthy session sockets, and applying jittered retry backoff.
29
+ - Resample retryable SSE HTTP failures and dropped streams up to five times before model-visible output while preserving cache and account identity; fail closed after visible output.
30
+ - Retry retryable WebSocket failures on up to five fresh connections before selecting sticky SSE when no model-visible output has been emitted.
31
+ - Treat WebSocket response metadata as non-visible during retry decisions and send the Responses Lite HTTP marker only on SSE while retaining its WebSocket body metadata.
32
+
33
+ ### Fixed
34
+
35
+ - Preserve completed native output items, discard only unfinished attempt content, and accumulate usage across provider-owned response resampling.
36
+ - Normalize missing, empty, and `functions` namespaces for Responses Lite function and custom calls, preserve the server's flat default calls in continuation history, and reject invalid default-namespace members before sending.
37
+ - Omit `reasoning.mode` for the default GPT-5.6 standard mode and send the field only when pro mode is selected.
38
+ - Continue interrupted Codex tasks automatically after an exact output-token-limit response, including after successful Pi threshold compaction, without treating incomplete WebSocket responses as completed continuation state.
39
+ - Render delete-and-recreate replacements and repeated in-place `apply_patch` operations as one logical file with a recomputed initial-to-final diff instead of counting the path multiple times.
40
+
41
+ ## 0.0.3 - 2026-08-04
42
+
43
+ ### Added
44
+
45
+ - Expose Pi AI-compatible Codex WebSocket debug statistics, reset helpers, and force-close control for connection reuse, continuation deltas, failures, and SSE fallback.
46
+
47
+ ### Changed
48
+
49
+ - Rely on Pi's compaction indicator instead of adding a temporary third footer status line during percentage-triggered compaction.
50
+ - Adapt the `image_gen.imagegen` descriptions and prompt metadata to Pi while retaining the server-reserved schema.
51
+ - Normalize local image paths before reading them and enforce image-count bounds and edit-selector exclusivity in the executor.
52
+ - Add a concise `web.run` prompt snippet and four high-signal system-prompt guidelines derived from its official description.
53
+ - Clarify the `apply_patch` prompt snippet, format its system-prompt guidance consistently, and omit the redundant Python file-I/O reminder.
54
+ - Disable standalone `web.run` and hosted web search by default; both remain available through settings or environment overrides.
55
+
56
+ ### Fixed
57
+
58
+ - Report WebSocket close codes and reasons instead of `[object CloseEvent]`, and preserve an underlying WebSocket error when a close event follows it.
59
+ - Scope sticky Codex WebSockets to the authenticated account so changing accounts cannot reuse a connection authorized for another account.
60
+ - Recover once from expired WebSocket continuations and connection-limit responses while preserving structured Codex API and protocol errors.
61
+ - Finish SSE requests as soon as a terminal response arrives, even if the response body remains open, and reject pre-aborted cached-WebSocket requests before sending.
62
+ - Apply `cacheRetention: "none"` consistently to ordinary and compaction payloads and clamp cache-affinity headers to the backend's 64-character limit.
63
+ - Keep SSE fallback sticky after midstream WebSocket failures and use the fallback for later requests regardless of the preferred WebSocket mode.
64
+ - Validate transport timeouts, allow zero to disable the WebSocket connect timeout, report SSE header timeouts clearly, and honor bounded `Retry-After` guidance.
65
+ - Surface concise structured Codex HTTP errors, fail closed on malformed WebSocket events, and retain WebSocket-to-SSE recovery diagnostics on assistant messages.
66
+ - Distinguish output-token truncation from other incomplete Codex responses and preserve the provider's incomplete reason.
67
+ - Match Pi AI's Codex stream lifecycle by delaying `start` until transport readiness, cleaning parser scratch state on failure, and normalizing structured or non-Error failures.
68
+ - Honor every non-undefined payload replacement and reject missing `streamSimple` authentication synchronously, matching Pi AI.
69
+ - Match Pi AI's configured SSE retry behavior for callback, body-read, and otherwise non-terminal response-acquisition failures.
70
+ - Use Pi AI's time-ordered UUIDv7 identifiers for WebSocket requests without session affinity.
71
+ - Stop at terminal WebSocket events and honor aborts while requests wait for session transport ownership or finish streaming.
72
+ - Ignore type-less Codex events before transport start and retain WebSocket continuation IDs supplied by `response.created`.
73
+ - Validate Codex authentication before request hooks, template capture, or native compaction work.
74
+ - Apply service-tier pricing to unsuccessful terminal responses and match Pi AI when total-token usage is absent.
75
+ - Match Pi AI's final-answer phase state, reasoning-part separators, and suppression of empty final tool-call deltas.
76
+ - Retry generic Codex usage-limit responses according to Pi AI's configured SSE retry policy.
77
+ - Require Pi AI's order-sensitive request and history equality before sending cached WebSocket deltas.
78
+ - Cache the exact canonical or native assistant representation replayed by the next turn so harmless response key ordering cannot disable WebSocket continuation.
79
+ - Send full WebSocket context when a payload hook supplies string-valued Responses input instead of incorrectly continuing with an empty delta.
80
+ - Treat an empty Codex session identifier as absent and generate a stable UUIDv7 WebSocket request identifier.
81
+ - Preserve Pi AI's exact Codex HTTP error bodies and fallback wording without local trimming or truncation.
82
+ - Keep native Codex history keyed by the complete Pi session identifier even when cache retention is disabled or the backend cache key is shortened.
83
+ - Represent overlong Codex cache identifiers with collision-resistant 64-character SHA-256 hex values instead of truncating them.
84
+ - Prevent SSE reader cleanup failures from masking the original Codex stream error.
85
+ - Reject malformed Codex credentials whose account identifier is empty.
86
+ - Snapshot cached WebSocket continuation requests through their JSON wire representation so payload-hook values omitted by JSON serialization cannot break completed responses.
87
+ - Match Pi AI's metadata for non-Error WebSocket failures and its fallback wording for unavailable transports and terminal responses without provider details.
88
+ - Discard WebSockets and continuation state when downstream response parsing or terminal validation fails, report parser diagnostics, and retry a fresh WebSocket on later requests without activating sticky SSE fallback.
89
+ - Reuse one JSON serialization for SSE requests, align outbound WebSocket close reasons, and match Pi AI's closed grammar-input error wording.
90
+
5
91
  ## 0.0.2 - 2026-08-03
6
92
 
7
93
  ### Added
package/README.md CHANGED
@@ -31,45 +31,61 @@ Authenticate through Pi if needed:
31
31
 
32
32
  ## Compatibility baseline and differences
33
33
 
34
- The compatibility baseline is official Codex CLI `0.146.0`, released July 29, 2026. Upstream `main` at commit `bb5054fe47abe73ecbbd454751066a28c89f4bb9` was also inspected on August 3, 2026. This section is the package's explicit compatibility contract: it distinguishes close protocol adaptations from deliberate Pi behavior, configurable defaults, known gaps, and unsupported Codex runtimes.
34
+ The compatibility baseline is official Codex CLI `0.146.0`, released July 29, 2026. Upstream `main` at commit `0bdce9f424eb9b39d7b3a8811742d10b6fbf8d54` was also inspected on August 7, 2026, including post-release routing and default-tool namespace changes. Current upstream, rather than the older installed CLI, is authoritative where they differ: commit `f21dc46388` replaced direct Lite function/custom declarations with one canonical `functions` namespace. This section is the package's explicit compatibility contract: it distinguishes close protocol adaptations from deliberate Pi behavior, configurable defaults, known gaps, and unsupported Codex runtimes. See the [Responses Lite compatibility report](RESPONSES_LITE_COMPATIBILITY.md) and [Codex caching and transport comparison](CODEX_CACHE_RESEARCH.md) for source revisions, request-path findings, and live cache trajectories.
35
35
 
36
36
  ### Configurable defaults that differ from Codex
37
37
 
38
- | Area | This package by default | Official Codex | Configuration |
39
- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
40
- | Generated-image detail sent back to the model | Sends image tool-result content with `input_image.detail: "auto"`. On GPT-5.6, `auto` uses original-size image accounting. | Uses `high`. | `imageDetail`: `auto`, `low`, `high`, or `original`. |
41
- | Image-generation tool | Enabled whenever an `openai-codex` model is selected. Backend capability and account failures surface when the tool executes. | Stable and enabled by default, but additionally gated by plan, model, provider, authentication, image-generation, and namespace capabilities. | `imageGeneration`: boolean. |
42
- | Standalone `web.run` | Enabled and preferred over hosted `web_search`, using the complete reserved command schema and tool description. | The standalone feature is under development and disabled by default outside environments such as Responses Lite. | `webRun`: boolean. |
43
- | Hosted web search | Omitted while `web.run` is active; otherwise injected in cached mode. | Normally uses hosted cached search because standalone `web.run` is disabled by default. | `webRun` and `webSearch`: `disabled`, `cached`, `indexed`, or `live`. |
44
- | Coding mutation tools | Enables `apply_patch` and suppresses Pi's active `edit` and `write` tools. | Chooses its tool surface from model metadata and runtime capabilities; there are no Pi `edit` or `write` tools to suppress. | `applyPatch`: boolean. |
45
- | Codex tool background | Uses a subtle theme-derived surface for extension-owned Codex tools. | Uses Codex's own TUI activity cells rather than Pi tool rows. | `toolBackground`: `subtle`, `status`, or `none`. |
46
- | Auto-compaction trigger | Relies on Pi's reserve-token threshold unless a percentage is configured. | Tracks Codex's model/token-budget state before and between sampling steps. | `autoCompactAtPercent`: percentage or unset. Pi's own compaction settings remain separate. |
47
- | Fast mode | Uses the normal tier. | Uses the configured Codex service tier. | `fastMode`: boolean; `true` requests the priority tier. |
48
- | Text and reasoning request controls | Explicitly sends low text verbosity, automatic reasoning summaries, and standard GPT-5.6 reasoning mode. | Resolves these controls through Codex configuration, model metadata, and turn state. | `textVerbosity`, `reasoningSummary`, and `reasoningMode`. |
38
+ | Area | This package by default | Official Codex | Configuration |
39
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
40
+ | Generated-image detail sent back to the model | Sends image tool-result content with `input_image.detail: "auto"`. On GPT-5.6, `auto` uses original-size image accounting. | Uses `high`. | `imageDetail`: `auto`, `low`, `high`, or `original`. |
41
+ | Image-generation tool | Enabled whenever an `openai-codex` model is selected. Backend capability and account failures surface when the tool executes. | Stable and enabled by default, but additionally gated by plan, model, provider, authentication, image-generation, and namespace capabilities. | `imageGeneration`: boolean. |
42
+ | Standalone `web.run` | Disabled by default; when enabled, preferred over hosted `web_search` and sent with the complete reserved schema and description. | Enabled by default for `gpt-5.6-sol` through Responses Lite; otherwise subject to standalone-search feature and runtime gates. | `webRun`: boolean. |
43
+ | Hosted web search | Disabled by default; when enabled, injected only for ordinary Responses while `web.run` is inactive. Responses Lite omits hosted tools. | Omitted for `gpt-5.6-sol` while standalone `web.run` is available; otherwise defaults to cached mode when hosted search is supported. | `webRun` and `webSearch`: `disabled`, `cached`, `indexed`, or `live`. |
44
+ | Coding mutation tools | Enables `apply_patch` and suppresses Pi's active `edit` and `write` tools. | Chooses its tool surface from model metadata and runtime capabilities; there are no Pi `edit` or `write` tools to suppress. | `applyPatch`: boolean. |
45
+ | Codex tool background | Uses a subtle theme-derived surface for extension-owned Codex tools. | Uses Codex's own TUI activity cells rather than Pi tool rows. | `toolBackground`: `subtle`, `status`, or `none`. |
46
+ | Auto-compaction trigger | Relies on Pi's reserve-token threshold unless a percentage is configured. | Tracks Codex's model/token-budget state before and between sampling steps. | `autoCompactAtPercent`: percentage or unset. Pi's own compaction settings remain separate. |
47
+ | Fast mode | Uses the normal tier. | Uses the configured Codex service tier. | `fastMode`: boolean; `true` requests the priority tier. |
48
+ | Responses Lite | Disabled; supported GPT-5.6 models use ordinary Responses. | Enabled according to Codex model metadata. | `responsesLite`: boolean; `true` enables Responses Lite. |
49
+ | Text and reasoning request controls | Sends low text verbosity and automatic reasoning summaries; omits the default GPT-5.6 standard mode and sends `reasoning.mode` only for pro mode. | Resolves these controls through Codex configuration, model metadata, and turn state. | `textVerbosity`, `reasoningSummary`, and `reasoningMode`. |
49
50
 
50
51
  `web.run` is a reserved GPT-5.6 tool name. Its declaration therefore reproduces the complete current Codex post-normalization `SearchCommands` schema and official tool description instead of using Pi's normal compact tool schema. This intentionally omits generated annotations such as `format` and `minimum` that Codex removes before sending the declaration to Responses.
51
52
 
52
53
  ### Non-configurable implementation differences
53
54
 
54
- | Area | Difference |
55
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
56
- | Session storage | Pi remains the canonical session owner. Opaque Codex compaction checkpoints are stored in Pi `compaction` entries, and otherwise lossy Responses output is stored in sparse custom native-response entries. Official Codex owns a rollout/thread store directly. |
57
- | Branching | Checkpoints and native response overrides follow Pi's active session branch. Official Codex uses its own thread, turn, rollback, fork, and context-window lineage. |
58
- | Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
59
- | System instructions | Pi rebuilds the current system prompt and sends it through Responses `instructions`; normal Pi history does not store it as replayed system/developer input. `/reload` therefore updates the next request without rewriting old checkpoints. |
60
- | Turn metadata | Requests do not yet send Codex's complete thread, turn, context-window, request-kind, workspace, and per-turn routing metadata. Sticky WebSocket continuation is implemented locally from response IDs and replayable items. |
61
- | Mid-turn compaction | Provider-boundary percentage compaction installs a checkpoint and continues the intercepted request. Pi threshold compaction runs after the agent response and does not automatically continue unless Pi has queued messages. Official Codex compacts inline and continues when a completed sampling step says more model/tool work is required. |
62
- | Incomplete responses | A Responses `status: "incomplete"` currently becomes Pi `stopReason: "length"`. Official Codex treats `response.incomplete` as a stream error. This can produce a Pi threshold compaction followed by an idle agent instead of an inline continuation or explicit error. |
63
- | Compaction lifecycle events | Percentage compaction writes through Pi's mutable session manager but cannot emit Pi's internal `session_compact` event through the public extension API. Manual, threshold, and overflow compactions initiated by Pi do emit the normal lifecycle. |
64
- | Header hooks | An internal percentage-compaction request reuses the already transformed provider headers. It cannot independently rerun Pi's `before_provider_headers` hook. |
65
- | Native retained context | The v2 retained-message selection and truncation match Codex. In normal Pi operation, system instructions are carried separately through `instructions`, so retained API history usually contains user messages rather than Codex's complete initial-context lifecycle. |
66
- | Tool namespaces | Pi registers dotted names such as `web.run` as exact flat identifiers. The provider converts only the fixed extension-owned allowlist into real Responses namespace/member identities and rejects unknown or ambiguously flat namespaced calls. |
67
- | Capability gating | Tool activation is based on the selected `openai-codex` provider plus package settings. It does not reproduce every official model-metadata, plan, feature-stage, executor, mode, or account gate. |
68
- | Sandbox and approvals | Pi extensions run with full process permissions. `apply_patch`, local image reads, generated-image writes, and sibling Codex endpoints do not use Codex's sandbox, permission-profile, or approval lifecycle. |
69
- | Image artifact hint | When image saving succeeds, this package always returns the path hint, says “the generated image,” and has no 1,024-byte cutoff. Official Codex says “a generated image” and omits the hint when it exceeds 1,024 UTF-8 bytes. |
70
- | Image artifacts | Generated files use Pi's agent directory and the Pi session/tool-call IDs. Official Codex uses its own artifact/output-directory lifecycle. |
71
- | Web references | `web.run` structured results are retained branch-locally in Pi tool-result details rather than Codex extension events, and hosted native items are preserved for provider replay. Reference IDs are resolved remotely by `alpha/search`, as in Codex. Hosted citation annotations remain a separate unimplemented path. |
72
- | UI | Pi renders its own conversation, footer, settings pane, branches, and compaction lifecycle. Extension-owned Codex tools have dedicated Pi renderers, but do not reproduce Codex app-server `WebSearchItem` or image-generation lifecycle notifications. |
55
+ | Area | Difference |
56
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
57
+ | Session storage | Pi remains the canonical session owner. Opaque Codex compaction checkpoints are stored in Pi `compaction` entries, and otherwise lossy Responses output is stored in sparse custom native-response entries. Official Codex owns a rollout/thread store directly. |
58
+ | Branching | Checkpoints and native response overrides follow Pi's active session branch. Official Codex uses its own thread, turn, rollback, fork, and context-window lineage. |
59
+ | Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
60
+ | System instructions | Pi rebuilds the current system prompt. Responses Lite models prepend it as developer input after `additional_tools`; other models send it through Responses `instructions`. Normal Pi history does not store it as replayed system/developer input. `/reload` updates the next request without rewriting old checkpoints. |
61
+ | Turn metadata | Requests send a persisted installation id plus Pi-derived session, thread, context-window, turn, source, sandbox, request-kind, and nested compaction-operation metadata in `client_metadata` and compatible headers. The in-memory context-window number advances after successful compaction. One turn id is reused throughout a Pi agent run, while prewarm has its own id. First-party requests also carry Codex's model-and-tier routing hint. The provider captures the server-issued `x-codex-turn-state` once per agent run, replays it on WebSocket retries, SSE requests, and WebSocket-to-SSE fallback, and records all identity values in transport diagnostics. Pi does not reconstruct prior window number after extension reload/session resume or reproduce workspace Git/parent/subagent/Code Mode metadata. Each marked Pi tree branch receives its own persisted thread UUID. |
62
+ | Cache preparation | Before the first cache-enabled WebSocket turn, the package prewarms only the stable instruction/tool prefix: ordinary Responses uses empty `input`, while Responses Lite uses `additional_tools` plus the developer instructions. The first generated request then contributes only dynamic conversation input to the continuation. No explicit prompt-cache breakpoints are added. |
63
+ | Mid-turn compaction | Provider-boundary percentage compaction installs a checkpoint and continues the intercepted request. Pi threshold compaction normally runs after the agent response; after Codex output-token truncation, the extension queues a hidden continuation so threshold compaction completes before sampling resumes. Official Codex owns this sampling and compaction loop directly. |
64
+ | Provider-owned follow-up | Completed responses with `end_turn: false` continue immediately from completed native output without synthetic user input. Retryable `response.failed` and all `response.incomplete` events are resampled with the official five-retry stream budget, preserving completed output and cumulative usage while excluding unfinished attempt content. A `max_output_tokens` response that exhausts this budget still becomes Pi `stopReason: "length"` and uses the extension's unbounded host-level continuation recovery. |
65
+ | Compaction lifecycle events | Percentage compaction writes through Pi's mutable session manager but cannot emit Pi's internal `session_compact` event through the public extension API. Manual, threshold, and overflow compactions initiated by Pi do emit the normal lifecycle. |
66
+ | Header hooks | An internal percentage-compaction request reuses the already transformed provider headers. It cannot independently rerun Pi's `before_provider_headers` hook. |
67
+ | Native retained context | Deliberately differs from current Codex. The package retains recent user/developer/system messages under the 64k budget before the opaque compaction item. Current Codex applies a second installed-history filter that drops developer/system wrappers and non-real-user messages, can retain eligible structured agent commentary, and trims oversized function outputs before compaction. Pi keeps its existing checkpoint shape by design. |
68
+ | Tool namespaces | Responses Lite groups Pi's ordinary function/custom declarations into upstream's canonical `functions` namespace and maps that default namespace back to bare Pi names. Pi registers dotted names such as `web.run` as exact flat identifiers, so the provider converts only the fixed extension-owned allowlist into non-default Responses namespace/member identities and rejects unknown or ambiguously flat namespaced calls. |
69
+ | Capability gating | Tool activation is based on the selected `openai-codex` provider plus package settings. It does not reproduce every official model-metadata, plan, feature-stage, executor, mode, or account gate. |
70
+ | Sandbox and approvals | Pi extensions run with full process permissions. `apply_patch`, local image reads, generated-image writes, and sibling Codex endpoints do not use Codex's sandbox, permission-profile, or approval lifecycle. |
71
+ | Image tool instructions | The package retains the server-reserved image-generation schema while replacing Rust-specific path annotations and Codex Code Mode instructions with model-facing descriptions, a prompt snippet, and system-prompt guidelines. Image-count bounds and selector exclusivity are enforced before execution. |
72
+ | Image artifact hint | When image saving succeeds, this package always returns the path hint, says “the generated image,” and has no 1,024-byte cutoff. Official Codex says “a generated image” and omits the hint when it exceeds 1,024 UTF-8 bytes. |
73
+ | Image artifacts | Generated files use Pi's agent directory and the Pi session/tool-call IDs. Official Codex uses its own artifact/output-directory lifecycle. |
74
+ | Web references | `web.run` structured results are retained branch-locally in Pi tool-result details rather than Codex extension events, and hosted native items are preserved for provider replay. Reference IDs are resolved remotely by `alpha/search`, as in Codex. Hosted citation annotations remain a separate unimplemented path. |
75
+ | UI | Pi renders its own conversation, footer, settings pane, branches, and compaction lifecycle. Extension-owned Codex tools have dedicated Pi renderers, but do not reproduce Codex app-server `WebSearchItem` or image-generation lifecycle notifications. |
76
+
77
+ Nested compaction metadata uses the official Responses Compaction v2 implementation and memento
78
+ strategy. Manual compaction is user-requested and standalone; threshold and provider-boundary
79
+ compaction are automatic context-limit operations in the pre-turn phase; overflow recovery is the
80
+ corresponding mid-turn operation.
81
+
82
+ When a finalized user prompt creates a new `/tree` branch, the extension inserts a hidden,
83
+ context-free custom marker as that prompt's parent. Navigation alone writes nothing. The marker is
84
+ hidden by Pi's default, no-tools, and user-only tree filters and appears only in the all-entries
85
+ filter. Root `session_id` and `prompt_cache_key` remain stable, while the branch gets a UUID
86
+ `thread_id`, `forked_from_thread_id`, and thread-scoped window number. Switching threads closes the
87
+ old WebSocket and discards its incompatible `previous_response_id` baseline; the new full-history
88
+ request remains eligible to reuse the common backend-cached prefix under the unchanged cache key.
73
89
 
74
90
  ### Tool and runtime coverage
75
91
 
@@ -137,6 +153,8 @@ Create a global configuration file at:
137
153
  ~/.pi/agent/openai-codex-compat.json
138
154
  ```
139
155
 
156
+ The extension also creates `openai-codex-compat-installation-id` in the active Pi agent directory. It contains the stable UUID used for official Codex installation metadata and is reused across sessions.
157
+
140
158
  A trusted project can override it at:
141
159
 
142
160
  ```text
@@ -152,13 +170,14 @@ Example:
152
170
  ```json
153
171
  {
154
172
  "fastMode": true,
173
+ "responsesLite": true,
155
174
  "toolBackground": "subtle",
156
175
  "applyPatch": true,
157
176
  "imageGeneration": true,
158
177
  "imageDetail": "auto",
159
- "webRun": true,
178
+ "webRun": false,
160
179
  "autoCompactAtPercent": 90,
161
- "webSearch": "cached",
180
+ "webSearch": "disabled",
162
181
  "textVerbosity": "low",
163
182
  "reasoningSummary": "auto",
164
183
  "reasoningMode": "standard"
@@ -170,16 +189,17 @@ Defaults:
170
189
  | Setting | Values | Default | Behavior |
171
190
  | ---------------------- | ---------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
191
  | `fastMode` | boolean | `false` | Adds `service_tier: "priority"` to requests while retaining the current `openai-codex` provider and model. |
192
+ | `responsesLite` | boolean | `false` | Uses Codex's Responses Lite input envelope on supported GPT-5.6 models when enabled. By default, those models use ordinary Responses instructions and tools. |
173
193
  | `toolBackground` | `subtle`, `status`, `none` | `subtle` | Controls the shared self-rendered background for `apply_patch`, `image_gen.imagegen`, and `web.run`. `status` uses Pi's pending/success/error backgrounds; `none` keeps the custom layout transparent. |
174
194
  | `applyPatch` | boolean | `true` | On selected `openai-codex` models, uses the extension's `apply_patch` tool instead of Pi's active `edit` and `write` tools. Other providers always use their normal Pi tool set. |
175
195
  | `imageGeneration` | boolean | `true` | Enables the extension-owned `image_gen.imagegen` tool on selected `openai-codex` models. |
176
196
  | `imageDetail` | `auto`, `low`, `high`, `original` | `auto` | Sets `input_image.detail` when an image tool result is sent back to the model. It does not change `gpt-image-2` generation quality. |
177
- | `webRun` | boolean | `true` | Enables the extension-owned `web.run` tool on selected `openai-codex` models. When active, it replaces hosted `web_search` in the Responses tool list. |
197
+ | `webRun` | boolean | `false` | Enables the extension-owned `web.run` tool on selected `openai-codex` models. When active, it replaces hosted `web_search` in the Responses tool list. |
178
198
  | `autoCompactAtPercent` | number greater than `0` and at most `100`, or `null` | unset | Adds provider-boundary compaction independently of Pi's normal reserve-token threshold. A project value of `null` disables a global percentage threshold. |
179
- | `webSearch` | `disabled`, `cached`, `indexed`, `live` | `cached` | Controls hosted search and standalone-search external access. `disabled` removes hosted search but leaves an independently enabled `web.run` in cached-only mode; `indexed` prefers indexed content; `live` permits live external access. |
199
+ | `webSearch` | `disabled`, `cached`, `indexed`, `live` | `disabled` | Controls hosted search and standalone-search external access. `disabled` removes hosted search but leaves an independently enabled `web.run` in cached-only mode; `indexed` prefers indexed content; `live` permits live external access. |
180
200
  | `textVerbosity` | `low`, `medium`, `high` | `low` | Sets Responses API `text.verbosity`. |
181
201
  | `reasoningSummary` | `auto`, `concise`, `detailed`, `off` | `auto` | Sets `reasoning.summary` when reasoning is enabled; `off` omits the summary parameter. |
182
- | `reasoningMode` | `standard`, `pro` | `standard` | Sets `reasoning.mode` on GPT-5.6 models independently of Pi's reasoning-effort control. |
202
+ | `reasoningMode` | `standard`, `pro` | `standard` | Controls GPT-5.6 execution mode independently of Pi's reasoning-effort control. The default omits `reasoning.mode`; `pro` sends `reasoning.mode: "pro"`. |
183
203
 
184
204
  Invalid JSON setting values are ignored and invalid JSON does not prevent Pi from starting. The settings pane never writes on ordinary changes, refuses to overwrite invalid JSON when `Enter` or `Ctrl+S` attempts to save, and retains unknown keys when saving. Project configuration is read only when the project is trusted.
185
205
 
@@ -188,6 +208,7 @@ Every setting can also be overridden for one Pi process with an environment vari
188
208
  | Setting | Environment variable |
189
209
  | ---------------------- | ------------------------------------------------ |
190
210
  | `fastMode` | `PI_OPENAI_CODEX_COMPAT_FAST_MODE` |
211
+ | `responsesLite` | `PI_OPENAI_CODEX_COMPAT_RESPONSES_LITE` |
191
212
  | `toolBackground` | `PI_OPENAI_CODEX_COMPAT_TOOL_BACKGROUND` |
192
213
  | `applyPatch` | `PI_OPENAI_CODEX_COMPAT_APPLY_PATCH` |
193
214
  | `imageGeneration` | `PI_OPENAI_CODEX_COMPAT_IMAGE_GENERATION` |
@@ -207,6 +228,7 @@ For example:
207
228
 
208
229
  ```bash
209
230
  PI_OPENAI_CODEX_COMPAT_WEB_RUN=off \
231
+ PI_OPENAI_CODEX_COMPAT_RESPONSES_LITE=off \
210
232
  PI_OPENAI_CODEX_COMPAT_IMAGE_DETAIL=high \
211
233
  PI_OPENAI_CODEX_COMPAT_AUTO_COMPACT_AT_PERCENT=90 \
212
234
  pi
@@ -221,7 +243,19 @@ The extension handles native compaction for `openai-codex`. It follows the Codex
221
243
  3. Retain approximately 64,000 tokens of recent user, developer, and system context.
222
244
  4. Persist the opaque checkpoint in the Pi session and replay it on later requests.
223
245
 
224
- Ordinary responses and compaction use the same extension-managed SSE/WebSocket transport. The provider stores a native response override only when Pi's canonical assistant representation cannot round-trip the provider output exactly; normal text, reasoning, and tool responses therefore do not duplicate session data. Native overrides are associated with canonical assistants by response id and replayed only when they are present on the active Pi branch.
246
+ Ordinary responses and compaction use the same extension-managed SSE/WebSocket transport. Before the first WebSocket turn for a session and model, the provider performs a best-effort v2 `generate: false` prewarm of the static instruction/tool prefix, then generates the dynamic conversation input from its continuation. With `responsesLite: false`, supported GPT-5.6 models use the ordinary Responses envelope and receive their own ordinary-prefix prewarm.
247
+
248
+ Healthy session WebSockets remain available until the server closes them or Pi tears down the session. Retryable WebSocket failures before model-visible output receive up to five fresh-connection retries before the session switches to sticky SSE. Retryable SSE HTTP failures and dropped streams receive up to five same-request resampling attempts before model-visible output. Both transports use Codex-style exponential backoff with ±10% jitter and preserve the same prompt-cache, session, account, installation, and window identities. Server metadata is not considered model-visible output, so a routing-state-only response can still be retried safely. Transport failures after model-visible output fail closed rather than risk duplicate text or tool calls. Explicit retryable `response.failed`/`response.incomplete` protocol terminals instead return to the provider-owned sampling loop, which preserves completed output items as the next request's history.
249
+
250
+ The provider stores a native response override only when Pi's canonical assistant representation cannot round-trip the provider output exactly; normal text, reasoning, and tool responses therefore do not duplicate session data. Native overrides are associated with canonical assistants by response id and replayed only when they are present on the active Pi branch.
251
+
252
+ Transparent prewarm, requests, continuation, and transport recovery are recorded in the resulting assistant message's `diagnostics` array in Pi's session JSONL:
253
+
254
+ - `codex_transport_prewarm` records whether static prewarm completed, established continuation state, and received turn state.
255
+ - `codex_transport_request` records the selected transport, full/delta input counts and byte sizes, exact session/account/cache/turn/response/routing-state identifiers, static-prefix and request-template fingerprints, instruction/tool fingerprints, cache affinity, and reported cache read/write token usage.
256
+ - `codex_transport_recovery` identifies fresh-WebSocket and SSE retries, rejected or locally bypassed continuations, and WebSocket-to-SSE recovery, including the exact triggering error, attempted request modes, and whether cache and account affinity were preserved.
257
+
258
+ Diagnostics intentionally retain exact request, cache-affinity, response, account, and server routing identifiers so a local Pi session file contains enough information to trace retries and cache behavior directly. Prompt and tool contents are still represented by byte counts and SHA-256 fingerprints rather than duplicated into every diagnostic.
225
259
 
226
260
  Any model switch is rejected while the active branch contains a native Codex checkpoint because checkpoints are model-specific. Navigate to a branch before the checkpoint or start a new session before switching. Toggling fast mode does not change the model id or invalidate the checkpoint.
227
261
 
@@ -293,6 +327,8 @@ The tool generates new images with `gpt-image-2` or edits up to five local/recen
293
327
 
294
328
  The active Pi agent directory replaces `~/.pi/agent` when configured differently. Turning `imageGeneration` off removes the tool immediately for the current session; `Enter` or `Ctrl+S` in `/codex-settings` persists the value.
295
329
 
330
+ The tool registers the server-reserved schema directly with a model-facing absolute-path annotation that names the supported image formats. OpenAI rejects additional schema keywords for image-count bounds and selector exclusivity, so the executor enforces those constraints before filesystem or network access. Local paths are lexically normalized before reading. A one-line system-prompt snippet and four high-signal guidelines cover normal model use. Local images are inspected with Pi's `read` tool, and generated image content is displayed and saved automatically without Codex Code Mode wrappers.
331
+
296
332
  Pi also persists the returned image content in tool-result history so later image edits and provider replay remain self-contained. Generated-image turns therefore increase the session file by approximately the base64 image size in addition to the saved PNG artifact.
297
333
 
298
334
  When the image tool result is serialized back to the model, `imageDetail` controls its Responses `input_image.detail`. The default remains `auto`; select `high` for the official Codex default. The saved-path hint intentionally differs from Codex: it always uses “the generated image” and is not removed when the UTF-8 hint exceeds 1,024 bytes.
@@ -345,11 +381,23 @@ npm run check
345
381
  npm test
346
382
  ```
347
383
 
384
+ Run the credentialed Pi/Codex integration tests separately. They load the real
385
+ extension into headless Pi sessions, use the real WebSocket service, and ask
386
+ the model to report all prior history markers after each text and tool
387
+ continuation:
388
+
389
+ ```bash
390
+ mise run test:live:codex
391
+ ```
392
+
393
+ The task obtains the local Codex bearer token and runs the tests with
394
+ `gpt-5.6-luna` at medium reasoning effort.
395
+
348
396
  The focused Pi AI serializer copy lives under `extensions/openai-codex-compat/vendor/pi-ai/`. The custom Codex provider transport and stream parser are focused adaptations of Pi AI's corresponding implementation. Equivalence and protocol tests cover canonical serialization, native namespace round-trips, raw native replay, sibling Codex JSON endpoints, SSE request behavior, WebSocket reuse, grammar tools, image results, standalone search, and compaction continuation.
349
397
 
350
398
  ## Release staging
351
399
 
352
- The GitHub Actions workflow stages npm releases when a `v*` tag is pushed. The tag must match `package.json` version and point at a commit whose subject is `release: v<version>`.
400
+ The GitHub Actions workflow stages npm releases when a `v*` tag is pushed. The tag must match the `package.json` version, point at a commit whose subject is `release: v<version>`, and be a lightweight tag. Create it with `git tag v<version>`; do not use `git tag -a`, `git tag -s`, `git tag -m`, or `cog bump --annotated`.
353
401
 
354
402
  ## Acknowledgements
355
403
 
@@ -7,7 +7,11 @@ import {
7
7
  visibleWidth,
8
8
  wrapTextWithAnsi,
9
9
  } from "@earendil-works/pi-tui";
10
- import type { AppliedPatchChange, ApplyPatchDetails } from "./apply-patch-engine.ts";
10
+ import {
11
+ type AppliedPatchChange,
12
+ type ApplyPatchDetails,
13
+ coalesceAppliedPatchChangesForRendering,
14
+ } from "./apply-patch-engine.ts";
11
15
  import { usesLightToolPalette } from "./codex-tool-surface.ts";
12
16
 
13
17
  type DiffLineKind = "add" | "delete" | "context";
@@ -154,7 +158,7 @@ export function isApplyPatchDetails(value: unknown): value is ApplyPatchDetails
154
158
 
155
159
  function sortedChanges(details: ApplyPatchDetails, cwd: string): AppliedPatchChange[] {
156
160
  if (!isApplyPatchDetails(details)) return [];
157
- return details.changes.toSorted((left, right) =>
161
+ return coalesceAppliedPatchChangesForRendering(details.changes, cwd).toSorted((left, right) =>
158
162
  comparePaths(resolve(cwd, left.path), resolve(cwd, right.path)),
159
163
  );
160
164
  }
@@ -674,6 +674,90 @@ function diffDetails(
674
674
  return { displayDiff, additions, deletions };
675
675
  }
676
676
 
677
+ function initialContent(change: AppliedPatchChange): string | undefined {
678
+ if (change.kind === "add") return change.overwrittenContent;
679
+ if (change.kind === "delete") return change.content;
680
+ return change.oldContent;
681
+ }
682
+
683
+ function finalContent(change: AppliedPatchChange): string | undefined {
684
+ if (change.kind === "delete") return undefined;
685
+ if (change.kind === "add") return change.content;
686
+ return change.newContent;
687
+ }
688
+
689
+ export function coalesceAppliedPatchChangesForRendering(
690
+ changes: readonly AppliedPatchChange[],
691
+ cwd: string,
692
+ ): AppliedPatchChange[] {
693
+ const groups = new Map<string, { firstIndex: number; changes: AppliedPatchChange[] }>();
694
+ const rendered: Array<{ index: number; change: AppliedPatchChange }> = [];
695
+
696
+ for (const [index, change] of changes.entries()) {
697
+ if (change.kind === "update" && change.moveTo) {
698
+ // Moves span source and destination identities, so retain their existing operation-level row.
699
+ rendered.push({ index, change });
700
+ continue;
701
+ }
702
+ const key = resolvePatchPath(cwd, change.path);
703
+ const group = groups.get(key);
704
+ if (group) {
705
+ group.changes.push(change);
706
+ } else {
707
+ groups.set(key, { firstIndex: index, changes: [change] });
708
+ }
709
+ }
710
+
711
+ for (const group of groups.values()) {
712
+ const first = group.changes[0]!;
713
+ const last = group.changes.at(-1)!;
714
+ if (group.changes.length === 1) {
715
+ rendered.push({ index: group.firstIndex, change: first });
716
+ continue;
717
+ }
718
+
719
+ const oldContent = initialContent(first);
720
+ const newContent = finalContent(last);
721
+ if (oldContent === undefined) {
722
+ if (newContent === undefined) continue;
723
+ rendered.push({
724
+ index: group.firstIndex,
725
+ change: {
726
+ kind: "add",
727
+ path: first.path,
728
+ content: newContent,
729
+ ...diffDetails("", newContent),
730
+ },
731
+ });
732
+ continue;
733
+ }
734
+ if (newContent === undefined) {
735
+ rendered.push({
736
+ index: group.firstIndex,
737
+ change: {
738
+ kind: "delete",
739
+ path: first.path,
740
+ content: oldContent,
741
+ ...diffDetails(oldContent, ""),
742
+ },
743
+ });
744
+ continue;
745
+ }
746
+ rendered.push({
747
+ index: group.firstIndex,
748
+ change: {
749
+ kind: "update",
750
+ path: first.path,
751
+ oldContent,
752
+ newContent,
753
+ ...diffDetails(oldContent, newContent),
754
+ },
755
+ });
756
+ }
757
+
758
+ return rendered.toSorted((left, right) => left.index - right.index).map(({ change }) => change);
759
+ }
760
+
677
761
  function emptyDetails(): ApplyPatchDetails {
678
762
  return {
679
763
  status: "completed",
@@ -883,10 +967,10 @@ export async function previewPatch(cwd: string, patch: string): Promise<ApplyPat
883
967
  const operations = resolveOperations(cwd, parsed.operations);
884
968
  await verifyOperations(operations);
885
969
  const details = emptyDetails();
886
- const changes = new Map<string, AppliedPatchChange>();
970
+ const changes: AppliedPatchChange[] = [];
887
971
  for (const operation of operations) {
888
972
  if (operation.kind === "add") {
889
- changes.set(operation.absolutePath, {
973
+ changes.push({
890
974
  kind: "add",
891
975
  path: operation.path,
892
976
  content: operation.content,
@@ -897,7 +981,7 @@ export async function previewPatch(cwd: string, patch: string): Promise<ApplyPat
897
981
  operation.absolutePath,
898
982
  `Failed to read ${operation.absolutePath}`,
899
983
  );
900
- changes.set(operation.absolutePath, {
984
+ changes.push({
901
985
  kind: "delete",
902
986
  path: operation.path,
903
987
  content,
@@ -909,7 +993,7 @@ export async function previewPatch(cwd: string, patch: string): Promise<ApplyPat
909
993
  `Failed to read file to update ${operation.absolutePath}`,
910
994
  );
911
995
  const newContent = deriveNewContent(oldContent, operation.chunks, operation.absolutePath);
912
- changes.set(operation.absolutePath, {
996
+ changes.push({
913
997
  kind: "update",
914
998
  path: operation.path,
915
999
  ...(operation.moveTo ? { moveTo: operation.moveTo } : {}),
@@ -919,7 +1003,7 @@ export async function previewPatch(cwd: string, patch: string): Promise<ApplyPat
919
1003
  });
920
1004
  }
921
1005
  }
922
- details.changes = [...changes.values()];
1006
+ details.changes = coalesceAppliedPatchChangesForRendering(changes, cwd);
923
1007
  for (const change of details.changes) {
924
1008
  if (change.kind === "add") details.added.push(change.path);
925
1009
  else if (change.kind === "delete") details.deleted.push(change.path);
@@ -73,12 +73,11 @@ export default function registerApplyPatch(
73
73
  label: APPLY_PATCH_TOOL_NAME,
74
74
  description:
75
75
  "The `apply_patch` tool can be used to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.",
76
- promptSnippet: "Apply Codex-format patches to add, edit, move, or delete files",
76
+ promptSnippet: "Apply freeform patches to add, update, move, or delete files",
77
77
  promptGuidelines: [
78
- "Use apply_patch for local file edits.",
79
- "Do not create or edit files with cat or other shell write tricks.",
80
- "Formatting commands and bulk mechanical rewrites do not need apply_patch.",
81
- "Do not use Python to read or write files when a simple shell command or apply_patch is enough.",
78
+ "Use `apply_patch` for local file edits.",
79
+ "Do not create or edit files with `cat` or other shell write tricks.",
80
+ "Formatting commands and bulk mechanical rewrites do not need `apply_patch`.",
82
81
  ],
83
82
  parameters: Type.Object({
84
83
  patch: Type.String({ description: "Raw patch text beginning with *** Begin Patch" }),