pi-openai-codex-compat 0.0.10-alpha.7 → 0.0.10
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 +18 -4
- package/README.md +51 -23
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/bin/pi-apply-patch.ts +13 -0
- package/extensions/openai-codex-compat/apply-patch-cli.ts +65 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +16 -1
- package/extensions/openai-codex-compat/codex-provider/codex-provider-history.ts +2 -33
- package/extensions/openai-codex-compat/codex-provider/codex-provider-response-attempts.ts +3 -2
- package/extensions/openai-codex-compat/codex-provider/codex-provider-runtime.ts +66 -25
- package/extensions/openai-codex-compat/codex-transport/codex-transport-contracts.ts +3 -3
- package/extensions/openai-codex-compat/codex-transport/codex-transport-websocket-continuation.ts +4 -4
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +34 -47
- package/extensions/openai-codex-compat/footer.ts +4 -1
- package/extensions/openai-codex-compat/remote-compaction.ts +6 -10
- package/extensions/openai-codex-compat/request-options.ts +1 -1
- package/extensions/openai-codex-compat/responses-lite.ts +1 -0
- package/extensions/openai-codex-compat/vendor/pi-ai/README.md +14 -6
- package/extensions/openai-codex-compat/vendor/pi-ai/openai-responses-serialization.ts +71 -64
- package/package.json +11 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.10 - 2026-09-20
|
|
6
|
+
|
|
5
7
|
### Added
|
|
6
8
|
|
|
9
|
+
- Add the `pi-apply-patch parse` command-line entry point, which parses a
|
|
10
|
+
Codex patch document from a file or standard input and prints its
|
|
11
|
+
operations as JSON without touching the filesystem.
|
|
12
|
+
- Support `gpt-6-astra` for opt-in Responses Lite requests and `pro` reasoning
|
|
13
|
+
mode, without changing either setting's default.
|
|
14
|
+
|
|
7
15
|
- Add configurable Codex `exec_command` + `write_stdin` and `shell_command`
|
|
8
16
|
command surfaces, defaulting to persistent unified exec and replacing only
|
|
9
17
|
an active Pi `bash` tool. Command output follows Pi's 2,000-line/50-KiB tail
|
|
@@ -18,6 +26,11 @@
|
|
|
18
26
|
|
|
19
27
|
### Fixed
|
|
20
28
|
|
|
29
|
+
- Preserve current instructions, dynamic tools, native compaction, and resumed
|
|
30
|
+
history with Pi 0.86's system-message transcript. Refresh tool declarations
|
|
31
|
+
when compaction replaces history and keep forced prompts authoritative.
|
|
32
|
+
- Match Pi 0.86's reasoning Off mapping and persist transport diagnostics as
|
|
33
|
+
strict JSON. Include standalone usage entries in footer totals.
|
|
21
34
|
- Show effective unified-exec yield durations beside accent-colored command
|
|
22
35
|
tool titles, include explicit working directories, emphasize commands on
|
|
23
36
|
their own separated rows, separate `apply_patch` results, and mute command
|
|
@@ -80,7 +93,7 @@
|
|
|
80
93
|
|
|
81
94
|
### Changed
|
|
82
95
|
|
|
83
|
-
-
|
|
96
|
+
- Require Pi `>=0.86.0 <0.87.0` and use Pi 0.86.0 as the development baseline.
|
|
84
97
|
- Describe `shell_command`, `exec_command`, and `write_stdin` in Pi's tool list,
|
|
85
98
|
guidelines, and detailed tool contracts using the actual resolved default
|
|
86
99
|
shell. Document command working-directory and `PI_*` context behavior,
|
|
@@ -106,9 +119,10 @@
|
|
|
106
119
|
stderr while retaining Pi's complete-output files.
|
|
107
120
|
- Replace the multi-line startup configuration dump with a concise package and
|
|
108
121
|
settings-command notice, and simplify the fallback settings-save error.
|
|
109
|
-
-
|
|
110
|
-
additional tools on capable Codex models
|
|
111
|
-
top-level
|
|
122
|
+
- Read dynamic tool declarations from Pi 0.86 system messages, preferring
|
|
123
|
+
message-anchored additional tools on capable Codex models. Use tool-search
|
|
124
|
+
pairs on other capable models and the complete top-level tool set when
|
|
125
|
+
additions are unsupported or tools are removed or redefined.
|
|
112
126
|
- Simplify `apply_patch` for its queued single-writer operating model: retain
|
|
113
127
|
Pi's mutation queue, remove the extension-local alias queue and
|
|
114
128
|
preflight-to-execution drift machinery, use direct writes for in-place text
|
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, reasoning summaries, and
|
|
16
|
+
- **Native request controls**: configures Responses API text verbosity, reasoning summaries, and standard/pro reasoning mode on supported models.
|
|
17
17
|
- **Session-local settings pane**: `/codex-settings` changes every compatibility setting for the current session; `Enter` persists and closes, `Escape` discards unsaved changes and closes, and `Ctrl+S` persists without closing.
|
|
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
|
|
|
@@ -22,7 +22,7 @@ Pi provides the Codex OAuth flow and model catalog. At session start, this packa
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
24
|
- Node.js 22.19 or newer
|
|
25
|
-
- Pi `>=0.
|
|
25
|
+
- Pi `>=0.86.0 <0.87.0`
|
|
26
26
|
- An OpenAI Codex login in Pi
|
|
27
27
|
|
|
28
28
|
Authenticate through Pi if needed:
|
|
@@ -37,21 +37,21 @@ The compatibility baseline is official Codex CLI `0.149.1`, released August 24,
|
|
|
37
37
|
|
|
38
38
|
### Configurable defaults that differ from Codex
|
|
39
39
|
|
|
40
|
-
| Area | This package by default
|
|
41
|
-
| --------------------------------------------- |
|
|
42
|
-
| 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.
|
|
43
|
-
| Image-generation tool | Enabled whenever an `openai-codex` model is selected. Backend capability and account failures surface when the tool executes.
|
|
44
|
-
| Standalone `web.run` | Disabled by default; when enabled, preferred over hosted `web_search` and sent with the complete reserved schema and description.
|
|
45
|
-
| Hosted web search | Disabled by default; when enabled, injected only for ordinary Responses while `web.run` is inactive. Responses Lite omits hosted tools.
|
|
46
|
-
| Coding mutation tools | Enables `apply_patch` and suppresses Pi's active `edit` and `write` tools.
|
|
47
|
-
| Command tools | Replaces an active Pi `bash` tool with `exec_command` and `write_stdin`.
|
|
48
|
-
| `apply_patch` debug output | Disabled; collapsed results show the normal visual summary and instruction rows.
|
|
49
|
-
| `apply_patch` diagnostics capture | Disabled; no separate request or filesystem snapshot artifacts are retained.
|
|
50
|
-
| Codex tool background | Uses a subtle theme-derived surface for extension-owned Codex tools.
|
|
51
|
-
| Auto-compaction trigger | Relies on Pi's reserve-token threshold unless a percentage is configured.
|
|
52
|
-
| Fast mode | Uses the normal tier.
|
|
53
|
-
| Responses Lite | Disabled; supported
|
|
54
|
-
| Text and reasoning request controls | Sends low text verbosity and automatic reasoning summaries; omits the default
|
|
40
|
+
| Area | This package by default | Official Codex | Configuration |
|
|
41
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| 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`. |
|
|
43
|
+
| 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. |
|
|
44
|
+
| 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. |
|
|
45
|
+
| 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`. |
|
|
46
|
+
| 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. |
|
|
47
|
+
| 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`. |
|
|
48
|
+
| `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. |
|
|
49
|
+
| `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. |
|
|
50
|
+
| 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`. |
|
|
51
|
+
| 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. |
|
|
52
|
+
| Fast mode | Uses the normal tier. | Uses the configured Codex service tier. | `fastMode`: boolean; `true` requests the priority tier. |
|
|
53
|
+
| Responses Lite | Disabled; supported models use ordinary Responses. | Enabled according to Codex model metadata. | `responsesLite`: boolean; `true` enables Responses Lite. |
|
|
54
|
+
| Text and reasoning request controls | Sends low text verbosity and automatic reasoning summaries; omits the default 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`. |
|
|
55
55
|
|
|
56
56
|
`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.
|
|
57
57
|
|
|
@@ -62,7 +62,8 @@ The compatibility baseline is official Codex CLI `0.149.1`, released August 24,
|
|
|
62
62
|
| 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. |
|
|
63
63
|
| 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. |
|
|
64
64
|
| Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
|
|
65
|
-
| System instructions | Pi
|
|
65
|
+
| System instructions | Replay Pi's system-message sections into the complete current prompt. Responses Lite models prepend it as developer input after `additional_tools`; other models send it through Responses `instructions`. Do not replay older Pi system-prompt text alongside that current prompt. `/reload`, forced prompts, compaction snapshots, and resumed sessions use the current state without rewriting old checkpoints. |
|
|
66
|
+
| Dynamic tool declarations | Replay additive `toolsAdded` system messages in place as `additional_tools` or tool-search pairs on capable models. Tool removal or redefinition uses the complete current top-level tool set instead. Compaction rebases declarations so tools remain available after their history is replaced. |
|
|
66
67
|
| 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. |
|
|
67
68
|
| 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. |
|
|
68
69
|
| Mid-turn compaction | Provider-boundary percentage compaction preserves a successful `end_turn:false` prefix as its own Pi assistant message, installs a checkpoint, and continues without synthetic model input. 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. |
|
|
@@ -198,7 +199,7 @@ Defaults:
|
|
|
198
199
|
| Setting | Values | Default | Behavior |
|
|
199
200
|
| ----------------------- | ---------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
200
201
|
| `fastMode` | boolean | `false` | Adds `service_tier: "priority"` to requests while retaining the current `openai-codex` provider and model. |
|
|
201
|
-
| `responsesLite` | boolean | `false` | Uses Codex's Responses Lite input envelope on supported
|
|
202
|
+
| `responsesLite` | boolean | `false` | Uses Codex's Responses Lite input envelope on supported models when enabled. By default, those models use ordinary Responses instructions and tools. |
|
|
202
203
|
| `toolBackground` | `subtle`, `status`, `none` | `subtle` | Controls the shared self-rendered background for command tools, `apply_patch`, `image_gen.imagegen`, and `web.run`. `status` uses Pi's pending/success/error backgrounds; `none` keeps the custom layout transparent. |
|
|
203
204
|
| `shellTool` | `unified_exec`, `shell_command` | `unified_exec` | Selects the command surface on `openai-codex` models. The selected Codex command surface replaces Pi `bash` only when `bash` was active. |
|
|
204
205
|
| `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. |
|
|
@@ -211,7 +212,12 @@ Defaults:
|
|
|
211
212
|
| `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. |
|
|
212
213
|
| `textVerbosity` | `low`, `medium`, `high` | `low` | Sets Responses API `text.verbosity`. |
|
|
213
214
|
| `reasoningSummary` | `auto`, `concise`, `detailed`, `off` | `auto` | Sets `reasoning.summary` when reasoning is enabled; `off` omits the summary parameter. |
|
|
214
|
-
| `reasoningMode` | `standard`, `pro` | `standard` | Controls
|
|
215
|
+
| `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"`. |
|
|
216
|
+
|
|
217
|
+
Responses Lite supports exactly `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`,
|
|
218
|
+
and `gpt-6-astra`. Pro reasoning mode supports `gpt-5.6`, `gpt-5.6-*`, and
|
|
219
|
+
exactly `gpt-6-astra`. Both controls remain opt-in; other GPT-6 model IDs are
|
|
220
|
+
not included.
|
|
215
221
|
|
|
216
222
|
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.
|
|
217
223
|
|
|
@@ -306,6 +312,12 @@ permissions.
|
|
|
306
312
|
|
|
307
313
|
## Native compaction
|
|
308
314
|
|
|
315
|
+
See the [Codex compaction approach review](CODEX_COMPACTION_APPROACH_REVIEW.md)
|
|
316
|
+
for a focused `0.153.4` source review, including experimental notes/history
|
|
317
|
+
recovery and unsummarized context resets, implementation options, and trade-offs.
|
|
318
|
+
That proposal does not change this package's runtime or its package-wide
|
|
319
|
+
compatibility baseline.
|
|
320
|
+
|
|
309
321
|
The extension handles native compaction for `openai-codex`. It follows the Codex v2 flow:
|
|
310
322
|
|
|
311
323
|
1. Send normal Responses history followed by `{ "type": "compaction_trigger" }`.
|
|
@@ -313,7 +325,7 @@ The extension handles native compaction for `openai-codex`. It follows the Codex
|
|
|
313
325
|
3. Retain approximately 64,000 tokens of recent user, developer, and system context.
|
|
314
326
|
4. Persist the opaque checkpoint in the Pi session and replay it on later requests.
|
|
315
327
|
|
|
316
|
-
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
|
|
328
|
+
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 models use the ordinary Responses envelope and receive their own ordinary-prefix prewarm.
|
|
317
329
|
|
|
318
330
|
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.
|
|
319
331
|
|
|
@@ -467,6 +479,23 @@ cannot be inspected, it says that the final state was not verified.
|
|
|
467
479
|
The complete feedback and rendering contract is documented in
|
|
468
480
|
[`APPLY_PATCH_INSTRUCTION_FEEDBACK.md`](APPLY_PATCH_INSTRUCTION_FEEDBACK.md).
|
|
469
481
|
|
|
482
|
+
### `pi-apply-patch` command line
|
|
483
|
+
|
|
484
|
+
The package installs a `pi-apply-patch` executable for use outside Pi sessions:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
pi-apply-patch parse patch.txt
|
|
488
|
+
cat patch.txt | pi-apply-patch parse
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`parse` runs the same parser as the tool and prints `{ operations, environmentId? }` as JSON.
|
|
492
|
+
Each operation is `add` with `content`, `delete`, or `update` with `chunks` and an optional
|
|
493
|
+
`moveTo`. It does not read or write any other file. Exit status is `0` when parsed, `1` for an
|
|
494
|
+
invalid patch, and `2` for a usage or read error.
|
|
495
|
+
|
|
496
|
+
The command line does not apply patches. Applying through a standalone process would bypass Pi's
|
|
497
|
+
file mutation queue and the tool-result contract, so applying stays inside the `apply_patch` tool.
|
|
498
|
+
|
|
470
499
|
## `image_gen.imagegen`
|
|
471
500
|
|
|
472
501
|
The package registers the dotted Pi tool name `image_gen.imagegen` and serializes it as a native Responses API namespace:
|
|
@@ -537,8 +566,7 @@ Both namespace tools are accepted only from the fixed extension-owned allowlist.
|
|
|
537
566
|
mise trust
|
|
538
567
|
mise install
|
|
539
568
|
npm install
|
|
540
|
-
|
|
541
|
-
npm test
|
|
569
|
+
mise run check
|
|
542
570
|
npm run pack:dry
|
|
543
571
|
```
|
|
544
572
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -12,7 +12,7 @@ Source: <https://github.com/openai/codex>
|
|
|
12
12
|
|
|
13
13
|
## Pi AI
|
|
14
14
|
|
|
15
|
-
Selected OpenAI Codex transport, Responses stream processing, and history serialization methods are adapted from `@earendil-works/pi-ai`.
|
|
15
|
+
Selected OpenAI Codex transport, Responses stream processing, and history serialization methods are adapted from `@earendil-works/pi-ai`. History serialization follows the Pi 0.86.0 transcript contract.
|
|
16
16
|
|
|
17
17
|
Pi AI, Copyright (c) 2025 Mario Zechner
|
|
18
18
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { text } from "node:stream/consumers";
|
|
3
|
+
import { runApplyPatchCli } from "../extensions/openai-codex-compat/apply-patch-cli.ts";
|
|
4
|
+
|
|
5
|
+
process.exitCode = await runApplyPatchCli(process.argv.slice(2), {
|
|
6
|
+
readStdin: () => text(process.stdin),
|
|
7
|
+
writeStdout: (content) => {
|
|
8
|
+
process.stdout.write(content);
|
|
9
|
+
},
|
|
10
|
+
writeStderr: (content) => {
|
|
11
|
+
process.stderr.write(content);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import type { ParsedPatch } from "./apply-patch-engine.ts";
|
|
3
|
+
import { ApplyPatchParseError, parsePatchDocument } from "./apply-patch-engine.ts";
|
|
4
|
+
import { errorMessage } from "./apply-patch-engine/apply-patch-engine-errors.ts";
|
|
5
|
+
|
|
6
|
+
export const APPLY_PATCH_CLI_NAME = "pi-apply-patch";
|
|
7
|
+
|
|
8
|
+
export const APPLY_PATCH_CLI_USAGE = `Usage: ${APPLY_PATCH_CLI_NAME} parse [<file>]
|
|
9
|
+
|
|
10
|
+
Parse a Codex apply_patch document and print its operations as JSON.
|
|
11
|
+
Reads <file>, or standard input when <file> is omitted or "-".
|
|
12
|
+
|
|
13
|
+
Exit status: 0 parsed, 1 invalid patch, 2 usage error.
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export type ApplyPatchCliIo = {
|
|
17
|
+
readStdin: () => Promise<string>;
|
|
18
|
+
writeStdout: (text: string) => void;
|
|
19
|
+
writeStderr: (text: string) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ApplyPatchCliParseOutput = Pick<ParsedPatch, "operations" | "environmentId">;
|
|
23
|
+
|
|
24
|
+
export function applyPatchCliParseOutput(parsed: ParsedPatch): ApplyPatchCliParseOutput {
|
|
25
|
+
return parsed.environmentId === undefined
|
|
26
|
+
? { operations: parsed.operations }
|
|
27
|
+
: { environmentId: parsed.environmentId, operations: parsed.operations };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runApplyPatchCli(
|
|
31
|
+
argv: readonly string[],
|
|
32
|
+
io: ApplyPatchCliIo,
|
|
33
|
+
): Promise<number> {
|
|
34
|
+
const [command, ...rest] = argv;
|
|
35
|
+
if (command === undefined || command === "--help" || command === "-h") {
|
|
36
|
+
io.writeStdout(APPLY_PATCH_CLI_USAGE);
|
|
37
|
+
return command === undefined ? 2 : 0;
|
|
38
|
+
}
|
|
39
|
+
if (command !== "parse" || rest.length > 1) {
|
|
40
|
+
io.writeStderr(APPLY_PATCH_CLI_USAGE);
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const source = rest[0];
|
|
45
|
+
let patch: string;
|
|
46
|
+
try {
|
|
47
|
+
patch =
|
|
48
|
+
source === undefined || source === "-"
|
|
49
|
+
? await io.readStdin()
|
|
50
|
+
: await readFile(source, "utf8");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
io.writeStderr(`${APPLY_PATCH_CLI_NAME}: cannot read patch: ${errorMessage(error)}\n`);
|
|
53
|
+
return 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const output = applyPatchCliParseOutput(parsePatchDocument(patch));
|
|
58
|
+
io.writeStdout(`${JSON.stringify(output, null, 2)}\n`);
|
|
59
|
+
return 0;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof ApplyPatchParseError)) throw error;
|
|
62
|
+
io.writeStderr(`${APPLY_PATCH_CLI_NAME}: ${error.message}\n`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -2,6 +2,8 @@ import { isBoolean, isNonNullObject, isNumber, isString } from "./value-contract
|
|
|
2
2
|
import {
|
|
3
3
|
calculateCost,
|
|
4
4
|
type Api,
|
|
5
|
+
type JsonObject as PiJsonObject,
|
|
6
|
+
type JsonValue as PiJsonValue,
|
|
5
7
|
type Model,
|
|
6
8
|
type ProviderHeaders,
|
|
7
9
|
type Usage,
|
|
@@ -69,7 +71,6 @@ export interface JsonRecord {
|
|
|
69
71
|
name?: JsonValue;
|
|
70
72
|
arguments?: JsonValue;
|
|
71
73
|
toolCallId?: JsonValue;
|
|
72
|
-
addedToolNames?: JsonValue;
|
|
73
74
|
kind?: JsonValue;
|
|
74
75
|
version?: JsonValue;
|
|
75
76
|
modelId?: JsonValue;
|
|
@@ -105,6 +106,20 @@ export function requireJsonRecord(value: unknown, label = "value"): JsonRecord {
|
|
|
105
106
|
return value;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/** Omit optional wire fields before persisting diagnostics in Pi's strict JSON data. */
|
|
110
|
+
export function toPiJsonObject(value: JsonRecord): PiJsonObject {
|
|
111
|
+
const result: PiJsonObject = {};
|
|
112
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
113
|
+
if (entry !== undefined) result[key] = toPiJsonValue(entry);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function toPiJsonValue(value: JsonValue): PiJsonValue {
|
|
119
|
+
if (Array.isArray(value)) return value.map(toPiJsonValue);
|
|
120
|
+
return isObject(value) ? toPiJsonObject(value) : value;
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
export function optionalJsonRecord(value: unknown, label = "value"): JsonRecord | undefined {
|
|
109
124
|
return value === undefined ? undefined : requireJsonRecord(value, label);
|
|
110
125
|
}
|
|
@@ -1,43 +1,11 @@
|
|
|
1
1
|
import type { SessionEntry, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { Api,
|
|
2
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
3
3
|
import { providerHistory, type GrammarToolInputProperties } from "../compaction-checkpoint.ts";
|
|
4
4
|
import type { ImageDetail } from "../config.ts";
|
|
5
5
|
import { normalizeReplayItem, stableResponsesJson } from "../responses-replay.ts";
|
|
6
6
|
import type { ResponsesInputItem, ResponsesOutputItem } from "../responses-item-schema.ts";
|
|
7
7
|
import type { ResponsesItem as SerializedResponsesItem } from "../vendor/pi-ai/openai-responses-serialization.ts";
|
|
8
8
|
|
|
9
|
-
export interface DeferredToolGroups {
|
|
10
|
-
immediate: Tool[];
|
|
11
|
-
deferred: Map<string, Tool>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function splitDeferredTools(context: Context, enabled: boolean): DeferredToolGroups {
|
|
15
|
-
const unique = new Map((context.tools ?? []).map((tool) => [tool.name, tool]));
|
|
16
|
-
if (!enabled) return { immediate: [...unique.values()], deferred: new Map() };
|
|
17
|
-
|
|
18
|
-
const deferredNames = new Set<string>();
|
|
19
|
-
const usedNames = new Set<string>();
|
|
20
|
-
for (const message of context.messages) {
|
|
21
|
-
if (message.role === "assistant") {
|
|
22
|
-
for (const block of message.content) {
|
|
23
|
-
if (block.type === "toolCall") usedNames.add(block.name);
|
|
24
|
-
}
|
|
25
|
-
} else if (message.role === "toolResult") {
|
|
26
|
-
for (const name of message.addedToolNames ?? []) {
|
|
27
|
-
if (!usedNames.has(name)) deferredNames.add(name);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const immediate: Tool[] = [];
|
|
33
|
-
const deferred = new Map<string, Tool>();
|
|
34
|
-
for (const [name, tool] of unique) {
|
|
35
|
-
if (deferredNames.has(name)) deferred.set(name, tool);
|
|
36
|
-
else immediate.push(tool);
|
|
37
|
-
}
|
|
38
|
-
return { immediate, deferred };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
9
|
export function nativeOverrideRequired(
|
|
42
10
|
rawItems: readonly ResponsesOutputItem[],
|
|
43
11
|
canonicalItems: readonly SerializedResponsesItem[],
|
|
@@ -91,6 +59,7 @@ export function splitUnsampledUserInput(options: {
|
|
|
91
59
|
allTools: options.allTools,
|
|
92
60
|
grammarToolInputProperties: options.grammarToolInputProperties,
|
|
93
61
|
imageDetail: options.imageDetail,
|
|
62
|
+
anchorsToolAdditions: false,
|
|
94
63
|
});
|
|
95
64
|
if (encoded.length === 0 || encoded.length > options.history.length) return { kind: "unsafe" };
|
|
96
65
|
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
AssistantMessage,
|
|
5
5
|
AssistantMessageDiagnostic,
|
|
6
6
|
Context,
|
|
7
|
+
JsonObject,
|
|
7
8
|
Model,
|
|
8
9
|
OpenAICodexResponsesOptions,
|
|
9
10
|
Usage,
|
|
@@ -57,7 +58,7 @@ export function assessAttemptToolCalls(
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export interface OutputItemTypeCounts {
|
|
60
|
-
[type: string]: number
|
|
61
|
+
[type: string]: number;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export function outputItemTypeCounts(items: readonly ResponsesOutputItem[]): OutputItemTypeCounts {
|
|
@@ -91,7 +92,7 @@ export function responseDecisionDiagnostic(options: {
|
|
|
91
92
|
options.capture.streamedToolCallIndexes.size > 0;
|
|
92
93
|
if (!nontrivial) return undefined;
|
|
93
94
|
|
|
94
|
-
const details:
|
|
95
|
+
const details: JsonObject = {
|
|
95
96
|
attempt: options.attempt,
|
|
96
97
|
terminalType: options.failureReason ?? options.terminalState?.type ?? "missing",
|
|
97
98
|
outputItemTypes: outputItemTypeCounts(options.attemptItems),
|
|
@@ -5,6 +5,10 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import {
|
|
6
6
|
clampThinkingLevel,
|
|
7
7
|
createAssistantMessageEventStream,
|
|
8
|
+
getCurrentSystemPrompt,
|
|
9
|
+
getDeclaredTools,
|
|
10
|
+
normalizeContext,
|
|
11
|
+
resolveTranscriptTools,
|
|
8
12
|
type Api,
|
|
9
13
|
type AssistantMessage,
|
|
10
14
|
type AssistantMessageEventStream,
|
|
@@ -13,6 +17,7 @@ import {
|
|
|
13
17
|
type OpenAICodexResponsesOptions,
|
|
14
18
|
type Provider,
|
|
15
19
|
type SimpleStreamOptions,
|
|
20
|
+
type TranscriptContext,
|
|
16
21
|
type Usage,
|
|
17
22
|
uuidv7,
|
|
18
23
|
} from "@earendil-works/pi-ai";
|
|
@@ -26,7 +31,6 @@ import {
|
|
|
26
31
|
providerHistory,
|
|
27
32
|
remoteCompactionMarkerSummary,
|
|
28
33
|
responsesCompatibility,
|
|
29
|
-
responsesDeferredToolsMode,
|
|
30
34
|
searchCheckpoint,
|
|
31
35
|
type CheckpointData,
|
|
32
36
|
type CompactionDecision,
|
|
@@ -38,6 +42,7 @@ import {
|
|
|
38
42
|
isObject,
|
|
39
43
|
remoteCompactionPayload,
|
|
40
44
|
requireResponsesInputItems,
|
|
45
|
+
toPiJsonObject,
|
|
41
46
|
withoutConversationInput,
|
|
42
47
|
type JsonRecord,
|
|
43
48
|
type JsonValue,
|
|
@@ -115,7 +120,6 @@ function codexStreamOptions(value: unknown): OpenAICodexResponsesOptions | undef
|
|
|
115
120
|
}
|
|
116
121
|
import {
|
|
117
122
|
nativeOverrideRequired,
|
|
118
|
-
splitDeferredTools,
|
|
119
123
|
splitUnsampledUserInput,
|
|
120
124
|
userEntryAfterLastSampled,
|
|
121
125
|
} from "./codex-provider-history.ts";
|
|
@@ -156,7 +160,7 @@ type CodexCompactionResult = { checkpoint: CheckpointData; usage?: Usage };
|
|
|
156
160
|
|
|
157
161
|
type BuildRequestBodyOptions = {
|
|
158
162
|
model: Model<Api>;
|
|
159
|
-
context:
|
|
163
|
+
context: TranscriptContext;
|
|
160
164
|
requestOptions: OpenAICodexResponsesOptions;
|
|
161
165
|
runtimeSessionId: string | undefined;
|
|
162
166
|
cacheSessionId: string | undefined;
|
|
@@ -488,19 +492,22 @@ export class CodexProviderRuntime {
|
|
|
488
492
|
|
|
489
493
|
private wireHistory(
|
|
490
494
|
model: Model<Api>,
|
|
491
|
-
context:
|
|
495
|
+
context: TranscriptContext,
|
|
492
496
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
493
497
|
sessionId: string | undefined,
|
|
498
|
+
anchorsToolAdditions: boolean,
|
|
494
499
|
): ResponsesInputItem[] {
|
|
495
500
|
const scope = sessionId ? this.scopes.get(sessionId) : undefined;
|
|
501
|
+
const compat = responsesCompatibility(model.compat);
|
|
496
502
|
if (!scope) {
|
|
497
|
-
const compat = responsesCompatibility(model.compat);
|
|
498
|
-
const deferredToolsMode = responsesDeferredToolsMode(compat);
|
|
499
503
|
const nativeItems = new Map<string, ResponsesOutputItem[]>();
|
|
500
504
|
const serializationOptions: NonNullable<Parameters<typeof convertResponsesMessages>[3]> = {
|
|
501
505
|
includeSystemPrompt: false,
|
|
506
|
+
includeSystemUpdates: false,
|
|
507
|
+
supportsMidConvoSystemMessages: true,
|
|
508
|
+
supportsAdditionalTools: anchorsToolAdditions && (compat.supportsAdditionalTools ?? false),
|
|
509
|
+
supportsToolSearch: anchorsToolAdditions && (compat.supportsToolSearch ?? false),
|
|
502
510
|
grammarToolInputProperties,
|
|
503
|
-
deferredTools: splitDeferredTools(context, deferredToolsMode !== undefined).deferred,
|
|
504
511
|
toolOptions: {
|
|
505
512
|
strict: false,
|
|
506
513
|
supportsStrictMode: compat.supportsStrictMode ?? true,
|
|
@@ -511,7 +518,6 @@ export class CodexProviderRuntime {
|
|
|
511
518
|
toolResultImageDetail: "auto",
|
|
512
519
|
nativeAssistantItems: nativeItems,
|
|
513
520
|
};
|
|
514
|
-
if (deferredToolsMode) serializationOptions.deferredToolsMode = deferredToolsMode;
|
|
515
521
|
return requireResponsesInputItems(
|
|
516
522
|
convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, serializationOptions),
|
|
517
523
|
);
|
|
@@ -522,6 +528,7 @@ export class CodexProviderRuntime {
|
|
|
522
528
|
allTools: this.pi.getAllTools(),
|
|
523
529
|
grammarToolInputProperties,
|
|
524
530
|
imageDetail: scope.config.imageDetail,
|
|
531
|
+
anchorsToolAdditions,
|
|
525
532
|
});
|
|
526
533
|
}
|
|
527
534
|
|
|
@@ -536,14 +543,39 @@ export class CodexProviderRuntime {
|
|
|
536
543
|
turnId,
|
|
537
544
|
} = options;
|
|
538
545
|
const compat = responsesCompatibility(model.compat);
|
|
539
|
-
const
|
|
540
|
-
const
|
|
546
|
+
const scope = runtimeSessionId ? this.scopes.get(runtimeSessionId) : undefined;
|
|
547
|
+
const threshold = scope?.config.autoCompactAtPercent;
|
|
548
|
+
// Percentage compaction drops historical tool additions. Rebase declarations
|
|
549
|
+
// before serialization so both the compaction and its continuation stay valid.
|
|
550
|
+
const compactionPending =
|
|
551
|
+
threshold !== undefined &&
|
|
552
|
+
scope?.contextPercent !== null &&
|
|
553
|
+
scope?.contextPercent !== undefined &&
|
|
554
|
+
scope.contextPercent >= threshold;
|
|
555
|
+
const toolPlacement = resolveTranscriptTools(
|
|
556
|
+
context.messages,
|
|
557
|
+
!compactionPending &&
|
|
558
|
+
((compat.supportsAdditionalTools ?? false) || (compat.supportsToolSearch ?? false)),
|
|
559
|
+
);
|
|
560
|
+
// A forced prompt can project all tools into a single leading system message.
|
|
561
|
+
// Do not replay additions from the stored branch after that projection.
|
|
562
|
+
const anchorsToolAdditions =
|
|
563
|
+
toolPlacement.anchorsAdditions &&
|
|
564
|
+
context.messages
|
|
565
|
+
.slice(1)
|
|
566
|
+
.some((message) => message.role === "system" && (message.toolsAdded?.length ?? 0) > 0);
|
|
541
567
|
let body: JsonRecord = {
|
|
542
568
|
model: model.id,
|
|
543
569
|
store: false,
|
|
544
570
|
stream: true,
|
|
545
|
-
instructions: context.
|
|
546
|
-
input: this.wireHistory(
|
|
571
|
+
instructions: getCurrentSystemPrompt(context.messages) || "You are a helpful assistant.",
|
|
572
|
+
input: this.wireHistory(
|
|
573
|
+
model,
|
|
574
|
+
context,
|
|
575
|
+
grammarToolInputProperties,
|
|
576
|
+
runtimeSessionId,
|
|
577
|
+
anchorsToolAdditions,
|
|
578
|
+
),
|
|
547
579
|
text: { verbosity: requestOptions.textVerbosity ?? "low" },
|
|
548
580
|
include: ["reasoning.encrypted_content"],
|
|
549
581
|
tool_choice: requestOptions.toolChoice ?? "auto",
|
|
@@ -562,8 +594,8 @@ export class CodexProviderRuntime {
|
|
|
562
594
|
),
|
|
563
595
|
});
|
|
564
596
|
if (requestOptions.serviceTier !== undefined) body.service_tier = requestOptions.serviceTier;
|
|
565
|
-
if (toolPlacement.
|
|
566
|
-
body.tools = convertResponsesTools(toolPlacement.
|
|
597
|
+
if (toolPlacement.requestTools.length > 0) {
|
|
598
|
+
body.tools = convertResponsesTools(toolPlacement.requestTools, {
|
|
567
599
|
strict: false,
|
|
568
600
|
supportsStrictMode: compat.supportsStrictMode ?? true,
|
|
569
601
|
supportsOpenAIGrammarTools: compat.supportsOpenAIGrammarTools ?? false,
|
|
@@ -573,7 +605,9 @@ export class CodexProviderRuntime {
|
|
|
573
605
|
if (requestOptions.reasoningEffort !== undefined) {
|
|
574
606
|
const mapped =
|
|
575
607
|
requestOptions.reasoningEffort === "none"
|
|
576
|
-
?
|
|
608
|
+
? model.thinkingLevelMap?.off === undefined
|
|
609
|
+
? "none"
|
|
610
|
+
: model.thinkingLevelMap.off
|
|
577
611
|
: (model.thinkingLevelMap?.[requestOptions.reasoningEffort] ??
|
|
578
612
|
requestOptions.reasoningEffort);
|
|
579
613
|
if (mapped !== null) {
|
|
@@ -582,6 +616,8 @@ export class CodexProviderRuntime {
|
|
|
582
616
|
summary: requestOptions.reasoningSummary ?? "auto",
|
|
583
617
|
};
|
|
584
618
|
}
|
|
619
|
+
} else if (model.reasoning && model.thinkingLevelMap?.off !== null) {
|
|
620
|
+
body["reasoning"] = { effort: model.thinkingLevelMap?.off ?? "none" };
|
|
585
621
|
}
|
|
586
622
|
return body;
|
|
587
623
|
}
|
|
@@ -682,7 +718,7 @@ export class CodexProviderRuntime {
|
|
|
682
718
|
|
|
683
719
|
private async maybeCompactPercentage(
|
|
684
720
|
model: Model<Api>,
|
|
685
|
-
context:
|
|
721
|
+
context: TranscriptContext,
|
|
686
722
|
options: OpenAICodexResponsesOptions,
|
|
687
723
|
body: JsonRecord,
|
|
688
724
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
@@ -742,7 +778,7 @@ export class CodexProviderRuntime {
|
|
|
742
778
|
template: withoutConversationInput(body),
|
|
743
779
|
instructions: isString(body.instructions)
|
|
744
780
|
? body.instructions
|
|
745
|
-
: context.
|
|
781
|
+
: getCurrentSystemPrompt(context.messages) || "You are a helpful assistant.",
|
|
746
782
|
grammarToolInputProperties,
|
|
747
783
|
priority: scope.config.fastMode,
|
|
748
784
|
compactionMetadata: responsesCompactionV2Metadata("auto", "context_limit", "pre_turn"),
|
|
@@ -781,9 +817,10 @@ export class CodexProviderRuntime {
|
|
|
781
817
|
|
|
782
818
|
stream(
|
|
783
819
|
model: Model<Api>,
|
|
784
|
-
|
|
820
|
+
input: Context,
|
|
785
821
|
options?: OpenAICodexResponsesOptions,
|
|
786
822
|
): AssistantMessageEventStream {
|
|
823
|
+
const context = normalizeContext(input);
|
|
787
824
|
const stream = createAssistantMessageEventStream();
|
|
788
825
|
const requestOptions = transportOptions(options);
|
|
789
826
|
const output: AssistantMessage = {
|
|
@@ -828,7 +865,7 @@ export class CodexProviderRuntime {
|
|
|
828
865
|
);
|
|
829
866
|
}
|
|
830
867
|
const grammarToolInputProperties = createGrammarToolInputProperties(
|
|
831
|
-
context.
|
|
868
|
+
getDeclaredTools(context.messages),
|
|
832
869
|
responsesCompatibility(model.compat).supportsOpenAIGrammarTools ?? false,
|
|
833
870
|
);
|
|
834
871
|
let body = this.buildRequestBody({
|
|
@@ -903,7 +940,12 @@ export class CodexProviderRuntime {
|
|
|
903
940
|
turnState: agentTurn.turnState,
|
|
904
941
|
cacheDiagnostics,
|
|
905
942
|
});
|
|
906
|
-
if (prewarmDiagnostics.length > 0)
|
|
943
|
+
if (prewarmDiagnostics.length > 0) {
|
|
944
|
+
output.diagnostics = prewarmDiagnostics.map((diagnostic) => ({
|
|
945
|
+
...diagnostic,
|
|
946
|
+
details: toPiJsonObject(diagnostic.details),
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
907
949
|
let continuationHandle: CodexContinuationHandle | undefined;
|
|
908
950
|
let webSocketResponseHandle: CodexWebSocketResponseHandle | undefined;
|
|
909
951
|
let startEmitted = false;
|
|
@@ -926,7 +968,10 @@ export class CodexProviderRuntime {
|
|
|
926
968
|
},
|
|
927
969
|
onTransportStart: emitStart,
|
|
928
970
|
onTransportDiagnostic(diagnostic: CodexTransportDiagnostic) {
|
|
929
|
-
output.diagnostics = [
|
|
971
|
+
output.diagnostics = [
|
|
972
|
+
...(output.diagnostics ?? []),
|
|
973
|
+
{ ...diagnostic, details: toPiJsonObject(diagnostic.details) },
|
|
974
|
+
];
|
|
930
975
|
},
|
|
931
976
|
};
|
|
932
977
|
let responseRequests = 0;
|
|
@@ -1153,12 +1198,9 @@ export class CodexProviderRuntime {
|
|
|
1153
1198
|
const canonicalContext: Context = {
|
|
1154
1199
|
messages: [output],
|
|
1155
1200
|
};
|
|
1156
|
-
if (context.tools) canonicalContext.tools = context.tools;
|
|
1157
|
-
const deferredToolsMode = responsesDeferredToolsMode(compat);
|
|
1158
1201
|
const serializationOptions: NonNullable<Parameters<typeof convertResponsesMessages>[3]> = {
|
|
1159
1202
|
includeSystemPrompt: false,
|
|
1160
1203
|
grammarToolInputProperties,
|
|
1161
|
-
deferredTools: splitDeferredTools(context, deferredToolsMode !== undefined).deferred,
|
|
1162
1204
|
toolOptions: {
|
|
1163
1205
|
strict: false,
|
|
1164
1206
|
supportsStrictMode: compat.supportsStrictMode ?? true,
|
|
@@ -1170,7 +1212,6 @@ export class CodexProviderRuntime {
|
|
|
1170
1212
|
(runtimeSessionId ? this.scopes.get(runtimeSessionId)?.config.imageDetail : undefined) ??
|
|
1171
1213
|
"auto",
|
|
1172
1214
|
};
|
|
1173
|
-
if (deferredToolsMode) serializationOptions.deferredToolsMode = deferredToolsMode;
|
|
1174
1215
|
const canonicalItems = convertResponsesMessages(
|
|
1175
1216
|
model,
|
|
1176
1217
|
canonicalContext,
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
} from "@earendil-works/pi-ai";
|
|
6
6
|
import type { CodexCacheDiagnosticContext } from "../codex-cache-diagnostics.ts";
|
|
7
7
|
import type { CodexRequestKind } from "../codex-metadata.ts";
|
|
8
|
-
import type { JsonRecord } from "../codex-protocol.ts";
|
|
8
|
+
import type { JsonRecord, JsonValue } from "../codex-protocol.ts";
|
|
9
9
|
import type { CodexTurnState } from "./codex-transport-turn-state.ts";
|
|
10
10
|
|
|
11
11
|
export type CodexJsonRequestOptions = {
|
|
@@ -43,8 +43,8 @@ export type CodexContinuationHistoryMismatch = {
|
|
|
43
43
|
index: number;
|
|
44
44
|
baselineInputItems: number;
|
|
45
45
|
currentInputItems: number;
|
|
46
|
-
baselineItem?:
|
|
47
|
-
currentItem?:
|
|
46
|
+
baselineItem?: JsonValue;
|
|
47
|
+
currentItem?: JsonValue;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
export type CodexTransportRecoveryAttempt = {
|
package/extensions/openai-codex-compat/codex-transport/codex-transport-websocket-continuation.ts
CHANGED
|
@@ -75,11 +75,11 @@ export function cachedRequestBody(entry: CachedWebSocket, body: JsonRecord): Cac
|
|
|
75
75
|
index: mismatchIndex,
|
|
76
76
|
baselineInputItems: baseline.length,
|
|
77
77
|
currentInputItems: currentInput.length,
|
|
78
|
-
baselineItem: structuredClone(baseline[mismatchIndex]),
|
|
79
78
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const baselineItem = baseline[mismatchIndex];
|
|
80
|
+
const currentItem = currentInput[mismatchIndex];
|
|
81
|
+
if (baselineItem !== undefined) historyMismatch.baselineItem = structuredClone(baselineItem);
|
|
82
|
+
if (currentItem !== undefined) historyMismatch.currentItem = structuredClone(currentItem);
|
|
83
83
|
return {
|
|
84
84
|
body,
|
|
85
85
|
contextMode: "full",
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type SessionEntry,
|
|
8
8
|
type ToolInfo,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import type { Api, Message, Model,
|
|
10
|
+
import type { Api, Message, Model, SystemMessage } from "@earendil-works/pi-ai";
|
|
11
11
|
import { Value } from "typebox/value";
|
|
12
12
|
import { APPLY_PATCH_LARK_GRAMMAR, APPLY_PATCH_TOOL_NAME } from "./apply-patch.ts";
|
|
13
13
|
import { CODEX_TOOL_CALL_PROVIDERS } from "./codex-identifiers.ts";
|
|
@@ -32,10 +32,7 @@ import {
|
|
|
32
32
|
type ResponsesOutputItem,
|
|
33
33
|
} from "./responses-item-schema.ts";
|
|
34
34
|
import type { ResponsesToolDefinition } from "./responses-tool-schema.ts";
|
|
35
|
-
import {
|
|
36
|
-
convertResponsesMessages,
|
|
37
|
-
type DeferredToolsMode,
|
|
38
|
-
} from "./vendor/pi-ai/openai-responses-serialization.ts";
|
|
35
|
+
import { convertResponsesMessages } from "./vendor/pi-ai/openai-responses-serialization.ts";
|
|
39
36
|
|
|
40
37
|
export const CHECKPOINT_ENTRY_TYPE = "openai-codex-compat-remote-compaction";
|
|
41
38
|
export const CHECKPOINT_FORMAT_VERSION = 1;
|
|
@@ -85,14 +82,6 @@ export function responsesCompatibility(value: unknown): ResponsesCompatibility {
|
|
|
85
82
|
return compatibility;
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
export function responsesDeferredToolsMode(
|
|
89
|
-
compatibility: ResponsesCompatibility,
|
|
90
|
-
): DeferredToolsMode | undefined {
|
|
91
|
-
if (compatibility.supportsAdditionalTools) return "additional-tools";
|
|
92
|
-
if (compatibility.supportsToolSearch) return "tool-search";
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
85
|
function responsesToolParameters(tool: ToolInfo): JsonRecord {
|
|
97
86
|
if (!isObject(tool.parameters)) {
|
|
98
87
|
throw new Error(`Tool ${tool.name} must have JSON object parameters.`);
|
|
@@ -164,6 +153,7 @@ type EncodeMessagesOptions = {
|
|
|
164
153
|
allTools: readonly ToolInfo[];
|
|
165
154
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
166
155
|
imageDetail: ImageDetail;
|
|
156
|
+
anchorsToolAdditions?: boolean;
|
|
167
157
|
nativeAssistantItems?: ReadonlyMap<string, readonly ResponsesOutputItem[]>;
|
|
168
158
|
};
|
|
169
159
|
|
|
@@ -174,40 +164,24 @@ export type EncodeSessionEntriesOptions = {
|
|
|
174
164
|
grammarToolInputProperties?: GrammarToolInputProperties;
|
|
175
165
|
imageDetail?: ImageDetail;
|
|
176
166
|
nativeAssistantItems?: ReadonlyMap<string, readonly ResponsesOutputItem[]>;
|
|
167
|
+
initialSystemMessage?: SystemMessage;
|
|
168
|
+
anchorsToolAdditions?: boolean;
|
|
177
169
|
};
|
|
178
170
|
|
|
179
|
-
function asPiTool(tool: ToolInfo, grammarToolInputProperties: GrammarToolInputProperties): Tool {
|
|
180
|
-
const piTool: Tool = {
|
|
181
|
-
name: tool.name,
|
|
182
|
-
description: tool.description,
|
|
183
|
-
parameters: tool.parameters,
|
|
184
|
-
};
|
|
185
|
-
if (tool.name === APPLY_PATCH_TOOL_NAME && grammarToolInputProperties.has(tool.name)) {
|
|
186
|
-
piTool.constrainedSampling = {
|
|
187
|
-
type: "grammar",
|
|
188
|
-
variants: { openai_lark: APPLY_PATCH_LARK_GRAMMAR },
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
return piTool;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
171
|
/** Encode Pi's canonical messages using Pi AI's OpenAI Responses serializer. */
|
|
195
172
|
function encodeMessages(options: EncodeMessagesOptions): ResponsesInputItem[] {
|
|
196
|
-
const {
|
|
197
|
-
|
|
198
|
-
messages,
|
|
199
|
-
allTools,
|
|
200
|
-
grammarToolInputProperties,
|
|
201
|
-
imageDetail,
|
|
202
|
-
nativeAssistantItems,
|
|
203
|
-
} = options;
|
|
204
|
-
const tools = allTools.map((tool) => asPiTool(tool, grammarToolInputProperties));
|
|
173
|
+
const { model, messages, grammarToolInputProperties, imageDetail, nativeAssistantItems } =
|
|
174
|
+
options;
|
|
205
175
|
const compat = responsesCompatibility(model.compat);
|
|
206
|
-
const deferredToolsMode = responsesDeferredToolsMode(compat);
|
|
207
176
|
const serializationOptions: NonNullable<Parameters<typeof convertResponsesMessages>[3]> = {
|
|
208
177
|
includeSystemPrompt: false,
|
|
178
|
+
includeSystemUpdates: false,
|
|
179
|
+
supportsMidConvoSystemMessages: true,
|
|
180
|
+
supportsAdditionalTools:
|
|
181
|
+
options.anchorsToolAdditions !== false && (compat.supportsAdditionalTools ?? false),
|
|
182
|
+
supportsToolSearch:
|
|
183
|
+
options.anchorsToolAdditions !== false && (compat.supportsToolSearch ?? false),
|
|
209
184
|
grammarToolInputProperties,
|
|
210
|
-
deferredTools: new Map(tools.map((tool) => [tool.name, tool])),
|
|
211
185
|
toolOptions: {
|
|
212
186
|
strict: false,
|
|
213
187
|
supportsStrictMode: compat?.supportsStrictMode ?? true,
|
|
@@ -217,17 +191,11 @@ function encodeMessages(options: EncodeMessagesOptions): ResponsesInputItem[] {
|
|
|
217
191
|
textContentItemToolResultNames: CODEX_TEXT_CONTENT_ITEM_TOOL_RESULT_NAMES,
|
|
218
192
|
toolResultImageDetail: imageDetail,
|
|
219
193
|
};
|
|
220
|
-
if (deferredToolsMode) serializationOptions.deferredToolsMode = deferredToolsMode;
|
|
221
194
|
if (nativeAssistantItems) {
|
|
222
195
|
serializationOptions.nativeAssistantItems = nativeAssistantItems;
|
|
223
196
|
}
|
|
224
197
|
return requireResponsesInputItems(
|
|
225
|
-
convertResponsesMessages(
|
|
226
|
-
model,
|
|
227
|
-
{ messages, tools },
|
|
228
|
-
CODEX_TOOL_CALL_PROVIDERS,
|
|
229
|
-
serializationOptions,
|
|
230
|
-
),
|
|
198
|
+
convertResponsesMessages(model, { messages }, CODEX_TOOL_CALL_PROVIDERS, serializationOptions),
|
|
231
199
|
);
|
|
232
200
|
}
|
|
233
201
|
|
|
@@ -243,11 +211,19 @@ export function encodeSessionEntries(options: EncodeSessionEntriesOptions): Resp
|
|
|
243
211
|
const messages = entries.flatMap((entry) => sessionEntryToContextMessages(entry));
|
|
244
212
|
const encodeOptions: EncodeMessagesOptions = {
|
|
245
213
|
model,
|
|
246
|
-
|
|
214
|
+
// A partial tail can start with a system update. Seed its prior state so
|
|
215
|
+
// the serializer does not mistake that update for the leading declaration.
|
|
216
|
+
messages: [
|
|
217
|
+
options.initialSystemMessage ?? { role: "system", content: "", timestamp: 0 },
|
|
218
|
+
...convertToLlm(messages),
|
|
219
|
+
],
|
|
247
220
|
allTools,
|
|
248
221
|
grammarToolInputProperties,
|
|
249
222
|
imageDetail,
|
|
250
223
|
};
|
|
224
|
+
if (options.anchorsToolAdditions !== undefined) {
|
|
225
|
+
encodeOptions.anchorsToolAdditions = options.anchorsToolAdditions;
|
|
226
|
+
}
|
|
251
227
|
if (nativeAssistantItems) encodeOptions.nativeAssistantItems = nativeAssistantItems;
|
|
252
228
|
return encodeMessages(encodeOptions);
|
|
253
229
|
}
|
|
@@ -371,6 +347,7 @@ export function providerHistory(options: {
|
|
|
371
347
|
grammarToolInputProperties?: GrammarToolInputProperties;
|
|
372
348
|
imageDetail?: ImageDetail;
|
|
373
349
|
recoverLatestOverflowPrefix?: boolean;
|
|
350
|
+
anchorsToolAdditions?: boolean;
|
|
374
351
|
}): ResponsesInputItem[] {
|
|
375
352
|
const branch = [...options.branch];
|
|
376
353
|
let recoveredPrefix: ResponsesInputItem[] = [];
|
|
@@ -407,6 +384,9 @@ export function providerHistory(options: {
|
|
|
407
384
|
`The latest Codex compaction checkpoint belongs to ${checkpoint.data.modelId}, not ${options.wireModel.id}.`,
|
|
408
385
|
);
|
|
409
386
|
}
|
|
387
|
+
const checkpointEntry = branch[checkpoint.entryIndex];
|
|
388
|
+
const initialSystemMessage =
|
|
389
|
+
checkpointEntry?.type === "compaction" ? checkpointEntry.systemMessage : undefined;
|
|
410
390
|
return [
|
|
411
391
|
...checkpoint.data.history.map((item) => structuredClone(item)),
|
|
412
392
|
...encodeSessionEntries({
|
|
@@ -416,6 +396,10 @@ export function providerHistory(options: {
|
|
|
416
396
|
grammarToolInputProperties: options.grammarToolInputProperties ?? new Map(),
|
|
417
397
|
imageDetail: options.imageDetail ?? "auto",
|
|
418
398
|
nativeAssistantItems,
|
|
399
|
+
...(initialSystemMessage ? { initialSystemMessage } : {}),
|
|
400
|
+
...(options.anchorsToolAdditions === undefined
|
|
401
|
+
? {}
|
|
402
|
+
: { anchorsToolAdditions: options.anchorsToolAdditions }),
|
|
419
403
|
}),
|
|
420
404
|
...recoveredPrefix,
|
|
421
405
|
];
|
|
@@ -430,6 +414,9 @@ export function providerHistory(options: {
|
|
|
430
414
|
grammarToolInputProperties: options.grammarToolInputProperties ?? new Map(),
|
|
431
415
|
imageDetail: options.imageDetail ?? "auto",
|
|
432
416
|
nativeAssistantItems,
|
|
417
|
+
...(options.anchorsToolAdditions === undefined
|
|
418
|
+
? {}
|
|
419
|
+
: { anchorsToolAdditions: options.anchorsToolAdditions }),
|
|
433
420
|
}),
|
|
434
421
|
...recoveredPrefix,
|
|
435
422
|
];
|
|
@@ -158,7 +158,10 @@ function aggregateFooterEntries(entries: readonly SessionEntry[]): SessionEntry[
|
|
|
158
158
|
entry.message.usage
|
|
159
159
|
) {
|
|
160
160
|
addUsage(precedingUsage, entry.message.usage);
|
|
161
|
-
} else if (
|
|
161
|
+
} else if (
|
|
162
|
+
(entry.type === "usage" || entry.type === "branch_summary" || entry.type === "compaction") &&
|
|
163
|
+
entry.usage
|
|
164
|
+
) {
|
|
162
165
|
addUsage(precedingUsage, entry.usage);
|
|
163
166
|
}
|
|
164
167
|
}
|
|
@@ -249,17 +249,13 @@ export default function registerRemoteCompaction(
|
|
|
249
249
|
grammarToolInputProperties,
|
|
250
250
|
imageDetail: config.imageDetail,
|
|
251
251
|
recoverLatestOverflowPrefix: event.reason === "overflow" && event.willRetry,
|
|
252
|
+
anchorsToolAdditions: false,
|
|
252
253
|
});
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
pi.getActiveTools(),
|
|
259
|
-
grammarToolInputProperties,
|
|
260
|
-
);
|
|
261
|
-
if (tools) template.tools = tools;
|
|
262
|
-
}
|
|
254
|
+
// A cached turn template can contain only the initial declarations.
|
|
255
|
+
// Compaction rebases history, so use the complete current tool set.
|
|
256
|
+
const template: JsonRecord = { ...matching?.payload };
|
|
257
|
+
template.tools =
|
|
258
|
+
activeResponsesTools(allTools, pi.getActiveTools(), grammarToolInputProperties) ?? [];
|
|
263
259
|
const requestOptions: OpenAICodexResponsesOptions = {
|
|
264
260
|
...matching?.requestOptions,
|
|
265
261
|
apiKey: authentication.apiKey,
|
|
@@ -20,7 +20,7 @@ export function isCodexModel(model: Model<Api> | undefined): model is Model<type
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function supportsReasoningMode(modelId: string): boolean {
|
|
23
|
-
return /^gpt-5\.6(?:-|$)/.test(modelId);
|
|
23
|
+
return modelId === "gpt-6-astra" || /^gpt-5\.6(?:-|$)/.test(modelId);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function isWebSearchTool(value: unknown): boolean {
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This directory intentionally contains only the Pi AI methods needed to serialize checkpoint and native replay history for OpenAI's Responses API. It does not contain Pi AI's complete source dependency graph.
|
|
4
4
|
|
|
5
|
-
[`openai-responses-serialization.ts`](openai-responses-serialization.ts) adapts the relevant methods from `@earendil-works/pi-ai@0.
|
|
6
|
-
|
|
7
|
-
Pi AI's
|
|
8
|
-
|
|
9
|
-
`
|
|
10
|
-
|
|
5
|
+
[`openai-responses-serialization.ts`](openai-responses-serialization.ts) adapts the relevant methods from `@earendil-works/pi-ai@0.86.0`. Its header lists the upstream source files. Keep the wire-equivalence tests in [`test/pi-ai-serialization.test.ts`](../../../../test/pi-ai-serialization.test.ts) passing when updating the Pi dependencies.
|
|
6
|
+
|
|
7
|
+
Pi AI's transcript helpers replay system sections and tool declarations.
|
|
8
|
+
`supportsAdditionalTools` and `supportsToolSearch` select the dynamic tool
|
|
9
|
+
history representation for additive `toolsAdded` system messages:
|
|
10
|
+
message-anchored `additional_tools` or a `tool_search_call`/`tool_search_output`
|
|
11
|
+
pair. Tool removal, redefinition, or a model without either capability requires
|
|
12
|
+
the complete current top-level tool list.
|
|
13
|
+
|
|
14
|
+
Compat uses the extension-owned `includeSystemUpdates: false` option alongside
|
|
15
|
+
`includeSystemPrompt: false`. It sends the complete current prompt separately
|
|
16
|
+
and preserves only supported tool additions in system-message positions.
|
|
17
|
+
This keeps old prompt text from overriding current instructions after
|
|
18
|
+
compaction, forced-prompt projection, reload, or resume.
|
|
11
19
|
|
|
12
20
|
The optional `namespacedToolNames`, `textContentItemToolResultNames`, and `toolResultImageDetail` paths are extension-owned additions. Without those options, serialization must continue to match Pi AI. `namespacedToolNames` groups only the fixed Codex allowlist into native Responses namespaces and replays namespace/member call identities. `textContentItemToolResultNames` preserves Codex tools such as `web.run` whose successful text output is transported as an `input_text` content-item array instead of Pi's usual plain string. `toolResultImageDetail` overrides the otherwise canonical `auto` detail used for image tool-result content.
|
|
13
21
|
|
|
@@ -19,10 +19,17 @@ import type {
|
|
|
19
19
|
ToolCall,
|
|
20
20
|
ToolResultMessage,
|
|
21
21
|
} from "@earendil-works/pi-ai";
|
|
22
|
+
import {
|
|
23
|
+
getSystemMessageText,
|
|
24
|
+
normalizeContext,
|
|
25
|
+
renderSystemMessageUpdate,
|
|
26
|
+
resolveTranscript,
|
|
27
|
+
resolveTranscriptTools,
|
|
28
|
+
} from "@earendil-works/pi-ai";
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* Focused copies of the methods used to serialize Pi messages for OpenAI's
|
|
25
|
-
* Responses API. Adapted from @earendil-works/pi-ai@0.
|
|
32
|
+
* Responses API. Adapted from @earendil-works/pi-ai@0.86.0:
|
|
26
33
|
*
|
|
27
34
|
* - src/api/openai-responses-shared.ts
|
|
28
35
|
* - src/api/transform-messages.ts
|
|
@@ -36,7 +43,6 @@ import type {
|
|
|
36
43
|
*/
|
|
37
44
|
|
|
38
45
|
export type ResponsesItem = JsonRecord;
|
|
39
|
-
export type DeferredToolsMode = "additional-tools" | "tool-search";
|
|
40
46
|
export type ToolResultImageDetail = "auto" | "low" | "high" | "original";
|
|
41
47
|
type ToolResultOutput =
|
|
42
48
|
| string
|
|
@@ -47,9 +53,12 @@ type ToolResultOutput =
|
|
|
47
53
|
|
|
48
54
|
type ConvertResponsesMessagesOptions = {
|
|
49
55
|
includeSystemPrompt?: boolean;
|
|
56
|
+
/** Compat sends the complete current prompt outside replay history. */
|
|
57
|
+
includeSystemUpdates?: boolean;
|
|
50
58
|
grammarToolInputProperties?: ReadonlyMap<string, string>;
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
supportsMidConvoSystemMessages?: boolean;
|
|
60
|
+
supportsAdditionalTools?: boolean;
|
|
61
|
+
supportsToolSearch?: boolean;
|
|
53
62
|
toolOptions?: ConvertResponsesToolsOptions;
|
|
54
63
|
nativeAssistantItems?: ReadonlyMap<string, readonly ResponsesItem[]>;
|
|
55
64
|
namespacedToolNames?: ReadonlySet<string>;
|
|
@@ -333,7 +342,7 @@ function transformMessages(
|
|
|
333
342
|
const imageAwareMessages = downgradeUnsupportedImages(normalizedMessages, model);
|
|
334
343
|
|
|
335
344
|
const transformed = imageAwareMessages.map((message) => {
|
|
336
|
-
if (message.role === "user") return message;
|
|
345
|
+
if (message.role === "user" || message.role === "system") return message;
|
|
337
346
|
if (message.role === "toolResult") {
|
|
338
347
|
const normalizedId = toolCallIdMap.get(message.toolCallId);
|
|
339
348
|
return normalizedId && normalizedId !== message.toolCallId
|
|
@@ -484,8 +493,11 @@ export function convertResponsesMessages(
|
|
|
484
493
|
allowedToolCallProviders: ReadonlySet<string>,
|
|
485
494
|
options?: ConvertResponsesMessagesOptions,
|
|
486
495
|
): ResponsesItem[] {
|
|
496
|
+
const transcript = resolveTranscript(
|
|
497
|
+
normalizeContext(context),
|
|
498
|
+
options?.supportsMidConvoSystemMessages,
|
|
499
|
+
);
|
|
487
500
|
const messages: ResponsesItem[] = [];
|
|
488
|
-
const loadedToolNames = new Set<string>();
|
|
489
501
|
const normalizeIdPart = (part: string): string => {
|
|
490
502
|
const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
491
503
|
const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
|
@@ -517,20 +529,62 @@ export function convertResponsesMessages(
|
|
|
517
529
|
return `${normalizedCallId}|${normalizedItemId}`;
|
|
518
530
|
};
|
|
519
531
|
|
|
520
|
-
const transformedMessages = transformMessages(
|
|
532
|
+
const transformedMessages = transformMessages(transcript.messages, model, normalizeToolCallId);
|
|
533
|
+
const transcriptTools = resolveTranscriptTools(
|
|
534
|
+
transcript.messages,
|
|
535
|
+
(options?.supportsAdditionalTools ?? false) || (options?.supportsToolSearch ?? false),
|
|
536
|
+
);
|
|
521
537
|
const includeSystemPrompt = options?.includeSystemPrompt ?? true;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
messages.push({
|
|
526
|
-
role: model.reasoning && supportsDeveloperRole ? "developer" : "system",
|
|
527
|
-
content: sanitizeSurrogates(context.systemPrompt),
|
|
528
|
-
});
|
|
529
|
-
}
|
|
538
|
+
const supportsDeveloperRole =
|
|
539
|
+
!isObject(model.compat) || model.compat["supportsDeveloperRole"] !== false;
|
|
540
|
+
const instructionRole = model.reasoning && supportsDeveloperRole ? "developer" : "system";
|
|
530
541
|
|
|
531
542
|
let messageIndex = 0;
|
|
543
|
+
let sourceIndex = 0;
|
|
532
544
|
for (const message of transformedMessages) {
|
|
533
|
-
|
|
545
|
+
const isLeadingSystemMessage = sourceIndex++ === 0 && message.role === "system";
|
|
546
|
+
if (message.role === "system") {
|
|
547
|
+
const addedTools =
|
|
548
|
+
!isLeadingSystemMessage && transcriptTools.anchorsAdditions
|
|
549
|
+
? (message.toolsAdded ?? [])
|
|
550
|
+
: [];
|
|
551
|
+
const toolOptions = { ...options?.toolOptions };
|
|
552
|
+
if (options?.namespacedToolNames) {
|
|
553
|
+
toolOptions.namespacedToolNames = options.namespacedToolNames;
|
|
554
|
+
}
|
|
555
|
+
if (addedTools.length > 0 && options?.supportsAdditionalTools) {
|
|
556
|
+
messages.push({
|
|
557
|
+
type: "additional_tools",
|
|
558
|
+
role: "developer",
|
|
559
|
+
tools: convertResponsesTools(addedTools, toolOptions),
|
|
560
|
+
});
|
|
561
|
+
} else if (addedTools.length > 0 && options?.supportsToolSearch) {
|
|
562
|
+
const names = addedTools.map((tool) => tool.name);
|
|
563
|
+
const callId = `pi_tool_load_${shortHash(`system:${messageIndex}:${names.join(",")}`)}`;
|
|
564
|
+
messages.push({
|
|
565
|
+
type: "tool_search_call",
|
|
566
|
+
call_id: callId,
|
|
567
|
+
execution: "client",
|
|
568
|
+
status: "completed",
|
|
569
|
+
arguments: { query: names.join(" "), limit: names.length },
|
|
570
|
+
});
|
|
571
|
+
messages.push({
|
|
572
|
+
type: "tool_search_output",
|
|
573
|
+
call_id: callId,
|
|
574
|
+
execution: "client",
|
|
575
|
+
status: "completed",
|
|
576
|
+
tools: convertResponsesTools(addedTools, { ...toolOptions, deferLoading: true }),
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (isLeadingSystemMessage ? includeSystemPrompt : options?.includeSystemUpdates !== false) {
|
|
580
|
+
const text = isLeadingSystemMessage
|
|
581
|
+
? getSystemMessageText(message)
|
|
582
|
+
: renderSystemMessageUpdate(message);
|
|
583
|
+
if (text.length > 0) {
|
|
584
|
+
messages.push({ role: instructionRole, content: sanitizeSurrogates(text) });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} else if (message.role === "user") {
|
|
534
588
|
if (isString(message.content)) {
|
|
535
589
|
messages.push({
|
|
536
590
|
role: "user",
|
|
@@ -650,55 +704,8 @@ export function convertResponsesMessages(
|
|
|
650
704
|
call_id: callId,
|
|
651
705
|
output,
|
|
652
706
|
});
|
|
653
|
-
|
|
654
|
-
const deferredTools: Tool[] = [];
|
|
655
|
-
for (const name of message.addedToolNames ?? []) {
|
|
656
|
-
const tool = options?.deferredTools?.get(name);
|
|
657
|
-
if (!tool || loadedToolNames.has(name)) continue;
|
|
658
|
-
loadedToolNames.add(name);
|
|
659
|
-
deferredTools.push(tool);
|
|
660
|
-
}
|
|
661
|
-
if (deferredTools.length > 0 && options?.deferredToolsMode === "additional-tools") {
|
|
662
|
-
const additionalToolOptions: ConvertResponsesToolsOptions = {
|
|
663
|
-
...options.toolOptions,
|
|
664
|
-
};
|
|
665
|
-
if (options.namespacedToolNames) {
|
|
666
|
-
additionalToolOptions.namespacedToolNames = options.namespacedToolNames;
|
|
667
|
-
}
|
|
668
|
-
messages.push({
|
|
669
|
-
type: "additional_tools",
|
|
670
|
-
role: "developer",
|
|
671
|
-
tools: convertResponsesTools(deferredTools, additionalToolOptions),
|
|
672
|
-
});
|
|
673
|
-
} else if (deferredTools.length > 0 && options?.deferredToolsMode === "tool-search") {
|
|
674
|
-
const names = deferredTools.map((tool) => tool.name);
|
|
675
|
-
const searchCallId = `pi_tool_load_${shortHash(
|
|
676
|
-
`${message.toolCallId}:${names.join(",")}`,
|
|
677
|
-
)}`;
|
|
678
|
-
messages.push({
|
|
679
|
-
type: "tool_search_call",
|
|
680
|
-
call_id: searchCallId,
|
|
681
|
-
execution: "client",
|
|
682
|
-
status: "completed",
|
|
683
|
-
arguments: { query: names.join(" "), limit: names.length },
|
|
684
|
-
});
|
|
685
|
-
const deferredToolOptions: ConvertResponsesToolsOptions = {
|
|
686
|
-
...options?.toolOptions,
|
|
687
|
-
deferLoading: true,
|
|
688
|
-
};
|
|
689
|
-
if (options?.namespacedToolNames) {
|
|
690
|
-
deferredToolOptions.namespacedToolNames = options.namespacedToolNames;
|
|
691
|
-
}
|
|
692
|
-
messages.push({
|
|
693
|
-
type: "tool_search_output",
|
|
694
|
-
call_id: searchCallId,
|
|
695
|
-
execution: "client",
|
|
696
|
-
status: "completed",
|
|
697
|
-
tools: convertResponsesTools(deferredTools, deferredToolOptions),
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
707
|
}
|
|
701
|
-
messageIndex++;
|
|
708
|
+
if (!isLeadingSystemMessage) messageIndex++;
|
|
702
709
|
}
|
|
703
710
|
return messages;
|
|
704
711
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-openai-codex-compat",
|
|
3
|
-
"version": "0.0.10
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/2h2d-co/pi-openai-codex-compat.git"
|
|
21
21
|
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"pi-apply-patch": "./bin/pi-apply-patch.ts"
|
|
24
|
+
},
|
|
22
25
|
"files": [
|
|
26
|
+
"bin",
|
|
23
27
|
"extensions",
|
|
24
28
|
"APPLY_PATCH_INSTRUCTION_FEEDBACK.md",
|
|
25
29
|
"README.md",
|
|
@@ -47,9 +51,9 @@
|
|
|
47
51
|
"devDependencies": {
|
|
48
52
|
"@2h2d/oxlint-config": "0.1.1",
|
|
49
53
|
"@2h2d/ts-config": "0.0.1",
|
|
50
|
-
"@earendil-works/pi-ai": "0.
|
|
51
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
52
|
-
"@earendil-works/pi-tui": "0.
|
|
54
|
+
"@earendil-works/pi-ai": "0.86.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.86.0",
|
|
56
|
+
"@earendil-works/pi-tui": "0.86.0",
|
|
53
57
|
"@types/node": "22.20.1",
|
|
54
58
|
"oxfmt": "0.64.0",
|
|
55
59
|
"oxlint": "1.79.0",
|
|
@@ -58,9 +62,9 @@
|
|
|
58
62
|
"typescript": "7.0.2"
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
61
|
-
"@earendil-works/pi-ai": ">=0.
|
|
62
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
63
|
-
"@earendil-works/pi-tui": ">=0.
|
|
65
|
+
"@earendil-works/pi-ai": ">=0.86.0 <0.87.0",
|
|
66
|
+
"@earendil-works/pi-coding-agent": ">=0.86.0 <0.87.0",
|
|
67
|
+
"@earendil-works/pi-tui": ">=0.86.0 <0.87.0",
|
|
64
68
|
"typebox": "*"
|
|
65
69
|
},
|
|
66
70
|
"engines": {
|