pi-openai-codex-compat 0.0.12 → 0.0.13
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 +17 -0
- package/README.md +18 -16
- package/extensions/openai-codex-compat/codex-provider/codex-provider-history.ts +0 -1
- package/extensions/openai-codex-compat/codex-provider/codex-provider-runtime.ts +12 -35
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +10 -17
- package/extensions/openai-codex-compat/pi-runtime.ts +1 -1
- package/extensions/openai-codex-compat/remote-compaction.ts +0 -2
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.13 - 2026-09-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Declare the complete current tool set in the top-level `tools` field on
|
|
10
|
+
every request and never inside `input`, as the official Codex client does.
|
|
11
|
+
Tools added mid-session were replayed as inline `additional_tools` items;
|
|
12
|
+
the Codex backend then often treated other tools as unavailable, and after a
|
|
13
|
+
percentage-triggered compaction the inline declarations were gone entirely,
|
|
14
|
+
so the model reported that it had no callable tools. A mid-session tool
|
|
15
|
+
change now costs one prompt-cache miss instead.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Use Pi 0.86.1 as the development and packaged-CLI integration baseline. The
|
|
20
|
+
supported range stays `>=0.86.0 <0.87.0`.
|
|
21
|
+
|
|
5
22
|
## 0.0.12 - 2026-09-20
|
|
6
23
|
|
|
7
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ The compatibility baseline is official Codex CLI `0.149.1`, released August 24,
|
|
|
68
68
|
| 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
69
|
| Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
|
|
70
70
|
| 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. |
|
|
71
|
-
| Dynamic tool declarations |
|
|
71
|
+
| Dynamic tool declarations | Send the complete current tool set, replayed from Pi's `toolsAdded` and `toolsRemoved` system messages, in the top-level `tools` field on every request, as the official Codex client does. Tool declarations never travel inside `input`: inline `additional_tools` items and synthetic tool-search pairs made the Codex backend unreliable about which tools exist. A mid-session tool change costs one prompt-cache miss, and native checkpoints need no declaration rebase. |
|
|
72
72
|
| 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. |
|
|
73
73
|
| 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. |
|
|
74
74
|
| 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. |
|
|
@@ -115,19 +115,19 @@ The package implements the Codex-specific pieces that fit a provider compatibili
|
|
|
115
115
|
|
|
116
116
|
The following official Codex facilities are not exact equivalents in this package:
|
|
117
117
|
|
|
118
|
-
| Official Codex facility | Pi/package behavior
|
|
119
|
-
| ----------------------------------------------------------------------- |
|
|
120
|
-
| `view_image` | Pi `read` already accepts images; no canonical `view_image` alias is registered.
|
|
121
|
-
| `update_plan`, `request_user_input`, permissions, and environment tools | Not implemented by this package.
|
|
122
|
-
| `send_user_message_async` | Not implemented; Pi owns visible assistant output and user-message history, while this package does not own Codex's asynchronous message-injection lifecycle.
|
|
123
|
-
| Context-window and clock tools | Not implemented. Compaction remains host/provider managed rather than model managed.
|
|
124
|
-
| MCP resources and dynamic MCP tools | Pi does not provide this package with Codex's MCP runtime.
|
|
125
|
-
| Plugin/connector installation | Not implemented; package installation remains an explicit Pi/user operation.
|
|
126
|
-
| Searchable `tool_search` catalog |
|
|
127
|
-
| Multi-agent V1/V2 coordination | Out of scope; no Codex agent tree, mailbox, task-path, or fork-depth runtime is implemented.
|
|
128
|
-
| JavaScript Code Mode and yielded cells | Out of scope; no V8 isolate, nested tool namespace, cell storage, or `wait` lifecycle is implemented.
|
|
129
|
-
| Goals, memories, and remote skill-resource tools | Not implemented; Pi's sessions, files, and native skills remain separate systems.
|
|
130
|
-
| Remote/deferred execution environments | Not implemented.
|
|
118
|
+
| Official Codex facility | Pi/package behavior |
|
|
119
|
+
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `view_image` | Pi `read` already accepts images; no canonical `view_image` alias is registered. |
|
|
121
|
+
| `update_plan`, `request_user_input`, permissions, and environment tools | Not implemented by this package. |
|
|
122
|
+
| `send_user_message_async` | Not implemented; Pi owns visible assistant output and user-message history, while this package does not own Codex's asynchronous message-injection lifecycle. |
|
|
123
|
+
| Context-window and clock tools | Not implemented. Compaction remains host/provider managed rather than model managed. |
|
|
124
|
+
| MCP resources and dynamic MCP tools | Pi does not provide this package with Codex's MCP runtime. |
|
|
125
|
+
| Plugin/connector installation | Not implemented; package installation remains an explicit Pi/user operation. |
|
|
126
|
+
| Searchable `tool_search` catalog | This package declares every tool at the top level and does not implement Codex's searchable deferred-tool catalog and ranking runtime. |
|
|
127
|
+
| Multi-agent V1/V2 coordination | Out of scope; no Codex agent tree, mailbox, task-path, or fork-depth runtime is implemented. |
|
|
128
|
+
| JavaScript Code Mode and yielded cells | Out of scope; no V8 isolate, nested tool namespace, cell storage, or `wait` lifecycle is implemented. |
|
|
129
|
+
| Goals, memories, and remote skill-resource tools | Not implemented; Pi's sessions, files, and native skills remain separate systems. |
|
|
130
|
+
| Remote/deferred execution environments | Not implemented. |
|
|
131
131
|
|
|
132
132
|
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
133
|
|
|
@@ -587,7 +587,7 @@ mise run test:live:codex
|
|
|
587
587
|
The task obtains the local Codex bearer token and runs the tests with
|
|
588
588
|
`gpt-5.6-luna` at medium reasoning effort.
|
|
589
589
|
It also packs the extension and loads that archive through the shipped Pi
|
|
590
|
-
0.86.
|
|
590
|
+
0.86.1 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
|
|
591
591
|
prompt reload, native compaction, and persisted-session resume against Codex.
|
|
592
592
|
Test credentials stay in memory and child-process environments. Test sessions
|
|
593
593
|
and configuration are isolated from the user's agent directory.
|
|
@@ -606,7 +606,9 @@ search, and compaction continuation.
|
|
|
606
606
|
2. The command builds the exact package locally and runs live CLI tests against that archive. It also runs the existing live SDK tests against checkout source. Both suites must pass before it records the archive's SHA-256 in an SSH-signed release commit, proves a clean rebuild is reproducible, and creates a lightweight tag. Missing credentials or failing live tests stop the release.
|
|
607
607
|
3. Inspect the result, then push atomically with `git push --atomic origin main vX.Y.Z`.
|
|
608
608
|
4. A read-only GitHub Actions job validates and packs the package. After approval in the tag-restricted `npm-publish` environment, a separate GitHub-owned job verifies the signature and signed digest before attesting and staging that exact archive through npm trusted publishing.
|
|
609
|
-
5.
|
|
609
|
+
5. A final job creates the immutable GitHub release for the tag from the same verified archive, its
|
|
610
|
+
checksum, and the version's `CHANGELOG.md` section (`Unreleased` for prereleases).
|
|
611
|
+
6. Approve the staged package on npmjs.com, or with `npm stage approve <stage-id>`.
|
|
610
612
|
|
|
611
613
|
Stable releases use `latest`; prereleases derive their npm dist-tag from the first prerelease identifier.
|
|
612
614
|
|
|
@@ -59,7 +59,6 @@ export function splitUnsampledUserInput(options: {
|
|
|
59
59
|
allTools: options.allTools,
|
|
60
60
|
grammarToolInputProperties: options.grammarToolInputProperties,
|
|
61
61
|
imageDetail: options.imageDetail,
|
|
62
|
-
anchorsToolAdditions: false,
|
|
63
62
|
});
|
|
64
63
|
if (encoded.length === 0 || encoded.length > options.history.length) return { kind: "unsafe" };
|
|
65
64
|
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
clampThinkingLevel,
|
|
7
7
|
createAssistantMessageEventStream,
|
|
8
8
|
getCurrentSystemPrompt,
|
|
9
|
+
getCurrentTools,
|
|
9
10
|
getDeclaredTools,
|
|
10
11
|
normalizeContext,
|
|
11
|
-
resolveTranscriptTools,
|
|
12
12
|
type Api,
|
|
13
13
|
type AssistantMessage,
|
|
14
14
|
type AssistantMessageEventStream,
|
|
@@ -495,7 +495,6 @@ export class CodexProviderRuntime {
|
|
|
495
495
|
context: TranscriptContext,
|
|
496
496
|
grammarToolInputProperties: GrammarToolInputProperties,
|
|
497
497
|
sessionId: string | undefined,
|
|
498
|
-
anchorsToolAdditions: boolean,
|
|
499
498
|
): ResponsesInputItem[] {
|
|
500
499
|
const scope = sessionId ? this.scopes.get(sessionId) : undefined;
|
|
501
500
|
const compat = responsesCompatibility(model.compat);
|
|
@@ -505,8 +504,8 @@ export class CodexProviderRuntime {
|
|
|
505
504
|
includeSystemPrompt: false,
|
|
506
505
|
includeSystemUpdates: false,
|
|
507
506
|
supportsMidConvoSystemMessages: true,
|
|
508
|
-
supportsAdditionalTools:
|
|
509
|
-
supportsToolSearch:
|
|
507
|
+
supportsAdditionalTools: false,
|
|
508
|
+
supportsToolSearch: false,
|
|
510
509
|
grammarToolInputProperties,
|
|
511
510
|
toolOptions: {
|
|
512
511
|
strict: false,
|
|
@@ -528,7 +527,6 @@ export class CodexProviderRuntime {
|
|
|
528
527
|
allTools: this.pi.getAllTools(),
|
|
529
528
|
grammarToolInputProperties,
|
|
530
529
|
imageDetail: scope.config.imageDetail,
|
|
531
|
-
anchorsToolAdditions,
|
|
532
530
|
});
|
|
533
531
|
}
|
|
534
532
|
|
|
@@ -543,39 +541,18 @@ export class CodexProviderRuntime {
|
|
|
543
541
|
turnId,
|
|
544
542
|
} = options;
|
|
545
543
|
const compat = responsesCompatibility(model.compat);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
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);
|
|
544
|
+
// Every request declares the complete current tool set at the top level,
|
|
545
|
+
// as the official Codex client does. Replaying additions inside the input
|
|
546
|
+
// made the Codex backend unreliable about which tools exist, and a native
|
|
547
|
+
// checkpoint drops such history anyway. A mid-session tool change costs one
|
|
548
|
+
// cache miss.
|
|
549
|
+
const requestTools = getCurrentTools(context.messages);
|
|
567
550
|
let body: JsonRecord = {
|
|
568
551
|
model: model.id,
|
|
569
552
|
store: false,
|
|
570
553
|
stream: true,
|
|
571
554
|
instructions: getCurrentSystemPrompt(context.messages) || "You are a helpful assistant.",
|
|
572
|
-
input: this.wireHistory(
|
|
573
|
-
model,
|
|
574
|
-
context,
|
|
575
|
-
grammarToolInputProperties,
|
|
576
|
-
runtimeSessionId,
|
|
577
|
-
anchorsToolAdditions,
|
|
578
|
-
),
|
|
555
|
+
input: this.wireHistory(model, context, grammarToolInputProperties, runtimeSessionId),
|
|
579
556
|
text: { verbosity: requestOptions.textVerbosity ?? "low" },
|
|
580
557
|
include: ["reasoning.encrypted_content"],
|
|
581
558
|
tool_choice: requestOptions.toolChoice ?? "auto",
|
|
@@ -594,8 +571,8 @@ export class CodexProviderRuntime {
|
|
|
594
571
|
),
|
|
595
572
|
});
|
|
596
573
|
if (requestOptions.serviceTier !== undefined) body.service_tier = requestOptions.serviceTier;
|
|
597
|
-
if (
|
|
598
|
-
body.tools = convertResponsesTools(
|
|
574
|
+
if (requestTools.length > 0) {
|
|
575
|
+
body.tools = convertResponsesTools(requestTools, {
|
|
599
576
|
strict: false,
|
|
600
577
|
supportsStrictMode: compat.supportsStrictMode ?? true,
|
|
601
578
|
supportsOpenAIGrammarTools: compat.supportsOpenAIGrammarTools ?? false,
|
|
@@ -153,7 +153,6 @@ type EncodeMessagesOptions = {
|
|
|
153
153
|
allTools: readonly ToolInfo[];
|
|
154
154
|
grammarToolInputProperties: GrammarToolInputProperties;
|
|
155
155
|
imageDetail: ImageDetail;
|
|
156
|
-
anchorsToolAdditions?: boolean;
|
|
157
156
|
nativeAssistantItems?: ReadonlyMap<string, readonly ResponsesOutputItem[]>;
|
|
158
157
|
};
|
|
159
158
|
|
|
@@ -165,10 +164,16 @@ export type EncodeSessionEntriesOptions = {
|
|
|
165
164
|
imageDetail?: ImageDetail;
|
|
166
165
|
nativeAssistantItems?: ReadonlyMap<string, readonly ResponsesOutputItem[]>;
|
|
167
166
|
initialSystemMessage?: SystemMessage;
|
|
168
|
-
anchorsToolAdditions?: boolean;
|
|
169
167
|
};
|
|
170
168
|
|
|
171
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Encode Pi's canonical messages using Pi AI's OpenAI Responses serializer.
|
|
171
|
+
*
|
|
172
|
+
* Tool declarations never enter the input: every request carries the complete
|
|
173
|
+
* current tool set at the top level, as the official Codex client does. Inline
|
|
174
|
+
* `additional_tools` items and synthetic tool-search pairs made the Codex
|
|
175
|
+
* backend unreliable about which tools exist, so neither is emitted.
|
|
176
|
+
*/
|
|
172
177
|
function encodeMessages(options: EncodeMessagesOptions): ResponsesInputItem[] {
|
|
173
178
|
const { model, messages, grammarToolInputProperties, imageDetail, nativeAssistantItems } =
|
|
174
179
|
options;
|
|
@@ -177,10 +182,8 @@ function encodeMessages(options: EncodeMessagesOptions): ResponsesInputItem[] {
|
|
|
177
182
|
includeSystemPrompt: false,
|
|
178
183
|
includeSystemUpdates: false,
|
|
179
184
|
supportsMidConvoSystemMessages: true,
|
|
180
|
-
supportsAdditionalTools:
|
|
181
|
-
|
|
182
|
-
supportsToolSearch:
|
|
183
|
-
options.anchorsToolAdditions !== false && (compat.supportsToolSearch ?? false),
|
|
185
|
+
supportsAdditionalTools: false,
|
|
186
|
+
supportsToolSearch: false,
|
|
184
187
|
grammarToolInputProperties,
|
|
185
188
|
toolOptions: {
|
|
186
189
|
strict: false,
|
|
@@ -221,9 +224,6 @@ export function encodeSessionEntries(options: EncodeSessionEntriesOptions): Resp
|
|
|
221
224
|
grammarToolInputProperties,
|
|
222
225
|
imageDetail,
|
|
223
226
|
};
|
|
224
|
-
if (options.anchorsToolAdditions !== undefined) {
|
|
225
|
-
encodeOptions.anchorsToolAdditions = options.anchorsToolAdditions;
|
|
226
|
-
}
|
|
227
227
|
if (nativeAssistantItems) encodeOptions.nativeAssistantItems = nativeAssistantItems;
|
|
228
228
|
return encodeMessages(encodeOptions);
|
|
229
229
|
}
|
|
@@ -347,7 +347,6 @@ export function providerHistory(options: {
|
|
|
347
347
|
grammarToolInputProperties?: GrammarToolInputProperties;
|
|
348
348
|
imageDetail?: ImageDetail;
|
|
349
349
|
recoverLatestOverflowPrefix?: boolean;
|
|
350
|
-
anchorsToolAdditions?: boolean;
|
|
351
350
|
}): ResponsesInputItem[] {
|
|
352
351
|
const branch = [...options.branch];
|
|
353
352
|
let recoveredPrefix: ResponsesInputItem[] = [];
|
|
@@ -397,9 +396,6 @@ export function providerHistory(options: {
|
|
|
397
396
|
imageDetail: options.imageDetail ?? "auto",
|
|
398
397
|
nativeAssistantItems,
|
|
399
398
|
...(initialSystemMessage ? { initialSystemMessage } : {}),
|
|
400
|
-
...(options.anchorsToolAdditions === undefined
|
|
401
|
-
? {}
|
|
402
|
-
: { anchorsToolAdditions: options.anchorsToolAdditions }),
|
|
403
399
|
}),
|
|
404
400
|
...recoveredPrefix,
|
|
405
401
|
];
|
|
@@ -414,9 +410,6 @@ export function providerHistory(options: {
|
|
|
414
410
|
grammarToolInputProperties: options.grammarToolInputProperties ?? new Map(),
|
|
415
411
|
imageDetail: options.imageDetail ?? "auto",
|
|
416
412
|
nativeAssistantItems,
|
|
417
|
-
...(options.anchorsToolAdditions === undefined
|
|
418
|
-
? {}
|
|
419
|
-
: { anchorsToolAdditions: options.anchorsToolAdditions }),
|
|
420
413
|
}),
|
|
421
414
|
...recoveredPrefix,
|
|
422
415
|
];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const TRANSCRIPT_APIS = [
|
|
2
2
|
"normalizeContext",
|
|
3
3
|
"getCurrentSystemPrompt",
|
|
4
|
+
"getCurrentTools",
|
|
4
5
|
"getDeclaredTools",
|
|
5
6
|
"getInitialSystemMessage",
|
|
6
|
-
"resolveTranscriptTools",
|
|
7
7
|
] as const;
|
|
8
8
|
|
|
9
9
|
/** Check the loaded host, not package metadata that PI_PACKAGE_DIR can override. */
|
|
@@ -249,9 +249,7 @@ export default function registerRemoteCompaction(
|
|
|
249
249
|
grammarToolInputProperties,
|
|
250
250
|
imageDetail: config.imageDetail,
|
|
251
251
|
recoverLatestOverflowPrefix: event.reason === "overflow" && event.willRetry,
|
|
252
|
-
anchorsToolAdditions: false,
|
|
253
252
|
});
|
|
254
|
-
// A cached turn template can contain only the initial declarations.
|
|
255
253
|
// Compaction rebases history, so use the complete current tool set.
|
|
256
254
|
const template: JsonRecord = { ...matching?.payload };
|
|
257
255
|
template.tools =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-openai-codex-compat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@2h2d/oxlint-config": "0.1.1",
|
|
53
53
|
"@2h2d/ts-config": "0.0.1",
|
|
54
|
-
"@earendil-works/pi-ai": "0.86.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "0.86.
|
|
56
|
-
"@earendil-works/pi-tui": "0.86.
|
|
54
|
+
"@earendil-works/pi-ai": "0.86.1",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "0.86.1",
|
|
56
|
+
"@earendil-works/pi-tui": "0.86.1",
|
|
57
57
|
"@types/node": "22.20.1",
|
|
58
58
|
"oxfmt": "0.64.0",
|
|
59
59
|
"oxlint": "1.79.0",
|