pi-openai-codex-compat 0.0.15 → 0.0.17

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,35 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.17 - 2026-09-25
6
+
7
+ ### Fixed
8
+
9
+ - Separate menu sections with blank rows, using compact spacing on short terminals.
10
+ - Align setting names on the left and values in a stable column on the right.
11
+ Render the search placeholder in muted gray instead of ordinary input text.
12
+ Keep Save and Discard hints visible in narrow terminals.
13
+ - Align settings with the Anthropic menu: Enter changes values, Ctrl+S saves and
14
+ applies drafts, and Escape discards unapplied drafts without changing active tools.
15
+ - Save only changed overrides, preserve inheritance and external edits, and
16
+ report conflicting saves without overwriting them.
17
+ - Block command-tool switches while persistent command sessions remain instead
18
+ of terminating them while browsing settings.
19
+
20
+ ### Added
21
+
22
+ - Apply to session without changing configuration files. Reopening settings shows
23
+ active session values, including after reload, restart, or resume.
24
+ Ctrl+S can save session-only changes later.
25
+ - Custom percentage editing, configuration-source and model-applicability labels,
26
+ inherited-value resets, and consistent keyboard, focus, and mouse support.
27
+
28
+ ## 0.0.16 - 2026-09-23
29
+
30
+ ### Added
31
+
32
+ - Opt-in Responses Lite for GPT-6 Sol and Luna on Pi 0.87.1.
33
+
5
34
  ## 0.0.15 - 2026-09-23
6
35
 
7
36
  ### Fixed
