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.
- package/CHANGELOG.md +86 -0
- package/README.md +86 -38
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +6 -2
- package/extensions/openai-codex-compat/apply-patch-engine.ts +89 -5
- package/extensions/openai-codex-compat/apply-patch.ts +4 -5
- package/extensions/openai-codex-compat/codex-cache-diagnostics.ts +97 -0
- package/extensions/openai-codex-compat/codex-cache-key.ts +9 -0
- package/extensions/openai-codex-compat/codex-installation.ts +51 -0
- package/extensions/openai-codex-compat/codex-metadata.ts +139 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +4 -2
- package/extensions/openai-codex-compat/codex-provider.ts +708 -128
- package/extensions/openai-codex-compat/codex-stream.ts +137 -40
- package/extensions/openai-codex-compat/codex-thread-lineage.ts +156 -0
- package/extensions/openai-codex-compat/codex-transport.ts +1795 -199
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +2 -2
- package/extensions/openai-codex-compat/config.ts +15 -2
- package/extensions/openai-codex-compat/image-generation-schema.ts +37 -0
- package/extensions/openai-codex-compat/image-generation.ts +25 -48
- package/extensions/openai-codex-compat/index.ts +13 -0
- package/extensions/openai-codex-compat/namespaced-tools.ts +2 -0
- package/extensions/openai-codex-compat/output-limit-continuation.ts +151 -0
- package/extensions/openai-codex-compat/provider-error.ts +79 -0
- package/extensions/openai-codex-compat/remote-compaction.ts +13 -0
- package/extensions/openai-codex-compat/request-options.ts +2 -2
- package/extensions/openai-codex-compat/responses-lite.ts +147 -0
- package/extensions/openai-codex-compat/responses-replay.ts +0 -7
- package/extensions/openai-codex-compat/settings-pane.ts +11 -0
- package/extensions/openai-codex-compat/web-run.ts +7 -0
- 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 `
|
|
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
|
|
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.
|
|
41
|
-
| Image-generation tool | Enabled whenever an `openai-codex` model is selected. Backend capability and account failures surface when the tool executes.
|
|
42
|
-
| Standalone `web.run` |
|
|
43
|
-
| Hosted web search |
|
|
44
|
-
| Coding mutation tools | Enables `apply_patch` and suppresses Pi's active `edit` and `write` tools.
|
|
45
|
-
| Codex tool background | Uses a subtle theme-derived surface for extension-owned Codex tools.
|
|
46
|
-
| Auto-compaction trigger | Relies on Pi's reserve-token threshold unless a percentage is configured.
|
|
47
|
-
| Fast mode | Uses the normal tier.
|
|
48
|
-
|
|
|
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
|
|
60
|
-
| Turn metadata | Requests
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
| Image
|
|
71
|
-
|
|
|
72
|
-
|
|
|
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":
|
|
178
|
+
"webRun": false,
|
|
160
179
|
"autoCompactAtPercent": 90,
|
|
161
|
-
"webSearch": "
|
|
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 | `
|
|
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` | `
|
|
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` |
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
970
|
+
const changes: AppliedPatchChange[] = [];
|
|
887
971
|
for (const operation of operations) {
|
|
888
972
|
if (operation.kind === "add") {
|
|
889
|
-
changes.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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" }),
|