pi-openai-codex-compat 0.0.2 → 0.0.3

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 CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.3 - 2026-08-04
6
+
7
+ ### Added
8
+
9
+ - Expose Pi AI-compatible Codex WebSocket debug statistics, reset helpers, and force-close control for connection reuse, continuation deltas, failures, and SSE fallback.
10
+
11
+ ### Changed
12
+
13
+ - Rely on Pi's compaction indicator instead of adding a temporary third footer status line during percentage-triggered compaction.
14
+ - Adapt the `image_gen.imagegen` descriptions and prompt metadata to Pi while retaining the server-reserved schema.
15
+ - Normalize local image paths before reading them and enforce image-count bounds and edit-selector exclusivity in the executor.
16
+ - Add a concise `web.run` prompt snippet and four high-signal system-prompt guidelines derived from its official description.
17
+ - Clarify the `apply_patch` prompt snippet, format its system-prompt guidance consistently, and omit the redundant Python file-I/O reminder.
18
+ - Disable standalone `web.run` and hosted web search by default; both remain available through settings or environment overrides.
19
+
20
+ ### Fixed
21
+
22
+ - Report WebSocket close codes and reasons instead of `[object CloseEvent]`, and preserve an underlying WebSocket error when a close event follows it.
23
+ - Scope sticky Codex WebSockets to the authenticated account so changing accounts cannot reuse a connection authorized for another account.
24
+ - Recover once from expired WebSocket continuations and connection-limit responses while preserving structured Codex API and protocol errors.
25
+ - 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.
26
+ - Apply `cacheRetention: "none"` consistently to ordinary and compaction payloads and clamp cache-affinity headers to the backend's 64-character limit.
27
+ - Keep SSE fallback sticky after midstream WebSocket failures and use the fallback for later requests regardless of the preferred WebSocket mode.
28
+ - Validate transport timeouts, allow zero to disable the WebSocket connect timeout, report SSE header timeouts clearly, and honor bounded `Retry-After` guidance.
29
+ - Surface concise structured Codex HTTP errors, fail closed on malformed WebSocket events, and retain WebSocket-to-SSE recovery diagnostics on assistant messages.
30
+ - Distinguish output-token truncation from other incomplete Codex responses and preserve the provider's incomplete reason.
31
+ - 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.
32
+ - Honor every non-undefined payload replacement and reject missing `streamSimple` authentication synchronously, matching Pi AI.
33
+ - Match Pi AI's configured SSE retry behavior for callback, body-read, and otherwise non-terminal response-acquisition failures.
34
+ - Use Pi AI's time-ordered UUIDv7 identifiers for WebSocket requests without session affinity.
35
+ - Stop at terminal WebSocket events and honor aborts while requests wait for session transport ownership or finish streaming.
36
+ - Ignore type-less Codex events before transport start and retain WebSocket continuation IDs supplied by `response.created`.
37
+ - Validate Codex authentication before request hooks, template capture, or native compaction work.
38
+ - Apply service-tier pricing to unsuccessful terminal responses and match Pi AI when total-token usage is absent.
39
+ - Match Pi AI's final-answer phase state, reasoning-part separators, and suppression of empty final tool-call deltas.
40
+ - Retry generic Codex usage-limit responses according to Pi AI's configured SSE retry policy.
41
+ - Require Pi AI's order-sensitive request and history equality before sending cached WebSocket deltas.
42
+ - Cache the exact canonical or native assistant representation replayed by the next turn so harmless response key ordering cannot disable WebSocket continuation.
43
+ - Send full WebSocket context when a payload hook supplies string-valued Responses input instead of incorrectly continuing with an empty delta.
44
+ - Treat an empty Codex session identifier as absent and generate a stable UUIDv7 WebSocket request identifier.
45
+ - Preserve Pi AI's exact Codex HTTP error bodies and fallback wording without local trimming or truncation.
46
+ - Keep native Codex history keyed by the complete Pi session identifier even when cache retention is disabled or the backend cache key is shortened.
47
+ - Represent overlong Codex cache identifiers with collision-resistant 64-character SHA-256 hex values instead of truncating them.
48
+ - Prevent SSE reader cleanup failures from masking the original Codex stream error.
49
+ - Reject malformed Codex credentials whose account identifier is empty.
50
+ - Snapshot cached WebSocket continuation requests through their JSON wire representation so payload-hook values omitted by JSON serialization cannot break completed responses.
51
+ - Match Pi AI's metadata for non-Error WebSocket failures and its fallback wording for unavailable transports and terminal responses without provider details.
52
+ - 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.
53
+ - Reuse one JSON serialization for SSE requests, align outbound WebSocket close reasons, and match Pi AI's closed grammar-input error wording.
54
+
5
55
  ## 0.0.2 - 2026-08-03