package/README.md CHANGED
@@ -14,7 +14,7 @@ OpenAI Codex compatibility for [Pi](https://github.com/earendil-works/pi-mono),
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
16
  - **Native request controls**: configures Responses API text verbosity, reasoning summaries, and standard/pro reasoning mode on supported models.
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.
17
+ - **Draft settings pane**: `/codex-settings` stages edits before applying them. Apply to session stores them with the session without changing configuration files. Ctrl+S saves and applies them. Escape discards only unapplied drafts.
18
18
  - **Session-aware footer**: shows the current Pi session ID on the first line and appends non-default Codex request modes to the model side of Pi's normal second line.
19
19
 
20
20
  Pi provides the Codex OAuth flow and model catalog. At session start, this package overrides the built-in `openai-codex` runtime under the same provider id so ordinary responses and remote compaction share one transport, parser, native-history store, and sticky WebSocket session.
@@ -173,7 +173,56 @@ A trusted project can override it at:
173
173
  <project>/.pi/openai-codex-compat.json
174
174
  ```
175
175
 
176
- Each session inherits the effective file-backed settings. Open `/codex-settings` to make immediate session-local changes. Press `Enter` to persist and close, `Escape` to discard unsaved changes and close, or `Ctrl+S` to persist without closing. The global file is the normal save target, while an existing trusted project override remains the target for that project. After `Ctrl+S`, later unsaved changes can still be discarded back to the values from that save.
176
+ Each session inherits the effective file-backed settings. `/codex-settings`
177
+ uses the same interaction contract as `/anthropic-settings`:
178
+
179
+ - **Enter** changes the selected value or opens its editor. It never implicitly saves.
180
+ - **Space** activates a result or inserts a space when search has focus.
181
+ - **Tab / Shift+Tab** move between search, results, and action controls.
182
+ - **F1** opens scrollable details, full errors, and the exact save target.
183
+ - Edits remain drafts until explicitly applied or saved.
184
+ - **Apply to session** applies drafts without writing configuration files and stays open.
185
+ - **Ctrl+S** saves and applies changes without closing.
186
+ - **Save and close** saves and applies changes, then closes on success.
187
+ - **Escape** cancels a field editor or discards unapplied drafts and closes the main menu.
188
+ It does not undo settings already applied to the session.
189
+
190
+ Search matches labels, configuration keys, and descriptions. The percentage
191
+ editor supports presets and custom fractional values. **Use inherited value**
192
+ removes an override. This differs from **Pi default**, which explicitly disables
193
+ an inherited percentage trigger. Rows show configuration sources, environment
194
+ locks, and model applicability. Pi's remapped selection and cancel keys are respected.
195
+
196
+ The global file is the normal save target. An existing trusted project override
197
+ is the target for that project. The target stays fixed while the menu is open.
198
+ Saving changes only edited overrides, preserves unknown keys and inherited values,
199
+ and preserves non-conflicting external edits. Conflicting edits are rejected for review.
200
+ Opening or closing without changes does not create a configuration file.
201
+ Reopening shows active session values, not freshly read file values. `*` marks
202
+ an unapplied draft. `~` marks an active session override or a value that differs
203
+ from the saved configuration. Details shows the saved value when it differs.
204
+ Ctrl+S also persists changes previously applied only to the session.
205
+ Session settings are stored as extension state entries in the Pi session,
206
+ outside model context. They survive menu closure, tree navigation, extension
207
+ reload, process restart, and switching away and back. Resuming the same session
208
+ restores its settings before provider operations. A new session, fork, or clone
209
+ has a separate identity and starts from configuration files.
210
+ Pi's `--no-session` mode cannot be resumed after exit. Pi also defers creating a
211
+ new session file until its first assistant message.
212
+ Environment-controlled fields remain locked and take precedence over restored
213
+ session preferences. Their values are not copied into session settings.
214
+
215
+ Applying and saving wait for Pi to become idle. Collect pending output with `write_stdin` or
216
+ stop running command sessions through `/ps` before applying or saving a command-tool switch.
217
+ Browsing settings and discarding drafts
218
+ never terminate those sessions. Escape cancels a pending operation without discarding
219
+ the draft. Failures leave the menu open. If a save reaches disk but cannot
220
+ be applied to the session, the menu reports that distinction and Ctrl+S retries
221
+ application. Cooperating writers use a `.settings-lock` file. An interrupted
222
+ writer can leave a lock that requires review; locks are not automatically
223
+ deleted. External editors do not participate in that lock.
224
+ Session overrides retain their original conflict checks across menu reopenings.
225
+ Conflicting external edits remain protected after reload or restart.
177
226
 
178
227
  The effective settings are printed once when a TUI session starts. The footer shows the current Pi session ID alongside the working directory and optional session name. It shows `fast` and `pro` only when enabled, and shows text verbosity or reasoning summary only when they differ from their defaults.
179
228
 
@@ -220,11 +269,14 @@ Defaults:
220
269
  | `reasoningMode` | `standard`, `pro` | `standard` | Controls supported models' execution mode independently of Pi's reasoning-effort control. The default omits `reasoning.mode`; `pro` sends `reasoning.mode: "pro"`. |
221
270
 
222
271
  Responses Lite supports exactly `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`,
223
- and `gpt-6-astra`. Pro reasoning mode supports `gpt-5.6`, `gpt-5.6-*`, and
224
- exactly `gpt-6-astra`. Both controls remain opt-in; other GPT-6 model IDs are
225
- not included.
272
+ `gpt-6-astra`, `gpt-6-sol`, and `gpt-6-luna`. Pro reasoning mode supports
273
+ `gpt-5.6`, `gpt-5.6-*`, and exactly `gpt-6-astra`. GPT-6 Sol and Luna use
274
+ standard reasoning mode even when `pro` is configured. Both controls remain
275
+ opt-in. GPT-6 Sol and Luna require Pi 0.87.1's model catalog.
226
276
 
227
- 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.
277
+ Invalid JSON setting values are ignored and invalid JSON does not prevent Pi from
278
+ starting. The settings pane reports malformed JSON instead of overwriting it.
279
+ Project configuration is read only when the project is trusted.
228
280
 
229
281
  Every setting can also be overridden for one Pi process with an environment variable:
230
282
 
@@ -532,7 +584,9 @@ The tool generates new images with `gpt-image-2` or edits up to five local/recen
532
584
  ~/.pi/agent/generated_images/<session-id>/<call-id>.png
533
585
  ```
534
586
 
535
- The active Pi agent directory replaces `~/.pi/agent` when configured differently. Turning `imageGeneration` off removes the tool immediately for the current session; `Enter` or `Ctrl+S` in `/codex-settings` persists the value.
587
+ The active Pi agent directory replaces `~/.pi/agent` when configured differently.
588
+ Saving `imageGeneration: false` through `/codex-settings` removes the tool from
589
+ the current session.
536
590
 
537
591
  The tool registers the server-reserved schema directly with a model-facing absolute-path annotation that names the supported image formats. OpenAI rejects additional schema keywords for image-count bounds and selector exclusivity, so the executor enforces those constraints before filesystem or network access. Local paths are lexically normalized before reading. A one-line system-prompt snippet and four high-signal guidelines cover normal model use. Local images are inspected with Pi's `read` tool, and generated image content is displayed and saved automatically without Codex Code Mode wrappers.
538
592
 
@@ -591,7 +645,7 @@ npm run pack:dry
591
645
  `mise run check` runs the linters, formatters, type checks, and the offline
592
646
  test suite; `mise run test` runs only the offline suite. Both tasks bind
593
647
  `PI_PACKAGE_DIR` to `node_modules/@earendil-works/pi-coding-agent`, so tests
594
- that load Pi in-process read this repository's Pi 0.87.0 resources even when a
648
+ that load Pi in-process read this repository's Pi 0.87.1 resources even when a
595
649
  global `PI_PACKAGE_DIR` points at another installation. Ordinary `pi` launches
596
650
  outside these tasks are unaffected. Offline tests also exercise
597
651
  `scripts/release.ts` with every child process mocked: they never run Git, npm,
@@ -607,10 +661,13 @@ mise run test:live:codex
607
661
  ```
608
662
 
609
663
  The task obtains the local Codex bearer token and runs the tests with
610
- `gpt-5.6-luna` at medium reasoning effort.
664
+ `gpt-5.6-luna`, `gpt-6-sol`, and `gpt-6-luna` at medium reasoning effort.
611
665
  It also packs the extension and loads that archive through the shipped Pi
612
- 0.87.0 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
666
+ 0.87.1 CLI. Ordinary Responses and Responses Lite tests exercise tool calls,
613
667
  prompt reload, native compaction, and persisted-session resume against Codex.
668
+ All three models use standard reasoning mode. SDK tests verify Responses Lite
669
+ WebSocket history, prewarming, continuation, and the built-in read tool.
670
+ CLI tests also execute the built-in read tool after resume.
614
671
  Test credentials stay in memory and child-process environments. Test sessions
615
672
  and configuration are isolated from the user's agent directory.
616
673
 
@@ -622,8 +679,8 @@ The packaged-CLI test selects its archive and executable as follows:
622
679
  existing regular file that `tar` can list, and an empty, missing, directory,
623
680
  or invalid value fails the test instead of falling back to a fresh pack.
624
681
  - `PI_CODEX_CLI_PATH` selects another Pi `cli.js`; the default is this
625
- repository's Pi 0.87.0 dependency. The test asserts that the selected
626
- executable reports version 0.87.0, the only Pi version the packaged CLI test
682
+ repository's Pi 0.87.1 dependency. The test asserts that the selected
683
+ executable reports version 0.87.1, the only Pi version the packaged CLI test
627
684
  is run against. The supported range is `>=0.87.0 <0.88.0`.
628
685
  - Each Pi child process receives `PI_PACKAGE_DIR` bound to the selected
629
686
  executable's package directory, so the runtime under test reads its own
@@ -59,6 +59,7 @@ export { formatBackgroundProcesses } from "./background-process-browser.ts";
59
59
  export type CommandToolsApi = Pick<ExtensionAPI, "on" | "registerCommand" | "registerTool">;
60
60
 
61
61
  export type CommandToolsController = {
62
+ hasPersistentSessions: () => boolean;
62
63
  terminateUnifiedExecSessions: () => void;
63
64
  };
64
65
 
@@ -352,6 +353,7 @@ export default function registerCommandTools(
352
353
  pi.on("session_shutdown", () => manager.terminateAll());
353
354
 
354
355
  return {
356
+ hasPersistentSessions: () => manager.activeSessionCount() > 0,
355
357
  terminateUnifiedExecSessions() {
356
358
  manager.terminateAll().catch((error: unknown) => {
357
359
  console.error("Could not terminate unified exec sessions.", error);
@@ -1,12 +1,10 @@
1
1
  import { isBoolean, isNumber, nodeErrorCode } from "./value-contracts.ts";
2
- import { randomUUID } from "node:crypto";
3
2
  import { existsSync, readFileSync } from "node:fs";
4
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
5
- import { basename, dirname, join } from "node:path";
3
+ import { join } from "node:path";
6
4
  import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
7
5
  import type { Static } from "typebox";
8
6
  import { Value } from "typebox/value";
9
- import { isObject, type JsonRecord } from "./codex-protocol.ts";
7
+ import { isObject } from "./codex-protocol.ts";
10
8
 
11
9
  export const WEB_SEARCH_MODE_SCHEMA = {
12
10
  enum: ["disabled", "cached", "indexed", "live"],
@@ -465,55 +463,3 @@ export function withoutEnvironmentOverrides(
465
463
  }
466
464
  return result;
467
465
  }
468
-
469
- async function readWritableConfig(filePath: string): Promise<JsonRecord> {
470
- try {
471
- const value: unknown = JSON.parse(await readFile(filePath, "utf8"));
472
- if (!isObject(value)) throw new Error("the root value must be a JSON object");
473
- return value;
474
- } catch (error) {
475
- if (nodeErrorCode(error) === "ENOENT") return {};
476
- const detail = error instanceof Error ? error.message : String(error);
477
- throw new Error(`Cannot update ${filePath}: ${detail}`, { cause: error });
478
- }
479
- }
480
-
481
- /**
482
- * Merge known setting changes into the dedicated extension file. Unknown keys
483
- * are retained, and an invalid existing file is never overwritten.
484
- */
485
- export async function saveConfig(
486
- cwd: string,
487
- projectTrusted: boolean,
488
- patch: ConfigLayer,
489
- ): Promise<string> {
490
- const filePath = writableConfigPath(cwd, projectTrusted);
491
- const current = await readWritableConfig(filePath);
492
- const next = { ...current, ...patch };
493
- const directory = dirname(filePath);
494
- const temporaryPath = join(
495
- directory,
496
- `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
497
- );
498
-
499
- await mkdir(directory, { recursive: true });
500
- const existingMode = await stat(filePath)
501
- .then((metadata) => metadata.mode & 0o777)
502
- .catch((error: unknown) => {
503
- if (nodeErrorCode(error) === "ENOENT") return 0o600;
504
- throw error;
505
- });
506
-
507
- try {
508
- await writeFile(temporaryPath, `${JSON.stringify(next, null, 2)}\n`, {
509
- encoding: "utf8",
510
- flag: "wx",
511
- mode: existingMode,
512
- });
513
- await rename(temporaryPath, filePath);
514
- } finally {
515
- await rm(temporaryPath, { force: true });
516
- }
517
-
518
- return filePath;
519
- }
@@ -12,7 +12,7 @@ import registerOutputLimitContinuation, {
12
12
  } from "./output-limit-continuation.ts";
