pi-openai-codex-compat 0.0.1 → 0.0.3

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.
@@ -10,11 +10,15 @@ import {
10
10
  type SettingItem,
11
11
  SettingsList,
12
12
  Text,
13
+ truncateToWidth,
13
14
  } from "@earendil-works/pi-tui";
14
15
  import {
16
+ CONFIG_ENVIRONMENT_VARIABLES,
15
17
  configLayer,
16
18
  loadConfig,
19
+ parseEnvironmentConfig,
17
20
  saveConfig,
21
+ withoutEnvironmentOverrides,
18
22
  writableConfigPath,
19
23
  type CodexCompatConfig,
20
24
  type ConfigLayer,
@@ -48,8 +52,11 @@ function thresholdValue(value: number | undefined): string {
48
52
  return value === undefined ? "Pi default" : `${value}%`;
49
53
  }
50
54
 
51
- export function settingItems(config: CodexCompatConfig): SettingItem[] {
52
- return [
55
+ export function settingItems(
56
+ config: CodexCompatConfig,
57
+ environmentConfig: ConfigLayer = {},
58
+ ): SettingItem[] {
59
+ const items: SettingItem[] = [
53
60
  {
54
61
  id: "fastMode",
55
62
  label: "Fast mode",
@@ -130,6 +137,18 @@ export function settingItems(config: CodexCompatConfig): SettingItem[] {
130
137
  values: ["Pi default", "75%", "80%", "85%", "90%", "95%"],
131
138
  },
132
139
  ];
140
+
141
+ return items.map((item) => {
142
+ const id = item.id as SettingId;
143
+ if (!Object.hasOwn(environmentConfig, id)) return item;
144
+
145
+ const variable = CONFIG_ENVIRONMENT_VARIABLES[id];
146
+ const lockedItem = { ...item };
147
+ lockedItem.currentValue = `${item.currentValue} (env)`;
148
+ lockedItem.description = `${item.description} Locked by ${variable}.`;
149
+ delete lockedItem.values;
150
+ return lockedItem;
151
+ });
133
152
  }
134
153
 
135
154
  export function settingPatch(id: string, value: string): ConfigLayer | undefined {
@@ -212,18 +231,21 @@ async function showSettings(
212
231
  }
213
232
 
214
233
  let config = callbacks.getConfig?.(ctx) ?? loadConfig(ctx.cwd, ctx.isProjectTrusted());
234
+ let persistedConfig = { ...config };
215
235
  let revision = 0;
216
236
  let savedRevision = 0;
217
237
  let saveQueue = Promise.resolve();
218
238
  const filePath = writableConfigPath(ctx.cwd, ctx.isProjectTrusted());
239
+ const environmentConfig = parseEnvironmentConfig();
219
240
 
220
- await ctx.ui.custom((tui, theme, _keybindings, done) => {
241
+ await ctx.ui.custom((tui, theme, keybindings, done) => {
242
+ let closing = false;
221
243
  const container = new Container();
222
244
  container.addChild(
223
245
  new Text(theme.fg("accent", theme.bold("OpenAI Codex Compatibility Settings")), 1, 1),
224
246
  );
225
247
  container.addChild(
226
- new Text(theme.fg("dim", `Session-only. Ctrl+S saves to ${filePath}`), 1, 0),
248
+ new Text(theme.fg("dim", `Ctrl+S saves without closing to ${filePath}`), 1, 0),
227
249
  );
228
250
  const saveStatus = new Text(
229
251
  theme.fg("dim", "Changes apply to this session immediately."),
@@ -232,10 +254,61 @@ async function showSettings(
232
254
  );
233
255
  container.addChild(saveStatus);
234
256
 
257
+ const queueSave = (closeAfterSave: boolean): void => {
258
+ if (closing) return;
259
+ if (closeAfterSave) closing = true;
260
+
261
+ const snapshotConfig = { ...config };
262
+ const snapshot = withoutEnvironmentOverrides(configLayer(snapshotConfig), environmentConfig);
263
+ const snapshotRevision = revision;
264
+ saveStatus.setText(theme.fg("dim", closeAfterSave ? "Saving and closing…" : "Saving…"));
265
+ saveQueue = saveQueue.then(async () => {
266
+ try {
267
+ const savedPath = await saveConfig(ctx.cwd, ctx.isProjectTrusted(), snapshot);
268
+ if (snapshotRevision >= savedRevision) {
269
+ persistedConfig = snapshotConfig;
270
+ savedRevision = snapshotRevision;
271
+ }
272
+ if (closeAfterSave) {
273
+ done(undefined);
274
+ return;
275
+ }
276
+ saveStatus.setText(
277
+ savedRevision === revision
278
+ ? theme.fg("success", `Saved to ${savedPath}`)
279
+ : theme.fg("warning", "Unsaved session changes."),
280
+ );
281
+ } catch (error) {
282
+ if (closeAfterSave) closing = false;
283
+ const message = error instanceof Error ? error.message : String(error);
284
+ saveStatus.setText(theme.fg("error", message));
285
+ ctx.ui.notify(message, "error");
286
+ }
287
+ tui.requestRender();
288
+ });
289
+ tui.requestRender();
290
+ };
291
+
292
+ const discardAndClose = (): void => {
293
+ if (closing) return;
294
+ closing = true;
295
+ saveStatus.setText(theme.fg("dim", "Discarding unsaved changes…"));
296
+ saveQueue = saveQueue.then(() => {
297
+ if (revision !== savedRevision) {
298
+ config = { ...persistedConfig };
299
+ revision = savedRevision;
300
+ callbacks.onChange?.(config, ctx);
301
+ }
302
+ done(undefined);
303
+ });
304
+ tui.requestRender();
305
+ };
306
+
307
+ const listTheme = getSettingsListTheme();
235
308
  const list = new SettingsList(
236
- settingItems(config),
309
+ settingItems(config, environmentConfig),
237
310
  12,
238
- getSettingsListTheme(),
311
+ listTheme,
239
312
  (id, value) => {
240
313
  const patch = settingPatch(id, value);
241
314
  if (!patch) return;
@@ -246,36 +319,42 @@ async function showSettings(
246
319
  saveStatus.setText(theme.fg("warning", "Unsaved session changes."));
247
320
  tui.requestRender();
248
321
  },
249
- () => done(undefined),
322
+ discardAndClose,
250
323
  { enableSearch: true },
251
324
  );
252
- container.addChild(list);
325
+ container.addChild({
326
+ render: (width: number) => {
327
+ const lines = list.render(width);
328
+ if (lines.length > 0) {
329
+ lines[lines.length - 1] = truncateToWidth(
330
+ listTheme.hint(
331
+ " Type to search · Space changes · Enter save & close · Esc discard & close",
332
+ ),
333
+ width,
334
+ "",
335
+ );
336
+ }
337
+ return lines;
338
+ },
339
+ invalidate: () => list.invalidate(),
340
+ handleInput: (data: string) => list.handleInput(data),
341
+ });
253
342
 
254
343
  return {
255
344
  render: (width: number) => container.render(width),
256
345
  invalidate: () => container.invalidate(),
257
346
  handleInput: (data: string) => {
347
+ if (closing) return;
258
348
  if (matchesKey(data, Key.ctrl("s"))) {
259
- const snapshot = configLayer(config);
260
- const snapshotRevision = revision;
261
- saveStatus.setText(theme.fg("dim", "Saving…"));
262
- saveQueue = saveQueue.then(async () => {
263
- try {
264
- const savedPath = await saveConfig(ctx.cwd, ctx.isProjectTrusted(), snapshot);
265
- savedRevision = Math.max(savedRevision, snapshotRevision);
266
- saveStatus.setText(
267
- savedRevision === revision
268
- ? theme.fg("success", `Saved to ${savedPath}`)
269
- : theme.fg("warning", "Unsaved session changes."),
270
- );
271
- } catch (error) {
272
- const message = error instanceof Error ? error.message : String(error);
273
- saveStatus.setText(theme.fg("error", message));
274
- ctx.ui.notify(message, "error");
275
- }
276
- tui.requestRender();
277
- });
278
- tui.requestRender();
349
+ queueSave(false);
350
+ return;
351
+ }
352
+ if (matchesKey(data, Key.enter)) {
353
+ queueSave(true);
354
+ return;
355
+ }
356
+ if (matchesKey(data, Key.escape) || keybindings.matches(data, "tui.select.cancel")) {
357
+ discardAndClose();
279
358
  return;
280
359
  }
281
360
  list.handleInput(data);
@@ -116,6 +116,13 @@ export default function registerWebRun(
116
116
  name: WEB_RUN_TOOL_NAME,
117
117
  label: WEB_RUN_TOOL_NAME,
118
118
  description: WEB_RUN_DESCRIPTION,
119
+ promptSnippet: "Search and browse the internet",
120
+ promptGuidelines: [
121
+ "Use `web.run` when the user explicitly asks to browse or when answering requires current, niche, high-stakes, or precisely sourced information, including recommendations that may change over time.",
122
+ 'Batch independent `web.run` operations in one call, pass only required parameters, and keep `search_query` to at most four queries; use `response_length: "medium"` or `"long"` when sending four.',
123
+ "For technical research, prefer primary sources; for OpenAI product questions, inspect local code first and restrict fallback browsing to official OpenAI sites.",
124
+ "Cite supported claims with direct Markdown links near the relevant text, never expose internal reference IDs, and respect the description's quotation and source word limits.",
125
+ ],
119
126
  parameters: WEB_RUN_PARAMETERS,
120
127
  executionMode: "parallel",
121
128
  renderShell: "self",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-codex-compat",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -31,6 +31,7 @@
31
31
  "scripts": {
32
32
  "check": "hk check --all --check",
33
33
  "test": "node --test --test-concurrency=1 test/*.test.ts",
34
+ "test:live:codex": "PI_CODEX_LIVE_TEST=1 node --test --test-concurrency=1 test/codex-transport.live.test.ts",
34
35
  "fmt": "oxfmt",
35
36
  "lint": "oxlint",
36
37
  "lint:fix": "oxlint --fix"