6
56
 
7
57
  ### Added
package/README.md CHANGED
@@ -35,17 +35,17 @@ The compatibility baseline is official Codex CLI `0.146.0`, released July 29, 20
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 while `web.run` is inactive. | 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
+ | 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`. |
49
49
 
50
50
  `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
51
 
@@ -66,6 +66,7 @@ The compatibility baseline is official Codex CLI `0.146.0`, released July 29, 20
66
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
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
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 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. |
69
70
  | 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
71
  | 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
72
  | 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. |
@@ -156,9 +157,9 @@ Example:
156
157
  "applyPatch": true,
157
158
  "imageGeneration": true,
158
159
  "imageDetail": "auto",
159
- "webRun": true,
160
+ "webRun": false,
160
161
  "autoCompactAtPercent": 90,
161
- "webSearch": "cached",
162
+ "webSearch": "disabled",
162
163
  "textVerbosity": "low",
163
164
  "reasoningSummary": "auto",
164
165
  "reasoningMode": "standard"
@@ -174,9 +175,9 @@ Defaults:
174
175
  | `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
176
  | `imageGeneration` | boolean | `true` | Enables the extension-owned `image_gen.imagegen` tool on selected `openai-codex` models. |
176
177
  | `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. |
178
+ | `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
179
  | `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. |
180
+ | `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
181
  | `textVerbosity` | `low`, `medium`, `high` | `low` | Sets Responses API `text.verbosity`. |
181
182
  | `reasoningSummary` | `auto`, `concise`, `detailed`, `off` | `auto` | Sets `reasoning.summary` when reasoning is enabled; `off` omits the summary parameter. |
182
183
  | `reasoningMode` | `standard`, `pro` | `standard` | Sets `reasoning.mode` on GPT-5.6 models independently of Pi's reasoning-effort control. |
@@ -293,6 +294,8 @@ The tool generates new images with `gpt-image-2` or edits up to five local/recen
293
294
 
294
295
  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
296
 
297
+ 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.
298
+
296
299
  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
300
 
298
301
  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 +348,22 @@ npm run check
345
348
  npm test
346
349
  ```
347
350
 
351
+ Run the credentialed Codex transport tests separately. They use the real
352
+ WebSocket service and ask the model to report all prior history markers after
353
+ each canonical and native continuation:
354
+
355
+ ```bash
356
+ mise run test:live:codex
357
+ ```
358
+
359
+ The task obtains the local Codex bearer token and runs the tests with
360
+ `gpt-5.6-luna` at medium reasoning effort.
361
+
348
362
  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
363
 
350
364
  ## Release staging
351
365
 
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>`.
366
+ 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
367
 
354
368
  ## Acknowledgements
355
369
 
@@ -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" }),
@@ -0,0 +1,9 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
4
+
5
+ export function codexCacheKey(sessionId: string | undefined): string | undefined {
6
+ if (sessionId === undefined) return undefined;
7
+ if (Array.from(sessionId).length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH) return sessionId;
8
+ return createHash("sha256").update(sessionId, "utf8").digest("hex");
9
+ }
@@ -269,7 +269,7 @@ export function remoteCompactionPayload(options: {
269
269
  modelId: string;
270
270
  history: readonly ResponsesItem[];
271
271
  instructions: string;
272
- sessionId: string;
272
+ sessionId?: string | undefined;
273
273
  fallbackTools?: unknown[] | undefined;
274
274
  priority: boolean;
275
275
  }): JsonRecord {
@@ -290,7 +290,8 @@ export function remoteCompactionPayload(options: {
290
290
  typeof payload.parallel_tool_calls === "boolean" ? payload.parallel_tool_calls : true;
291
291
  payload.tool_choice ??= "auto";
292
292
  payload.include = [...new Set([...include, "reasoning.encrypted_content"])];
293
- payload.prompt_cache_key = options.sessionId;
293
+ if (options.sessionId) payload.prompt_cache_key = options.sessionId;
294
+ else delete payload.prompt_cache_key;
294
295
  payload.text =
295
296
  isObject(payload.text) && typeof payload.text.verbosity === "string"
296
297
  ? { verbosity: payload.text.verbosity }