13
13
  import registerRemoteCompaction, { remoteCompactionApi } from "./remote-compaction.ts";
14
14
  import registerCodexRequestOptions from "./request-options.ts";
15
- import registerCodexSettings from "./settings-pane.ts";
15
+ import registerCodexSettings, { loadSessionConfig } from "./settings-pane.ts";
16
16
  import registerCodexThreadLineage, { codexThreadLineageApi } from "./codex-thread-lineage.ts";
17
17
  import registerCodexTools, { syncCodexTools } from "./tools.ts";
18
18
 
@@ -26,8 +26,11 @@ export {
26
26
  export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
27
27
  requirePiTranscriptRuntime(piAi, piCodingAgent);
28
28
  let activeConfig: CodexCompatConfig | undefined;
29
+ let sessionContext: Parameters<typeof loadSessionConfig>[0] | undefined;
29
30
  const resolveConfig = (ctx: ConfigContext): CodexCompatConfig => {
30
- activeConfig ??= loadConfig(ctx.cwd, ctx.isProjectTrusted());
31
+ activeConfig ??= sessionContext
32
+ ? loadSessionConfig(sessionContext)
33
+ : loadConfig(ctx.cwd, ctx.isProjectTrusted());
31
34
  return activeConfig;
32
35
  };
33
36
  const resolveToolBackground = () => activeConfig?.toolBackground ?? DEFAULT_CONFIG.toolBackground;
@@ -37,7 +40,9 @@ export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
37
40
  activeConfig?.applyPatchDiagnostics ?? DEFAULT_CONFIG.applyPatchDiagnostics;
38
41
 
39
42
  pi.on("session_start", (event, ctx) => {
40
- activeConfig = loadConfig(ctx.cwd, ctx.isProjectTrusted());
43
+ sessionContext = ctx;
44
+ activeConfig = undefined;
45
+ activeConfig = resolveConfig(ctx);
41
46
  installCodexFooter(ctx, resolveConfig);
42
47
  if (event.reason !== "reload" && ctx.mode === "tui") {
43
48
  ctx.ui.notify("pi-openai-codex-compat loaded · /codex-settings", "info");
@@ -60,6 +65,7 @@ export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
60
65
  registerCodexModelPolicy(modelPolicy, resolveConfig, commandTools);
61
66
  registerCodexSettings(pi, {
62
67
  getConfig: resolveConfig,
68
+ hasPersistentSessions: commandTools.hasPersistentSessions,
63
69
  onChange(config, ctx) {
64
70
  activeConfig = config;
65
71
  codexProvider.updateSessionConfig(ctx.sessionId, config);
@@ -10,6 +10,8 @@ const RESPONSES_LITE_MODELS: ReadonlySet<string> = new Set([
10
10
  "gpt-5.6-terra",
11
11
  "gpt-5.6-luna",
12
12
  "gpt-6-astra",
13
+ "gpt-6-sol",
14
+ "gpt-6-luna",
13
15
  ]);
14
16
 
15
17
  function isHostedTool(tool: JsonRecord): boolean {