@monotykamary/pi-better-openai 0.2.9 → 0.2.11

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
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.11
4
+
5
+ - Auto-redeem banked Codex resets ten minutes before expiry instead of one minute, with matching settings and redemption-time displays.
6
+ - Preserve the scheduled credit across polling refreshes so the longer lead retains the no-fallback safeguard.
7
+ - Extend the shared redemption cooldown to ten minutes and add regression coverage for exact timing and startup inside the redemption window.
8
+
9
+ ## 0.2.10
10
+
11
+ - Hide disabled image, web search, and decision tools and their prompt guidance, including from pi-fabric capture.
12
+ - Apply settings and decision-command toggles immediately without reactivating unrelated tools; retain execution guards and configuration commands.
13
+ - Add real Pi regression coverage for startup, live toggles, nested-call availability, and prompt updates.
14
+ - Update brace-expansion to 5.0.11 and undici to 8.10.2 to resolve high-severity security advisories.
15
+
3
16
  ## 0.2.9
4
17
 
5
18
  - Validate Pi 1.0.0 with exact development pins and wildcard host peers.
package/README.md CHANGED
@@ -54,9 +54,9 @@ Usage display, image generation, and live voice require pi's `openai-codex` OAut
54
54
 
55
55
  ## Banked resets
56
56
 
57
- Unused banked Codex resets **auto-redeem by default, 1 minute before expiry**, while an interactive pi session is running and Codex credentials are available. Starting pi within that final one-minute window also triggers the check; expired credits are skipped. This runs independently of the usage display and current model. The reset picker and confirmation show each credit's actual local auto-redemption date and time (expiry minus one minute) beside its expiry. Disable **Auto-redeem banked resets** in `/openai-settings` or set `usage.autoRedeemBankedResets` to `false` to opt out.
57
+ Unused banked Codex resets **auto-redeem by default, 10 minutes before expiry**, while an interactive pi session is running and Codex credentials are available. Starting pi within that final ten-minute window also triggers the check; expired credits are skipped. This runs independently of the usage display and current model. The reset picker and confirmation show each credit's actual local auto-redemption date and time (expiry minus ten minutes) beside its expiry. Disable **Auto-redeem banked resets** in `/openai-settings` or set `usage.autoRedeemBankedResets` to `false` to opt out.
58
58
 
59
- For safety, each attempt targets one explicit, freshly checked credit ID, with no fallback to another credit and no automatic retry after a consume request (including errors or `nothing_to_reset`). A persistent per-account guard permits at most one redemption attempt in one minute across pi sessions sharing the same agent directory; manual redemption uses the same guard. Automatic reservations recheck the one-minute eligibility window while holding an exclusive filesystem lock, and attempted credit IDs remain blocked even after later redemptions or restarts. Simultaneously expiring credits are not drained, and later credits wait for their own final one-minute window. Reservations live under `$PI_CODING_AGENT_DIR/pi-better-openai/reset-redemptions` (default `~/.pi/agent/pi-better-openai/reset-redemptions`); unreadable state or an orphaned lock blocks redemption rather than risking a duplicate. Update/restart all pi instances to use the current guard. Separate machines/agent directories cannot coordinate this local guard; instances using the same account should share an agent directory.
59
+ For safety, each attempt targets one explicit, freshly checked credit ID, with no fallback to another credit and no automatic retry after a consume request (including errors or `nothing_to_reset`). A persistent per-account guard permits at most one redemption attempt in ten minutes across pi sessions sharing the same agent directory; manual redemption uses the same guard. Automatic reservations recheck the ten-minute eligibility window while holding an exclusive filesystem lock, and attempted credit IDs remain blocked even after later redemptions or restarts. Simultaneously expiring credits are not drained, and later credits wait for their own final ten-minute window. Reservations live under `$PI_CODING_AGENT_DIR/pi-better-openai/reset-redemptions` (default `~/.pi/agent/pi-better-openai/reset-redemptions`); unreadable state or an orphaned lock blocks redemption rather than risking a duplicate. Update/restart all pi instances to use the current guard. Separate machines/agent directories cannot coordinate this local guard; instances using the same account should share an agent directory.
60
60
 
61
61
  Pi must remain running and awake; this is not an OS-level scheduled task. No eligible usage window or unavailable credentials can prevent redemption.
62
62
 
@@ -139,6 +139,8 @@ Example config:
139
139
  }
