pi-openai-codex-compat 0.0.18 → 0.0.19
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 +35 -0
- package/README.md +54 -35
- package/extensions/openai-codex-compat/apply-patch.ts +7 -0
- package/extensions/openai-codex-compat/codex-provider/codex-provider-contracts.ts +0 -2
- package/extensions/openai-codex-compat/codex-provider/codex-provider-runtime.ts +58 -23
- package/extensions/openai-codex-compat/codex-transport/codex-transport-contracts.ts +6 -0
- package/extensions/openai-codex-compat/codex-transport/codex-transport-errors.ts +13 -1
- package/extensions/openai-codex-compat/codex-transport/codex-transport-sse-stream.ts +1 -0
- package/extensions/openai-codex-compat/codex-transport/codex-transport-websocket-stream.ts +1 -0
- package/extensions/openai-codex-compat/codex-transport.ts +1 -0
- package/extensions/openai-codex-compat/command-tool-contract.ts +9 -0
- package/extensions/openai-codex-compat/command-tools.ts +4 -0
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +35 -145
- package/extensions/openai-codex-compat/config.ts +0 -24
- package/extensions/openai-codex-compat/footer.ts +0 -1
- package/extensions/openai-codex-compat/image-generation.ts +8 -0
- package/extensions/openai-codex-compat/index.ts +2 -2
- package/extensions/openai-codex-compat/pi-runtime.ts +62 -9
- package/extensions/openai-codex-compat/remote-compaction.ts +46 -71
- package/extensions/openai-codex-compat/request-options.ts +2 -9
- package/extensions/openai-codex-compat/request-tools-entry.ts +113 -0
- package/extensions/openai-codex-compat/settings-pane.ts +2 -11
- package/extensions/openai-codex-compat/web-run.ts +2 -0
- package/extensions/settings-session.ts +11 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.19 - 2026-10-01
|
|
6
|
+
|
|
7
|
+
### Removed
|
|
8
|
+
|
|
9
|
+
- The `reasoningMode` setting, its `PI_OPENAI_CODEX_COMPAT_REASONING_MODE` override,
|
|
10
|
+
and the `pro` footer label. The Codex endpoint rejects `reasoning.mode`, so the
|
|
11
|
+
extension now always omits it. Stale `reasoningMode` values in configuration
|
|
12
|
+
files are ignored.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Declare the same tools in native compaction requests as in turn requests while
|
|
17
|
+
Pi codemode is active. Compaction rebuilt the declarations from Pi's tool
|
|
18
|
+
registry, which lacks codemode's rewritten descriptions and still lists the
|
|
19
|
+
tools codemode `only` mode hides, so every compaction request missed the
|
|
20
|
+
prompt cache. Turn requests now save the tools they send in the session
|
|
21
|
+
whenever those tools change, and compaction declares the saved tools, also
|
|
22
|
+
after a resume or a branch switch.
|
|
23
|
+
- Resume sessions whose saved `/codex-settings` values include a setting this
|
|
24
|
+
version removed or no longer accepts, such as `reasoningMode`. Those fields are
|
|
25
|
+
skipped instead of failing the whole record at session start.
|
|
26
|
+
- Refuse to load on Pi runtimes older than 0.99.1. The load-time check accepted
|
|
27
|
+
any runtime since Pi 0.87 and reported the requirement as Pi 0.99.x. It now
|
|
28
|
+
requires Pi 0.99 host APIs and a reported Pi version of at least 0.99.1.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Forward every Codex stream event of a turn to Pi's `provider_stream_event` hook
|
|
33
|
+
as the server sent it, including metadata, error events, and terminal aliases,
|
|
34
|
+
so debugging extensions see Codex traffic. Pi reports a failing handler and
|
|
35
|
+
continues the turn.
|
|
36
|
+
- Annotate the command tools, `apply_patch`, `image_gen.imagegen`, and `web.run`
|
|
37
|
+
with read-only, destructive, and open-world hints for permission extensions.
|
|
38
|
+
`web.run` is the only read-only tool.
|
|
39
|
+
|
|
5
40
|
## 0.0.18 - 2026-09-30
|
|
6
41
|
|
|
7
42
|
### Fixed
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ OpenAI Codex compatibility for [Pi](https://github.com/earendil-works/pi-mono),
|
|
|
13
13
|
- **Standalone web search**: exposes Pi's dotted `web.run` tool as a native Responses namespace and executes search and browsing through Codex `alpha/search`.
|
|
14
14
|
- **Dedicated Codex tool UI**: renders command tools, `apply_patch`, `image_gen.imagegen`, and `web.run` on a shared configurable surface with compact summaries and `Ctrl+O` expansion.
|
|
15
15
|
- **Hosted web-search fallback**: injects native `web_search` only when `web.run` is inactive, with cached, indexed, or live modes.
|
|
16
|
-
- **Native request controls**: configures Responses API text verbosity
|
|
16
|
+
- **Native request controls**: configures Responses API text verbosity and reasoning summaries.
|
|
17
17
|
- **Draft settings pane**: `/codex-settings` stages edits before applying them. Apply to session stores them with the session without changing configuration files. Ctrl+S saves and applies them. Escape discards only unapplied drafts.
|
|
18
18
|
- **Session-aware footer**: shows the current Pi session ID on the first line and appends non-default Codex request modes to the model side of Pi's normal second line.
|
|
19
19
|
|
|
@@ -26,9 +26,14 @@ Pi provides the Codex OAuth flow and model catalog. At session start, this packa
|
|
|
26
26
|
- An OpenAI Codex login in Pi
|
|
27
27
|
|
|
28
28
|
Restart Pi after upgrading its runtime. `/reload` reloads extensions but cannot
|
|
29
|
-
upgrade the running Pi process.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
upgrade the running Pi process. At load time, the extension requires the host's
|
|
30
|
+
transcript APIs, the Pi 0.99 APIs `SessionManager.getEntryCount()` and
|
|
31
|
+
`ExtensionAPI.getSettings()`, and a reported Pi version of at least 0.99.1. It
|
|
32
|
+
refuses to load otherwise. The load check does not reject newer releases: the
|
|
33
|
+
`<0.100.0` bound is the package's peer range, which Pi does not enforce when it
|
|
34
|
+
installs packages. `PI_PACKAGE_DIR` can point an older executable at
|
|
35
|
+
newer package metadata, so the reported version alone does not establish
|
|
36
|
+
compatibility. The API checks cover that case.
|
|
32
37
|
|
|
33
38
|
Authenticate through Pi if needed:
|
|
34
39
|
|
|
@@ -36,27 +41,29 @@ Authenticate through Pi if needed:
|
|
|
36
41
|
/login openai-codex
|
|
37
42
|
```
|
|
38
43
|
|
|
44
|
+
**Pi's virtual models are not supported.** The package's features check the selected model, not the model that answers a request. Pi's experimental virtual models, registered with `pi.registerVirtualModel()`, stay selected while Pi routes each request to a physical model. With a virtual model selected, the package deactivates its tools, omits fast mode and the other request controls, and leaves compaction and output-limit continuation to Pi. This holds even for a virtual model listed under `openai-codex` or one that routes every request to an `openai-codex` model. Routed `openai-codex` requests still use the package's transport. Select an `openai-codex` model directly to use the package's features.
|
|
45
|
+
|
|
39
46
|
## Compatibility baseline and differences
|
|
40
47
|
|
|
41
48
|
The compatibility baseline is official Codex CLI `0.149.1`, released August 24, 2026, at commit `ff29a44391deccde0aba0f8390337d7f3c319ea4`. It retains the `0.147.0` Responses Lite contract that groups direct function and custom declarations into one canonical `functions` namespace for namespace-capable providers. The `0.148.0`–`0.149.0` review adopted typed misalignment-policy failures while recording official unbounded connection recovery as an intentional bounded-transport deviation. The `0.149.1` review retained this package's checkpoint shape rather than exposing official Codex's new default-disabled retained-image budget. The [official release compatibility log](OFFICIAL_CODEX_CLI_RELEASES.md) is the canonical release-by-release record of protocol and `apply_patch` alignment, intentional deviations, and excluded runtimes. This section is the package's user-facing compatibility contract. See the [Responses Lite compatibility report](RESPONSES_LITE_COMPATIBILITY.md) and [Codex caching and transport comparison](CODEX_CACHE_RESEARCH.md) for detailed request-path findings and live cache trajectories.
|
|
42
49
|
|
|
43
50
|
### Configurable defaults that differ from Codex
|
|
44
51
|
|
|
45
|
-
| Area | This package by default
|
|
46
|
-
| --------------------------------------------- |
|
|
47
|
-
| 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.
|
|
48
|
-
| Image-generation tool | Enabled whenever an `openai-codex` model is selected. Backend capability and account failures surface when the tool executes.
|
|
49
|
-
| Standalone `web.run` | Disabled by default; when enabled, preferred over hosted `web_search` and sent with the complete reserved schema and description.
|
|
50
|
-
| Hosted web search | Disabled by default; when enabled, injected only for ordinary Responses while `web.run` is inactive. Responses Lite omits hosted tools.
|
|
51
|
-
| Coding mutation tools | Enables `apply_patch` and suppresses Pi's active `edit` and `write` tools.
|
|
52
|
-
| Command tools | Replaces an active Pi `bash` tool with `exec_command` and `write_stdin`.
|
|
53
|
-
| `apply_patch` debug output | Disabled; collapsed results show the normal visual summary and instruction rows.
|
|
54
|
-
| `apply_patch` diagnostics capture | Disabled; no separate request or filesystem snapshot artifacts are retained.
|
|
55
|
-
| Codex tool background | Uses a subtle theme-derived surface for extension-owned Codex tools.
|
|
56
|
-
| Auto-compaction trigger | Relies on Pi's reserve-token threshold unless a percentage is configured.
|
|
57
|
-
| Fast mode | Uses the normal tier.
|
|
58
|
-
| Responses Lite | Disabled; supported models use ordinary Responses.
|
|
59
|
-
| Text and reasoning request controls | Sends low text verbosity and automatic reasoning summaries
|
|
52
|
+
| Area | This package by default | Official Codex | Configuration |
|
|
53
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| 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`. |
|
|
55
|
+
| 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. |
|
|
56
|
+
| 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. |
|
|
57
|
+
| 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`. |
|
|
58
|
+
| 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. |
|
|
59
|
+
| Command tools | Replaces an active Pi `bash` tool with `exec_command` and `write_stdin`. | Chooses unified exec or legacy shell from model metadata, platform, execution environment, and runtime capabilities. | `shellTool`: `unified_exec` or `shell_command`. |
|
|
60
|
+
| `apply_patch` debug output | Disabled; collapsed results show the normal visual summary and instruction rows. | Not applicable to Pi's tool-result renderer. | `applyPatchDebug`: boolean. |
|
|
61
|
+
| `apply_patch` diagnostics capture | Disabled; no separate request or filesystem snapshot artifacts are retained. | Codex owns its rollout diagnostics rather than writing this package's artifact format. | `applyPatchDiagnostics`: boolean. |
|
|
62
|
+
| 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`. |
|
|
63
|
+
| 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. Mid-response percentage boundaries use Pi's bounded compact-and-continue lifecycle, so Pi auto-compaction must remain enabled. |
|
|
64
|
+
| Fast mode | Uses the normal tier. | Uses the configured Codex service tier. | `fastMode`: boolean; `true` requests the priority tier. |
|
|
65
|
+
| Responses Lite | Disabled; supported models use ordinary Responses. | Enabled according to Codex model metadata. | `responsesLite`: boolean; `true` enables Responses Lite. |
|
|
66
|
+
| Text and reasoning request controls | Sends low text verbosity and automatic reasoning summaries, and omits `reasoning.mode`, which the Codex endpoint rejects. | Resolves these controls through Codex configuration, model metadata, and turn state. | `textVerbosity` and `reasoningSummary`. |
|
|
60
67
|
|
|
61
68
|
`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.
|
|
62
69
|
|
|
@@ -64,7 +71,7 @@ The compatibility baseline is official Codex CLI `0.149.1`, released August 24,
|
|
|
64
71
|
|
|
65
72
|
| Area | Difference |
|
|
66
73
|
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
67
|
-
| Session storage | Pi remains the canonical session owner. Opaque Codex compaction checkpoints are stored in Pi `compaction` entries,
|
|
74
|
+
| Session storage | Pi remains the canonical session owner. Opaque Codex compaction checkpoints are stored in Pi `compaction` entries, otherwise lossy Responses output is stored in sparse custom native-response entries, and each change to the tools a turn request sends is stored in a custom request-tools entry. Official Codex owns a rollout/thread store directly. |
|
|
68
75
|
| 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. |
|
|
69
76
|
| Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
|
|
70
77
|
| System instructions | Send the leading Pi system message as the prompt, as Pi AI's Codex adapter does: Responses Lite models prepend it as developer input after `additional_tools`; other models send it through Responses `instructions`. Later Pi system messages (`/reload`, section changes) travel inline as developer items on models that accept mid-conversation system messages and collapse into the leading prompt otherwise. A forced prompt replaces the leading message. Compaction snapshots and resumed sessions lead with Pi's replayed prompt without rewriting old checkpoints. |
|
|
@@ -129,6 +136,19 @@ The following official Codex facilities are not exact equivalents in this packag
|
|
|
129
136
|
| Goals, memories, and remote skill-resource tools | Not implemented; Pi's sessions, files, and native skills remain separate systems. |
|
|
130
137
|
| Remote/deferred execution environments | Not implemented. |
|
|
131
138
|
|
|
139
|
+
Each tool declares Pi tool annotations, which permission extensions can use to decide which calls to confirm:
|
|
140
|
+
|
|
141
|
+
| Tool | Read-only | Destructive | Open world |
|
|
142
|
+
| ---------------------------------------------- | --------- | ----------- | ---------- |
|
|
143
|
+
| `exec_command`, `write_stdin`, `shell_command` | No | Yes | Yes |
|
|
144
|
+
| `apply_patch` | No | Yes | No |
|
|
145
|
+
| `image_gen.imagegen` | No | No | Yes |
|
|
146
|
+
| `web.run` | Yes | n/a | Yes |
|
|
147
|
+
|
|
148
|
+
None of the tools is idempotent. Pi does not verify annotations.
|
|
149
|
+
|
|
150
|
+
The provider passes every event of a turn request, over WebSocket and SSE, to Pi's `provider_stream_event` hook as the server sent it. Forwarding happens before the transport drops WebSocket `response.metadata` events, renames `response.done` to `response.completed`, or turns an `error` event into a failed turn. Prewarm and remote compaction requests are not forwarded. Pi reports a failing hook handler and continues the turn. When an SDK caller passes its own `onProviderStreamEvent` callback and it throws, the turn ends with an error without a WebSocket retry or the SSE fallback.
|
|
151
|
+
|
|
132
152
|
See [`OFFICIAL_CODEX_CLI_TOOL_CATALOG.md`](OFFICIAL_CODEX_CLI_TOOL_CATALOG.md) for the complete researched Codex tool inventory, and [`CUSTOM_CODEX_PROVIDER_WEB_REFERENCES.md`](CUSTOM_CODEX_PROVIDER_WEB_REFERENCES.md) for the unimplemented citation/reference design.
|
|
133
153
|
|
|
134
154
|
## Install
|
|
@@ -224,7 +244,7 @@ deleted. External editors do not participate in that lock.
|
|
|
224
244
|
Session overrides retain their original conflict checks across menu reopenings.
|
|
225
245
|
Conflicting external edits remain protected after reload or restart.
|
|
226
246
|
|
|
227
|
-
The effective settings are printed once when a TUI session starts. The footer shows the current Pi session ID alongside the working directory and optional session name. It shows `fast`
|
|
247
|
+
The effective settings are printed once when a TUI session starts. The footer shows the current Pi session ID alongside the working directory and optional session name. It shows `fast` only when enabled, and shows text verbosity or reasoning summary only when they differ from their defaults.
|
|
228
248
|
|
|
229
249
|
Example:
|
|
230
250
|
|
|
@@ -243,8 +263,7 @@ Example:
|
|
|
243
263
|
"autoCompactAtPercent": 90,
|
|
244
264
|
"webSearch": "disabled",
|
|
245
265
|
"textVerbosity": "low",
|
|
246
|
-
"reasoningSummary": "auto"
|
|
247
|
-
"reasoningMode": "standard"
|
|
266
|
+
"reasoningSummary": "auto"
|
|
248
267
|
}
|
|
249
268
|
```
|
|
250
269
|
|
|
@@ -266,18 +285,10 @@ Defaults:
|
|
|
266
285
|
| `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. |
|
|
267
286
|
| `textVerbosity` | `low`, `medium`, `high` | `low` | Sets Responses API `text.verbosity`. |
|
|
268
287
|
| `reasoningSummary` | `auto`, `concise`, `detailed`, `off` | `auto` | Sets `reasoning.summary` when reasoning is enabled; `off` omits the summary parameter. |
|
|
269
|
-
| `reasoningMode` | `standard`, `pro` | `standard` | Controls supported models' execution mode independently of Pi's reasoning-effort control. The default omits `reasoning.mode`; `pro` sends `reasoning.mode: "pro"`. |
|
|
270
288
|
|
|
271
289
|
Responses Lite supports exactly `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`,
|
|
272
|
-
`gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, and `gpt-6.1-sol`.
|
|
273
|
-
|
|
274
|
-
Luna, and GPT-6.1 Sol use standard reasoning mode even when `pro` is configured.
|
|
275
|
-
Both controls remain opt-in. GPT-6.1 Sol requires Pi 0.99.1's model catalog.
|
|
276
|
-
|
|
277
|
-
As of September 30, 2026, the Codex endpoint rejects `reasoning.mode` for every
|
|
278
|
-
tested model, including those listed above, so `pro` requests fail there. The
|
|
279
|
-
setting remains for providers that accept it. Releases do not test pro mode
|
|
280
|
-
against `openai-codex`.
|
|
290
|
+
`gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, and `gpt-6.1-sol`. It remains opt-in.
|
|
291
|
+
GPT-6.1 Sol requires Pi 0.99.1's model catalog.
|
|
281
292
|
|
|
282
293
|
Invalid JSON setting values are ignored and invalid JSON does not prevent Pi from
|
|
283
294
|
starting. The settings pane reports malformed JSON instead of overwriting it.
|
|
@@ -301,7 +312,6 @@ Every setting can also be overridden for one Pi process with an environment vari
|
|
|
301
312
|
| `webSearch` | `PI_OPENAI_CODEX_COMPAT_WEB_SEARCH_MODE` |
|
|
302
313
|
| `textVerbosity` | `PI_OPENAI_CODEX_COMPAT_TEXT_VERBOSITY` |
|
|
303
314
|
| `reasoningSummary` | `PI_OPENAI_CODEX_COMPAT_REASONING_SUMMARY` |
|
|
304
|
-
| `reasoningMode` | `PI_OPENAI_CODEX_COMPAT_REASONING_MODE` |
|
|
305
315
|
|
|
306
316
|
Environment variables have the highest precedence: defaults < global JSON < trusted-project JSON < environment. Boolean values accept `true`/`false`, `1`/`0`, `on`/`off`, or `enabled`/`disabled`. Other settings use the values in the defaults table; `PI_OPENAI_CODEX_COMPAT_AUTO_COMPACT_AT_PERCENT=off` and `PI_OPENAI_CODEX_COMPAT_AUTO_COMPACT_AT_PERCENT=default` explicitly select Pi's default compaction lifecycle.
|
|
307
317
|
|
|
@@ -391,6 +401,15 @@ The extension handles native compaction for `openai-codex`. It follows the Codex
|
|
|
391
401
|
deduplicated, because each one is a distinct instruction the model already saw.
|
|
392
402
|
4. Persist the opaque checkpoint in the Pi session and replay it on later requests.
|
|
393
403
|
|
|
404
|
+
A compaction request declares the same tools as the branch's latest turn request to the
|
|
405
|
+
selected model, so both share a prompt-cache prefix. Pi's transcript lists every active
|
|
406
|
+
tool, but Pi leaves some out of requests, for example the tools codemode hides in `only`
|
|
407
|
+
mode, and other extensions can edit a request's tools. Each turn request therefore saves
|
|
408
|
+
the tools it sent in an `openai-codex-compat-request-tools` session entry whenever they
|
|
409
|
+
differ from the branch's latest saved tools for that model. Compaction declares the saved
|
|
410
|
+
tools, also after a resume or a branch switch. A branch without a saved entry for the
|
|
411
|
+
model, such as one an older version wrote, declares the transcript's current tools.
|
|
412
|
+
|
|
394
413
|
Overflow and length recovery follow Pi 0.87's `context_edit` semantics. Every request
|
|
395
414
|
reads Pi's session projection, so earlier omissions and replacements (an elided tool
|
|
396
415
|
output, an omitted reply) apply exactly as they do for Pi's own adapters. Before an
|
|
@@ -670,7 +689,7 @@ The task obtains the local Codex bearer token and runs the tests with
|
|
|
670
689
|
effort. It also packs the extension and loads that archive through the shipped Pi
|
|
671
690
|
0.99.1 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
|
|
672
691
|
prompt reload, native compaction, and persisted-session resume against Codex.
|
|
673
|
-
|
|
692
|
+
SDK tests verify Responses Lite
|
|
674
693
|
WebSocket history, prewarming, continuation, and the built-in read tool.
|
|
675
694
|
CLI tests also execute the built-in read tool after resume.
|
|
676
695
|
Test credentials stay in memory and child-process environments. Test sessions
|
|
@@ -135,6 +135,13 @@ export default function registerApplyPatch(
|
|
|
135
135
|
},
|
|
136
136
|
executionMode: "sequential",
|
|
137
137
|
renderShell: "self",
|
|
138
|
+
// Patches can overwrite and delete local files; they never reach the network.
|
|
139
|
+
annotations: {
|
|
140
|
+
readOnlyHint: false,
|
|
141
|
+
destructiveHint: true,
|
|
142
|
+
idempotentHint: false,
|
|
143
|
+
openWorldHint: false,
|
|
144
|
+
},
|
|
138
145
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
139
146
|
const diagnosticsStartedAt = performance.now();
|
|
140
147
|
let preparedDiagnostics: PreparedApplyPatchDiagnostics | undefined;
|
|
@@ -47,8 +47,6 @@ export type RuntimeScope = {
|
|
|
47
47
|
export type RequestTemplate = {
|
|
48
48
|
modelId: string;
|
|
49
49
|
payload: JsonRecord;
|
|
50
|
-
/** Fingerprint of the raw tool definitions that produced `payload.tools`. */
|
|
51
|
-
toolFingerprint: string;
|
|
52
50
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
53
51
|
requestOptions: OpenAICodexResponsesOptions;
|
|
54
52
|
};
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
clampThinkingLevel,
|
|
7
7
|
createAssistantMessageEventStream,
|
|
8
8
|
getCurrentTools,
|
|
9
|
-
getDeclaredTools,
|
|
10
9
|
type Api,
|
|
11
10
|
type AssistantMessage,
|
|
12
11
|
type AssistantMessageEventStream,
|
|
@@ -28,11 +27,12 @@ import {
|
|
|
28
27
|
checkpointData,
|
|
29
28
|
providerHistory,
|
|
30
29
|
remoteCompactionMarkerSummary,
|
|
30
|
+
requestGrammarToolInputProperties,
|
|
31
31
|
requestInstructions,
|
|
32
|
+
requestTools,
|
|
32
33
|
resolveRequestTranscript,
|
|
33
34
|
responsesCompatibility,
|
|
34
35
|
searchCheckpoint,
|
|
35
|
-
toolDefinitionFingerprint,
|
|
36
36
|
type CheckpointData,
|
|
37
37
|
type CompactionDecision,
|
|
38
38
|
type GrammarToolInputProperties,
|
|
@@ -57,11 +57,18 @@ import {
|
|
|
57
57
|
} from "../codex-metadata.ts";
|
|
58
58
|
import { resolveCodexThreadIdentity, type CodexThreadIdentity } from "../codex-thread-lineage.ts";
|
|
59
59
|
import { applyResponsesLite } from "../responses-lite.ts";
|
|
60
|
+
import {
|
|
61
|
+
REQUEST_TOOLS_ENTRY_TYPE,
|
|
62
|
+
requestToolsChanged,
|
|
63
|
+
requestToolsData,
|
|
64
|
+
type RequestToolsData,
|
|
65
|
+
} from "../request-tools-entry.ts";
|
|
60
66
|
import { requiredValue } from "../required-value.ts";
|
|
61
67
|
import { processCodexStream, type CodexStreamAttemptState } from "../codex-stream.ts";
|
|
62
68
|
import {
|
|
63
69
|
CodexTransport,
|
|
64
70
|
CodexTurnState,
|
|
71
|
+
ProviderStreamEventCallbackError,
|
|
65
72
|
WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE,
|
|
66
73
|
isWebSocketConnectionLimitReachedError,
|
|
67
74
|
validateCodexAuthentication,
|
|
@@ -84,11 +91,7 @@ import {
|
|
|
84
91
|
type ResponsesInputItem,
|
|
85
92
|
type ResponsesOutputItem,
|
|
86
93
|
} from "../responses-item-schema.ts";
|
|
87
|
-
import {
|
|
88
|
-
convertResponsesTools,
|
|
89
|
-
convertResponsesMessages,
|
|
90
|
-
createGrammarToolInputProperties,
|
|
91
|
-
} from "../vendor/pi-ai/openai-responses-serialization.ts";
|
|
94
|
+
import { convertResponsesMessages } from "../vendor/pi-ai/openai-responses-serialization.ts";
|
|
92
95
|
import { errorFromThrown } from "../error-from-thrown.ts";
|
|
93
96
|
import type {
|
|
94
97
|
ActiveAgentTurn,
|
|
@@ -539,13 +542,12 @@ export class CodexProviderRuntime {
|
|
|
539
542
|
grammarToolInputProperties,
|
|
540
543
|
turnId,
|
|
541
544
|
} = options;
|
|
542
|
-
const compat = responsesCompatibility(model.compat);
|
|
543
545
|
// Every request declares the complete current tool set at the top level,
|
|
544
546
|
// as the official Codex client does. Replaying additions inside the input
|
|
545
547
|
// made the Codex backend unreliable about which tools exist, and a native
|
|
546
548
|
// checkpoint drops such history anyway. A mid-session tool change costs one
|
|
547
549
|
// cache miss.
|
|
548
|
-
const
|
|
550
|
+
const currentTools = getCurrentTools(context.messages);
|
|
549
551
|
// The leading system message is the prompt; later system messages travel
|
|
550
552
|
// inline in `input` (or collapsed into the leading message by
|
|
551
553
|
// resolveRequestTranscript), as Pi AI's Codex adapter does.
|
|
@@ -573,14 +575,7 @@ export class CodexProviderRuntime {
|
|
|
573
575
|
),
|
|
574
576
|
});
|
|
575
577
|
if (requestOptions.serviceTier !== undefined) body.service_tier = requestOptions.serviceTier;
|
|
576
|
-
if (
|
|
577
|
-
body.tools = convertResponsesTools(requestTools, {
|
|
578
|
-
strict: false,
|
|
579
|
-
supportsStrictMode: compat.supportsStrictMode ?? true,
|
|
580
|
-
supportsOpenAIGrammarTools: compat.supportsOpenAIGrammarTools ?? false,
|
|
581
|
-
namespacedToolNames: CODEX_NAMESPACED_TOOL_NAMES,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
578
|
+
if (currentTools.length > 0) body.tools = requestTools(model, currentTools);
|
|
584
579
|
if (requestOptions.reasoningEffort !== undefined) {
|
|
585
580
|
const mapped =
|
|
586
581
|
requestOptions.reasoningEffort === "none"
|
|
@@ -846,10 +841,7 @@ export class CodexProviderRuntime {
|
|
|
846
841
|
requestOptions.signal,
|
|
847
842
|
);
|
|
848
843
|
}
|
|
849
|
-
const grammarToolInputProperties =
|
|
850
|
-
getDeclaredTools(context.messages),
|
|
851
|
-
responsesCompatibility(model.compat).supportsOpenAIGrammarTools ?? false,
|
|
852
|
-
);
|
|
844
|
+
const grammarToolInputProperties = requestGrammarToolInputProperties(model, context.messages);
|
|
853
845
|
let body = this.buildRequestBody({
|
|
854
846
|
model,
|
|
855
847
|
context,
|
|
@@ -868,15 +860,30 @@ export class CodexProviderRuntime {
|
|
|
868
860
|
body = requireJsonRecord(transformed, "Codex payload replacement");
|
|
869
861
|
}
|
|
870
862
|
}
|
|
871
|
-
|
|
863
|
+
// Only agent turns define the prefix native compaction must match. Other
|
|
864
|
+
// requests with the session id, such as Pi's bug-report summary, run
|
|
865
|
+
// outside an agent run and must not replace the turn template or tools.
|
|
866
|
+
const agentRequest =
|
|
867
|
+
runtimeSessionId !== undefined && this.activeAgentTurns.has(runtimeSessionId);
|
|
868
|
+
if (runtimeSessionId && agentRequest) {
|
|
872
869
|
this.templates.set(runtimeSessionId, {
|
|
873
870
|
modelId: model.id,
|
|
874
871
|
payload: replacementValues ? {} : withoutConversationInput(body),
|
|
875
|
-
toolFingerprint: toolDefinitionFingerprint(getCurrentTools(context.messages)),
|
|
876
872
|
grammarToolInputProperties,
|
|
877
873
|
requestOptions: { ...requestOptions },
|
|
878
874
|
});
|
|
879
875
|
}
|
|
876
|
+
// Saved after the response, because an entry appended now would move the
|
|
877
|
+
// branch leaf that percentage compaction checks. A payload edit that
|
|
878
|
+
// removed `tools` sent no tools.
|
|
879
|
+
const sentTools =
|
|
880
|
+
agentRequest && !replacementValues
|
|
881
|
+
? requestToolsData(
|
|
882
|
+
model.id,
|
|
883
|
+
Array.isArray(body.tools) ? body.tools.filter(isObject) : [],
|
|
884
|
+
grammarToolInputProperties,
|
|
885
|
+
)
|
|
886
|
+
: undefined;
|
|
880
887
|
if (!replacementValues) {
|
|
881
888
|
body = await this.maybeCompactPercentage(
|
|
882
889
|
model,
|
|
@@ -937,6 +944,7 @@ export class CodexProviderRuntime {
|
|
|
937
944
|
startEmitted = true;
|
|
938
945
|
stream.push({ type: "start", partial: output });
|
|
939
946
|
};
|
|
947
|
+
const onProviderStreamEvent = requestOptions.onProviderStreamEvent;
|
|
940
948
|
const transportRequestOptions = {
|
|
941
949
|
...requestOptions,
|
|
942
950
|
accountId,
|
|
@@ -950,6 +958,19 @@ export class CodexProviderRuntime {
|
|
|
950
958
|
webSocketResponseHandle = handle;
|
|
951
959
|
},
|
|
952
960
|
onTransportStart: emitStart,
|
|
961
|
+
// Pi's `provider_stream_event` hook sees each event as the server sent
|
|
962
|
+
// it, before the transport filters or normalizes it.
|
|
963
|
+
...(onProviderStreamEvent
|
|
964
|
+
? {
|
|
965
|
+
async forwardRawEvent(event: JsonRecord) {
|
|
966
|
+
try {
|
|
967
|
+
await onProviderStreamEvent(event, model);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
throw new ProviderStreamEventCallbackError(error);
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
}
|
|
973
|
+
: {}),
|
|
953
974
|
onTransportDiagnostic(diagnostic: CodexTransportDiagnostic) {
|
|
954
975
|
output.diagnostics = [
|
|
955
976
|
...(output.diagnostics ?? []),
|
|
@@ -1204,6 +1225,7 @@ export class CodexProviderRuntime {
|
|
|
1204
1225
|
(item) =>
|
|
1205
1226
|
item["type"] !== "function_call_output" && item["type"] !== "custom_tool_call_output",
|
|
1206
1227
|
);
|
|
1228
|
+
if (runtimeSessionId && sentTools) this.saveRequestTools(runtimeSessionId, sentTools);
|
|
1207
1229
|
const persistNativeItems =
|
|
1208
1230
|
rawItems.length > 0 &&
|
|
1209
1231
|
(responseRequests > 1 || nativeOverrideRequired(rawItems, canonicalItems));
|
|
@@ -1277,6 +1299,19 @@ export class CodexProviderRuntime {
|
|
|
1277
1299
|
return this.templates.get(sessionId);
|
|
1278
1300
|
}
|
|
1279
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* Save the tools a turn request of the session sends when they differ from the
|
|
1304
|
+
* branch's latest saved tools for the model. Native compaction declares them,
|
|
1305
|
+
* because the transcript also lists tools Pi leaves out of requests.
|
|
1306
|
+
*/
|
|
1307
|
+
private saveRequestTools(sessionId: string, data: RequestToolsData): void {
|
|
1308
|
+
const scope = this.scopes.get(sessionId);
|
|
1309
|
+
if (!scope) return;
|
|
1310
|
+
if (requestToolsChanged(scope.manager.getBranch(), data)) {
|
|
1311
|
+
this.pi.appendEntry(REQUEST_TOOLS_ENTRY_TYPE, data);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1280
1315
|
async compact(options: {
|
|
1281
1316
|
model: Model<Api>;
|
|
1282
1317
|
requestOptions: OpenAICodexResponsesOptions;
|
|
@@ -190,6 +190,12 @@ export type CodexTransportOptions = OpenAICodexResponsesOptions & {
|
|
|
190
190
|
onWebSocketResponseHandle?: (handle: CodexWebSocketResponseHandle) => void;
|
|
191
191
|
onTransportStart?: () => void;
|
|
192
192
|
onTransportDiagnostic?: (diagnostic: CodexTransportDiagnostic) => void;
|
|
193
|
+
/**
|
|
194
|
+
* Receives each parsed server event before the transport filters or
|
|
195
|
+
* normalizes it. A rejection must be a `ProviderStreamEventCallbackError`,
|
|
196
|
+
* which ends the request without a retry or transport fallback.
|
|
197
|
+
*/
|
|
198
|
+
forwardRawEvent?: (event: JsonRecord) => Promise<void>;
|
|
193
199
|
warmup?: boolean;
|
|
194
200
|
requestKind?: CodexRequestKind;
|
|
195
201
|
turnState?: CodexTurnState;
|
|
@@ -102,11 +102,23 @@ export function thrownMessage(error: unknown): string {
|
|
|
102
102
|
return error instanceof Error ? error.message || error.name : String(error);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** A failed `provider_stream_event` callback. It ends the turn and is never retried. */
|
|
106
|
+
export class ProviderStreamEventCallbackError extends Error {
|
|
107
|
+
constructor(cause: unknown) {
|
|
108
|
+
super(
|
|
109
|
+
`Provider stream event handler failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
110
|
+
{ cause },
|
|
111
|
+
);
|
|
112
|
+
this.name = "ProviderStreamEventCallbackError";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
export function isCodexNonTransportError(error: unknown): boolean {
|
|
106
117
|
return (
|
|
107
118
|
error instanceof CodexApiError ||
|
|
108
119
|
error instanceof CodexHttpError ||
|
|
109
|
-
error instanceof CodexProtocolError
|
|
120
|
+
error instanceof CodexProtocolError ||
|
|
121
|
+
error instanceof ProviderStreamEventCallbackError
|
|
110
122
|
);
|
|
111
123
|
}
|
|
112
124
|
|
|
@@ -206,6 +206,7 @@ export async function* requestSse(
|
|
|
206
206
|
options.onTransportStart?.();
|
|
207
207
|
let terminal = false;
|
|
208
208
|
for await (const event of parseSse(response, options.signal)) {
|
|
209
|
+
await options.forwardRawEvent?.(event);
|
|
209
210
|
const normalized = normalizeEvent(event);
|
|
210
211
|
if (!normalized) continue;
|
|
211
212
|
yield normalized;
|
|
@@ -254,6 +254,7 @@ export async function* requestWebSocket(
|
|
|
254
254
|
let responseId: string | undefined;
|
|
255
255
|
let responseCompleted = false;
|
|
256
256
|
for await (const event of parseWebSocket(acquired.socket, options.signal, timeoutMs)) {
|
|
257
|
+
await options.forwardRawEvent?.(event);
|
|
257
258
|
captureTurnStateEvent(event, options.turnState);
|
|
258
259
|
if (event.type === "response.metadata") continue;
|
|
259
260
|
if (
|
|
@@ -95,6 +95,7 @@ export {
|
|
|
95
95
|
resetOpenAICodexWebSocketDebugStats,
|
|
96
96
|
} from "./codex-transport/codex-transport-websocket-pool.ts";
|
|
97
97
|
export {
|
|
98
|
+
ProviderStreamEventCallbackError,
|
|
98
99
|
WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE,
|
|
99
100
|
isWebSocketConnectionLimitReachedError,
|
|
100
101
|
} from "./codex-transport/codex-transport-errors.ts";
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import type { ToolAnnotations } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
|
|
3
4
|
export const EXEC_COMMAND_TOOL_NAME = "exec_command";
|
|
4
5
|
export const WRITE_STDIN_TOOL_NAME = "write_stdin";
|
|
5
6
|
export const SHELL_COMMAND_TOOL_NAME = "shell_command";
|
|
6
7
|
|
|
8
|
+
/** Commands run arbitrary programs: they can overwrite or delete data and reach the network. */
|
|
9
|
+
export const COMMAND_TOOL_ANNOTATIONS = {
|
|
10
|
+
readOnlyHint: false,
|
|
11
|
+
destructiveHint: true,
|
|
12
|
+
idempotentHint: false,
|
|
13
|
+
openWorldHint: true,
|
|
14
|
+
} as const satisfies ToolAnnotations;
|
|
15
|
+
|
|
7
16
|
export type CommandToolPromptMetadata = {
|
|
8
17
|
description: string;
|
|
9
18
|
promptSnippet: string;
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type CommandCallRenderContext,
|
|
25
25
|
} from "./command-render.ts";
|
|
26
26
|
import {
|
|
27
|
+
COMMAND_TOOL_ANNOTATIONS,
|
|
27
28
|
EXEC_COMMAND_PARAMETERS,
|
|
28
29
|
EXEC_COMMAND_TOOL_NAME,
|
|
29
30
|
execCommandPromptMetadata,
|
|
@@ -253,6 +254,7 @@ export default function registerCommandTools(
|
|
|
253
254
|
pi.registerTool<typeof EXEC_COMMAND_PARAMETERS, CommandOutputDetails, ExecCommandRenderState>({
|
|
254
255
|
name: EXEC_COMMAND_TOOL_NAME,
|
|
255
256
|
label: EXEC_COMMAND_TOOL_NAME,
|
|
257
|
+
annotations: COMMAND_TOOL_ANNOTATIONS,
|
|
256
258
|
description: execCommandPrompt.description,
|
|
257
259
|
promptSnippet: execCommandPrompt.promptSnippet,
|
|
258
260
|
promptGuidelines: execCommandPrompt.promptGuidelines,
|
|
@@ -289,6 +291,7 @@ export default function registerCommandTools(
|
|
|
289
291
|
pi.registerTool<typeof WRITE_STDIN_PARAMETERS, CommandOutputDetails, WriteStdinRenderState>({
|
|
290
292
|
name: WRITE_STDIN_TOOL_NAME,
|
|
291
293
|
label: WRITE_STDIN_TOOL_NAME,
|
|
294
|
+
annotations: COMMAND_TOOL_ANNOTATIONS,
|
|
292
295
|
description: writeStdinPrompt.description,
|
|
293
296
|
promptSnippet: writeStdinPrompt.promptSnippet,
|
|
294
297
|
promptGuidelines: writeStdinPrompt.promptGuidelines,
|
|
@@ -318,6 +321,7 @@ export default function registerCommandTools(
|
|
|
318
321
|
pi.registerTool({
|
|
319
322
|
name: SHELL_COMMAND_TOOL_NAME,
|
|
320
323
|
label: SHELL_COMMAND_TOOL_NAME,
|
|
324
|
+
annotations: COMMAND_TOOL_ANNOTATIONS,
|
|
321
325
|
description: shellCommandPrompt.description,
|
|
322
326
|
promptSnippet: shellCommandPrompt.promptSnippet,
|
|
323
327
|
promptGuidelines: shellCommandPrompt.promptGuidelines,
|