pi-openai-codex-compat 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.23 - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Send system messages that other extensions add to a single request, such as
|
|
10
|
+
context-threshold instructions from `pi-compact-assistant`, to Codex as
|
|
11
|
+
developer messages at their position. Codex requests are built from the
|
|
12
|
+
stored session, so these messages were previously dropped.
|
|
13
|
+
|
|
5
14
|
## 0.0.22 - 2026-10-08
|
|
6
15
|
|
|
7
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ The compatibility baseline is official Codex CLI `0.149.1`, released August 24,
|
|
|
76
76
|
| 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. |
|
|
77
77
|
| 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. |
|
|
78
78
|
| Model switching | This package rejects model switches while the active Pi branch contains a native Codex checkpoint because the checkpoint is model-specific. |
|
|
79
|
-
| 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.
|
|
79
|
+
| 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. Request history is rebuilt from the stored session, and system messages that another extension adds to only one request, such as `pi-compact-assistant` instructions, are inserted after the stored message they follow. A forced prompt replaces the leading message. Compaction snapshots and resumed sessions lead with Pi's replayed prompt without rewriting old checkpoints. |
|
|
80
80
|
| 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. |
|
|
81
81
|
| 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. |
|
|
82
82
|
| 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. |
|
|
@@ -524,11 +524,14 @@ export class CodexProviderRuntime {
|
|
|
524
524
|
convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, serializationOptions),
|
|
525
525
|
);
|
|
526
526
|
}
|
|
527
|
+
// History comes from the branch so native items replay exactly. Pi's live
|
|
528
|
+
// transcript adds only the system messages extensions placed in this request.
|
|
527
529
|
return providerHistory({
|
|
528
530
|
branch: scope.manager.getBranch(),
|
|
529
531
|
wireModel: model,
|
|
530
532
|
grammarToolInputProperties,
|
|
531
533
|
imageDetail: scope.config.imageDetail,
|
|
534
|
+
requestMessages: context.messages,
|
|
532
535
|
});
|
|
533
536
|
}
|
|
534
537
|
|
|
@@ -169,6 +169,8 @@ export type EncodeSessionEntriesOptions = {
|
|
|
169
169
|
initialSystemMessage?: SystemMessage;
|
|
170
170
|
/** Model-visible messages per entry. Defaults to the entry's raw messages. */
|
|
171
171
|
entryMessages?: (entry: SessionEntry) => AgentMessages;
|
|
172
|
+
/** Pi's live request transcript, whose request-local system messages are added in place. */
|
|
173
|
+
requestMessages?: readonly Message[];
|
|
172
174
|
};
|
|
173
175
|
|
|
174
176
|
/**
|
|
@@ -221,6 +223,7 @@ export function encodeSessionEntries(options: EncodeSessionEntriesOptions): Resp
|
|
|
221
223
|
entryMessages = sessionEntryToContextMessages,
|
|
222
224
|
} = options;
|
|
223
225
|
const messages = entries.flatMap((entry) => entryMessages(entry));
|
|
226
|
+
const converted = convertToLlm(messages);
|
|
224
227
|
const encodeOptions: EncodeMessagesOptions = {
|
|
225
228
|
model,
|
|
226
229
|
// A partial tail can start with a system update. Seed its prior state so
|
|
@@ -228,7 +231,9 @@ export function encodeSessionEntries(options: EncodeSessionEntriesOptions): Resp
|
|
|
228
231
|
// leading system message that `instructions` already carries.
|
|
229
232
|
messages: [
|
|
230
233
|
options.initialSystemMessage ?? { role: "system", content: "", timestamp: 0 },
|
|
231
|
-
...
|
|
234
|
+
...(options.requestMessages
|
|
235
|
+
? withRequestSystemMessages(converted, options.requestMessages)
|
|
236
|
+
: converted),
|
|
232
237
|
],
|
|
233
238
|
grammarToolInputProperties,
|
|
234
239
|
imageDetail,
|
|
@@ -237,6 +242,68 @@ export function encodeSessionEntries(options: EncodeSessionEntriesOptions): Resp
|
|
|
237
242
|
return encodeMessages(encodeOptions);
|
|
238
243
|
}
|
|
239
244
|
|
|
245
|
+
function systemKey(message: SystemMessage): string {
|
|
246
|
+
return JSON.stringify([message.timestamp, message.content, message.sections ?? null]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function sameMessage(left: Message, right: Message): boolean {
|
|
250
|
+
if (left.role !== right.role || left.timestamp !== right.timestamp) return false;
|
|
251
|
+
if (left.role === "toolResult" && right.role === "toolResult") {
|
|
252
|
+
return left.toolCallId === right.toolCallId;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Add the system messages an extension placed in Pi's live request transcript
|
|
259
|
+
* without storing them in the session, such as request-local instructions from a
|
|
260
|
+
* `context_with_system` handler. History is built from the session branch, so
|
|
261
|
+
* such messages would otherwise never reach Codex.
|
|
262
|
+
*
|
|
263
|
+
* A request-local system message is one that matches no stored system message by
|
|
264
|
+
* timestamp, content, and sections; the leading prompt is excluded. It follows the
|
|
265
|
+
* nearest earlier request message that is also in `messages`, matched by role,
|
|
266
|
+
* timestamp, and tool call ID. Without such a message it leads the conversation
|
|
267
|
+
* after `start` messages. Messages anchored before an encoded range are skipped.
|
|
268
|
+
*/
|
|
269
|
+
export function withRequestSystemMessages(
|
|
270
|
+
messages: readonly Message[],
|
|
271
|
+
requestMessages: readonly Message[],
|
|
272
|
+
start = 0,
|
|
273
|
+
): Message[] {
|
|
274
|
+
const stored = new Set(
|
|
275
|
+
messages.flatMap((message) => (message.role === "system" ? [systemKey(message)] : [])),
|
|
276
|
+
);
|
|
277
|
+
const after = new Map<number, Message[]>();
|
|
278
|
+
for (let index = 1; index < requestMessages.length; index++) {
|
|
279
|
+
const message = requestMessages[index];
|
|
280
|
+
if (message?.role !== "system" || stored.has(systemKey(message))) continue;
|
|
281
|
+
let position = start - 1;
|
|
282
|
+
for (let previous = index - 1; previous > 0; previous--) {
|
|
283
|
+
const anchor = requestMessages[previous];
|
|
284
|
+
if (!anchor || anchor.role === "system") continue;
|
|
285
|
+
const found = messages.findLastIndex(
|
|
286
|
+
(candidate) => candidate.role !== "system" && sameMessage(candidate, anchor),
|
|
287
|
+
);
|
|
288
|
+
if (found >= 0) {
|
|
289
|
+
position = found;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
position = Number.NaN;
|
|
293
|
+
}
|
|
294
|
+
if (Number.isNaN(position)) continue;
|
|
295
|
+
after.set(position, [...(after.get(position) ?? []), message]);
|
|
296
|
+
}
|
|
297
|
+
if (after.size === 0) return [...messages];
|
|
298
|
+
return [
|
|
299
|
+
...messages.slice(0, start),
|
|
300
|
+
...(after.get(start - 1) ?? []),
|
|
301
|
+
...messages
|
|
302
|
+
.slice(start)
|
|
303
|
+
.flatMap((message, offset) => [message, ...(after.get(start + offset) ?? [])]),
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
|
|
240
307
|
export function parseCheckpoint(value: unknown): CheckpointData | undefined {
|
|
241
308
|
if (!isObject(value)) return undefined;
|
|
242
309
|
if (value.kind !== CHECKPOINT_ENTRY_TYPE || value.version !== CHECKPOINT_FORMAT_VERSION) {
|
|
@@ -415,6 +482,8 @@ export function providerHistory(options: {
|
|
|
415
482
|
grammarToolInputProperties?: GrammarToolInputProperties;
|
|
416
483
|
imageDetail?: ImageDetail;
|
|
417
484
|
recoverLatestOverflowPrefix?: boolean;
|
|
485
|
+
/** Pi's live request transcript, whose request-local system messages are added in place. */
|
|
486
|
+
requestMessages?: readonly Message[];
|
|
418
487
|
}): ResponsesInputItem[] {
|
|
419
488
|
const branch = [...options.branch];
|
|
420
489
|
let recoveredPrefix: ResponsesInputItem[] = [];
|
|
@@ -486,17 +555,25 @@ export function providerHistory(options: {
|
|
|
486
555
|
nativeAssistantItems,
|
|
487
556
|
entryMessages,
|
|
488
557
|
...(initialSystemMessage ? { initialSystemMessage } : {}),
|
|
558
|
+
...(options.requestMessages ? { requestMessages: options.requestMessages } : {}),
|
|
489
559
|
}),
|
|
490
560
|
...recoveredPrefix,
|
|
491
561
|
];
|
|
492
562
|
}
|
|
493
563
|
|
|
564
|
+
const messages = convertToLlm(
|
|
565
|
+
projection.entries.flatMap((entry) => entryMessages(entry.sourceEntry)),
|
|
566
|
+
);
|
|
494
567
|
return [
|
|
495
568
|
...encodeMessages({
|
|
496
569
|
model: options.wireModel,
|
|
497
|
-
messages:
|
|
498
|
-
|
|
499
|
-
|
|
570
|
+
messages: options.requestMessages
|
|
571
|
+
? withRequestSystemMessages(
|
|
572
|
+
messages,
|
|
573
|
+
options.requestMessages,
|
|
574
|
+
getInitialSystemMessage(messages) ? 1 : 0,
|
|
575
|
+
)
|
|
576
|
+
: messages,
|
|
500
577
|
grammarToolInputProperties: options.grammarToolInputProperties ?? new Map(),
|
|
501
578
|
imageDetail: options.imageDetail ?? "auto",
|
|
502
579
|
nativeAssistantItems,
|