140
140
  ```
141
141
 
142
+ Setting `image.enabled`, `websearch.enabled`, or `decisions.enabled` to `false` hides that tool and removes its system-prompt guidance, including from pi-fabric capture. Changes in `/openai-settings` or `/openai-decisions` apply immediately; use `/reload` after editing config files manually. Configuration commands remain available.
143
+
142
144
  ## Service tiers
143
145
 
144
146
  `/openai-tier fast` requests `service_tier: "priority"`. `/openai-tier standard` explicitly requests `"default"`, clearing an inherited Fast/Ultrafast request tier on OpenAI providers. Legacy disabled configurations without an explicit tier leave payloads untouched. `/fast` and the `--fast` flag never select Ultrafast.
package/index.ts CHANGED
@@ -83,6 +83,7 @@ import { registerOpenAIImage, _imageTest } from "./src/image.ts";
83
83
  import { registerOpenAIWebSearch, _websearchTest } from "./src/websearch.ts";
84
84
  import { registerOpenAILive } from "./src/live/index.ts";
85
85
  import { registerOpenAIDecisions } from "./src/decisions.ts";
86
+ import type { OptionalTool } from "./src/optional-tool.ts";
86
87
  import {
87
88
  type CodexPetPackage,
88
89
  codexHome,
@@ -280,6 +281,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
280
281
 
281
282
  const fastController = new FastController(SERVICE_TIER);
282
283
  let cachedConfig: ResolvedConfig | undefined;
284
+ const optionalTools = new Map<"image" | "websearch" | "decisions", OptionalTool>();
283
285
  let footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
284
286
  let footerInstalled = false;
285
287
  let statusInstalled = false;
@@ -327,8 +329,13 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
327
329
  });
328
330
  }
329
331
 
332
+ function syncToolExposure(cfg: ResolvedConfig): void {
333
+ for (const [feature, tool] of optionalTools) tool.setEnabled(cfg[feature].enabled);
334
+ }
335
+
330
336
  function refresh(ctx: ExtensionContext): ResolvedConfig {
331
337
  cachedConfig = resolveConfig(ctx.cwd || process.cwd());
338
+ syncToolExposure(cachedConfig);
332
339
  return cachedConfig;
333
340
  }
334
341
 
@@ -1187,9 +1194,10 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
1187
1194
  },
1188
1195
  });
1189
1196
 
1190
- registerOpenAIImage(pi, config);
1191
- registerOpenAIWebSearch(pi, config);
1192
- registerOpenAIDecisions(pi, config, refresh);
1197
+ optionalTools.set("image", registerOpenAIImage(pi, config));
1198
+ optionalTools.set("websearch", registerOpenAIWebSearch(pi, config));
1199
+ optionalTools.set("decisions", registerOpenAIDecisions(pi, config, refresh));
1200
+ if (cachedConfig) syncToolExposure(cachedConfig);
1193
1201
  registerOpenAILive(pi, config);
1194
1202
  registerOpenAIPets(pi, {
1195
1203
  wake: async (ctx, slug) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-better-openai",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Improve OpenAI in pi with fast mode, usage stats, realtime voice, image generation, and footer polish.",
5
5
  "keywords": [
6
6
  "fast",
@@ -61,7 +61,7 @@
61
61
  "https-proxy-agent": "9.1.0",
62
62
  "proxy-from-env": "2.1.0",
63
63
  "sharp": "0.35.4",
64
- "undici": "8.10.0",
64
+ "undici": "8.10.2",
65
65
  "ws": "8.21.2"
66
66
  },
67
67
  "devDependencies": {
@@ -91,11 +91,11 @@
91
91
  "overrides": {
92
92
  "@earendil-works/pi-agent-core": "1.0.0",
93
93
  "@earendil-works/pi-ai": "1.0.0",
94
- "brace-expansion": "5.0.9",
94
+ "brace-expansion": "5.0.11",
95
95
  "nanoid": "3.3.18",
96
96
  "postcss": "8.5.25",
97
97
  "protobufjs": "7.6.5",
98
- "undici": "8.10.0"
98
+ "undici": "8.10.2"
99
99
  },
100
100
  "engines": {
101
101
  "node": ">=22.19.0"
package/src/config.ts CHANGED
@@ -410,7 +410,7 @@ export const USAGE_SETTING_DESCRIPTORS: readonly SettingsOptionDescriptor[] = [
410
410
  label: "Auto-redeem banked resets",
411
411
  currentValue: (cfg) => String(cfg.usage.autoRedeemBankedResets),
412
412
  values: ["true", "false"],
413
- description: "Redeem one unused banked reset 1 minute before expiry while pi is running.",
413
+ description: "Redeem one unused banked reset 10 minutes before expiry while pi is running.",
414
414
  parse: booleanSetting,
415
415
  },
416
416
  ];
package/src/decisions.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  type DecisionsConfig,
14
14
  type ResolvedConfig,
15
15
  } from "./config.ts";
16
+ import { registerOptionalTool, type OptionalTool } from "./optional-tool.ts";
16
17
 
17
18
  export const OPENAI_DECIDE_TOOL = "openai_decide";
18
19
  export const OPENAI_DECISIONS_COMMAND = "openai-decisions";
@@ -335,7 +336,7 @@ export function registerOpenAIDecisions(
335
336
  pi: ExtensionAPI,
336
337
  getConfig: (ctx: ExtensionContext) => ResolvedConfig,
337
338
  refreshConfig: (ctx: ExtensionContext) => ResolvedConfig,
338
- ): void {
339
+ ): OptionalTool {
339
340
  function save(ctx: ExtensionContext, patch: DecisionsConfig): void {
340
341
  const cfg = refreshConfig(ctx);
341
342
  const raw = readRawConfig(cfg.configPath);
@@ -343,7 +344,7 @@ export function registerOpenAIDecisions(
343
344
  ...raw,
344
345
  decisions: { ...(isRecord(raw.decisions) ? raw.decisions : {}), ...patch },
345
346
  });
346
- refreshConfig(ctx);
347
+ tool.setEnabled(refreshConfig(ctx).decisions.enabled);
347
348
  }
348
349
 
349
350
  pi.registerCommand(OPENAI_DECISIONS_COMMAND, {
@@ -388,7 +389,7 @@ export function registerOpenAIDecisions(
388
389
  }
389
390
  },
390
391
  });
391
- pi.registerTool({
392
+ const tool = registerOptionalTool(pi, {
392
393
  name: OPENAI_DECIDE_TOOL,
393
394
  label: "Typed decision",
394
395
  description:
@@ -402,4 +403,5 @@ export function registerOpenAIDecisions(
402
403
  execute: (_id, params, signal, _onUpdate, ctx) =>
403
404
  evaluateDecision(ctx, getConfig(ctx), params, signal),
404
405
  });
406
+ return tool;
405
407
  }
package/src/image.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  } from "./codex-auth.ts";
14
14
  import { maskIdentifier, sanitizeDiagnosticError } from "./format.ts";
15
15
  import { piAgentDir, resolveUserPath } from "./paths.ts";
16
+ import { registerOptionalTool, type OptionalTool } from "./optional-tool.ts";
16
17
 
17
18
  const OPENAI_IMAGE_TOOL = "openai_image";
18
19
  const OPENAI_IMAGE_COMMAND = "openai-image";
@@ -464,7 +465,7 @@ function resultText(result: CodexImageResult): string {
464
465
  export function registerOpenAIImage(
465
466
  pi: ExtensionAPI,
466
467
  getConfig: (ctx: ExtensionContext) => ResolvedConfig,
467
- ): { getDebug: (ctx: ExtensionContext) => Promise<ImageGenerationDebug> } {
468
+ ): OptionalTool & { getDebug: (ctx: ExtensionContext) => Promise<ImageGenerationDebug> } {
468
469
  let lastStatus: string | undefined;
469
470
  let lastError: string | undefined;
470
471
 
@@ -581,7 +582,7 @@ export function registerOpenAIImage(
581
582
  },
582
583
  });
583
584
 
584
- pi.registerTool({
585
+ const tool = registerOptionalTool(pi, {
585
586
  name: OPENAI_IMAGE_TOOL,
586
587
  label: "OpenAI image",
587
588
  description:
@@ -616,7 +617,7 @@ export function registerOpenAIImage(
616
617
  },
617
618
  });
618
619
 
619
- return { getDebug };
620
+ return { getDebug, ...tool };
620
621
  }
621
622
 
622
623
  export const _imageTest = {
@@ -0,0 +1,27 @@
1
+ import type { TSchema } from "@earendil-works/pi-ai";
2
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
3
+
4
+ export interface OptionalTool {
5
+ setEnabled(enabled: boolean): void;
6
+ }
7
+
8
+ /** Hidden exposure withdraws both native calls and Fabric capture, unlike deactivation alone. */
9
+ export function registerOptionalTool<TParams extends TSchema, TDetails>(
10
+ pi: ExtensionAPI,
11
+ definition: ToolDefinition<TParams, TDetails>,
12
+ ): OptionalTool {
13
+ let enabled: boolean | undefined;
14
+ const tool: OptionalTool = {
15
+ setEnabled(next) {
16
+ if (enabled === next) return;
17
+ pi.registerTool({
18
+ ...definition,
19
+ exposure: next ? (definition.exposure ?? "direct") : "hidden",
20
+ });
21
+ enabled = next;
22
+ },
23
+ };
24
+ // The factory has no session cwd yet. Stay unreachable until config is resolved.
25
+ tool.setEnabled(false);
26
+ return tool;
27
+ }
@@ -159,7 +159,7 @@ export class ResetController {
159
159
  );
160
160
  }
161
161
 
162
- private scheduleAutoRedeem(): void {
162
+ private scheduleAutoRedeem(scheduled?: BankedResetCredit): void {
163
163
  const ctx = this.activeCtx;
164
164
  if (!ctx || this.redeeming) return;
165
165
  try {
@@ -170,7 +170,7 @@ export class ResetController {
170
170
  // A cache refresh must not replace a due timer with the next credit,
171
171
  // particularly when the scheduled one was redeemed in another client.
172
172
  if (this.autoTimer) return;
173
- const credit = selectAutoRedeemCredit(this.cache?.credits.credits ?? []);
173
+ const credit = scheduled ?? selectAutoRedeemCredit(this.cache?.credits.credits ?? []);
174
174
  if (!credit || credit.expiresAtMs === null) return;
175
175
  const dueAt = Math.max(
176
176
  credit.expiresAtMs - BANKED_RESET_AUTO_REDEEM_LEAD_MS,
@@ -179,7 +179,9 @@ export class ResetController {
179
179
  this.autoTimer = setTimeout(
180
180
  () => {
181
181
  this.autoTimer = undefined;
182
- if (Date.now() < dueAt) this.scheduleAutoRedeem();
182
+ // Preserve the exact credit across capped timer wakeups, even when
183
+ // a polling refresh has changed the cached credit list.
184
+ if (Date.now() < dueAt) this.scheduleAutoRedeem(credit);
183
185
  else void this.autoRedeem(ctx, credit);
184
186
  },
185
187
  Math.max(0, Math.min(dueAt - Date.now(), BANKED_RESET_CACHE_TTL_MS)),
package/src/resets.ts CHANGED
@@ -7,11 +7,10 @@ export const RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limi
7
7
  export const CONSUME_RESET_URL =
8
8
  "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
9
9
 
10
- // Auto-redeem fires in the final minute before expiry. The automatic flow
11
- // (credentials, a fresh credit re-check, reservation, and consume POST) shares
12
- // one 10s abort deadline, so a 60s lead still leaves the server tens of seconds
13
- // of headroom while spending the credit as late as safely possible.
14
- export const BANKED_RESET_AUTO_REDEEM_LEAD_MS = 60_000;
10
+ // Auto-redeem fires ten minutes before expiry to leave more headroom than a
11
+ // last-minute attempt. The automatic flow (credentials, a fresh credit re-check,
12
+ // reservation, and consume POST) still shares one 10s abort deadline.
13
+ export const BANKED_RESET_AUTO_REDEEM_LEAD_MS = 10 * 60_000;
15
14
 
16
15
  export type BankedResetStatus = "available" | "redeeming" | "redeemed" | "unknown";
17
16
 
package/src/websearch.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  } from "./config.ts";
9
9
  import { getCodexCredentials, type CodexCredentialsWithSource } from "./codex-auth.ts";
10
10
  import { maskIdentifier, sanitizeDiagnosticError } from "./format.ts";
11
+ import { registerOptionalTool, type OptionalTool } from "./optional-tool.ts";
11
12
 
12
13
  export const OPENAI_WEBSEARCH_TOOL = "openai_websearch";
13
14
  export const OPENAI_WEBSEARCH_COMMAND = "openai-websearch";
@@ -316,7 +317,7 @@ async function requestWebSearch(
316
317
  export function registerOpenAIWebSearch(
317
318
  pi: ExtensionAPI,
318
319
  getConfig: (ctx: ExtensionContext) => ResolvedConfig,
319
- ): { getDebug: (ctx: ExtensionContext) => Promise<WebSearchDebug> } {
320
+ ): OptionalTool & { getDebug: (ctx: ExtensionContext) => Promise<WebSearchDebug> } {
320
321
  let lastStatus: string | undefined;
321
322
  let lastError: string | undefined;
322
323
 
@@ -402,7 +403,7 @@ export function registerOpenAIWebSearch(
402
403
  },
403
404
  });
404
405
 
405
- pi.registerTool({
406
+ const tool = registerOptionalTool(pi, {
406
407
  name: OPENAI_WEBSEARCH_TOOL,
407
408
  label: "OpenAI web search",
408
409
  description:
@@ -426,7 +427,7 @@ export function registerOpenAIWebSearch(
426
427
  },
427
428
  });
428
429
 
429
- return { getDebug };
430
+ return { getDebug, ...tool };
430
431
  }
431
432
 
432
433
  export const _websearchTest = {