@monotykamary/pi-better-openai 0.1.40 → 0.1.41

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/README.md CHANGED
@@ -26,6 +26,7 @@ Usage display, image generation, and live voice require pi's `openai-codex` OAut
26
26
  2. Verify subscription usage with `/openai-usage`, or open `/openai-settings` and check **Diagnostics**.
27
27
  3. The extension reads auth from pi's agent auth store, normally `~/.pi/agent/auth.json`. Do not copy, paste, or commit values from this file.
28
28
  4. If `PI_CODING_AGENT_DIR` is set, the auth store, global extension config, and global generated-image directory use that agent directory instead of `~/.pi/agent`. A leading `~/` is expanded to your home directory.
29
+ 5. When [pi-multiprovider](https://github.com/monotykamary/pi-multiprovider) 0.4.0+ pools several `openai-codex` accounts, the session's active account (chosen with `/switch-account`) is resolved first for usage display, image generation, web search, and live voice; the usage widget refreshes on every switch. Without that extension, credential resolution is unchanged.
29
30
 
30
31
  ## Features
31
32
 
package/index.ts CHANGED
@@ -14,6 +14,13 @@ import {
14
14
  } from "@earendil-works/pi-tui";
15
15
  import { registerOpenAICodexModels } from "./src/codex-models.ts";
16
16
  import { CONFIG_BASENAME, STATUS_KEY } from "./src/identity.ts";
17
+ import {
18
+ CODEX_PROVIDER_ID,
19
+ isMultiproviderService,
20
+ MULTIPROVIDER_SERVICE_EVENT,
21
+ setActiveMultiproviderService,
22
+ type MultiproviderService,
23
+ } from "./src/multiprovider.ts";
17
24
  import {
18
25
  formatTokens,
19
26
  redactDiagnosticValue,
@@ -227,6 +234,32 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
227
234
  let cachedSessionName: string | undefined;
228
235
  const usageController = new UsageController(config, updateFooter);
229
236
  const petController = new PetFooterController(config, updateFooter, () => footerInstalled);
237
+ let multiproviderService: MultiproviderService | undefined;
238
+ let unsubscribeMultiprovider: (() => void) | undefined;
239
+ let multiproviderRefreshCtx: ExtensionContext | undefined;
240
+
241
+ // Follow pi-multiprovider's active pooled account for openai-codex. The
242
+ // event re-fires with the same stable object at load and session start; the
243
+ // identity check keeps the change subscription attached exactly once. When
244
+ // the extension is absent, nothing here activates and credential resolution
245
+ // keeps its standalone behavior.
246
+ if (typeof pi.events?.on === "function") {
247
+ pi.events.on(MULTIPROVIDER_SERVICE_EVENT, (value) => {
248
+ if (!isMultiproviderService(value) || value === multiproviderService) return;
249
+ unsubscribeMultiprovider?.();
250
+ multiproviderService = value;
251
+ setActiveMultiproviderService(value);
252
+ unsubscribeMultiprovider = value.onActiveAccountChanged(CODEX_PROVIDER_ID, (event) => {
253
+ void usageController.refresh(event.ctx, undefined, { force: true });
254
+ updateFooter(event.ctx);
255
+ });
256
+ const ctx = multiproviderRefreshCtx;
257
+ if (ctx) {
258
+ void usageController.refresh(ctx, undefined, { force: true });
259
+ updateFooter(ctx);
260
+ }
261
+ });
262
+ }
230
263
 
231
264
  function refresh(ctx: ExtensionContext): ResolvedConfig {
232
265
  cachedConfig = resolveConfig(ctx.cwd || process.cwd());
@@ -1261,6 +1294,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
1261
1294
  pi.on("session_start", (_event, ctx) => {
1262
1295
  invalidateContextUsage();
1263
1296
  invalidateSessionName();
1297
+ multiproviderRefreshCtx = ctx;
1264
1298
  const nextConfig = refresh(ctx);
1265
1299
  fastController.initializeForSession(ctx, nextConfig, pi.getFlag(FLAG) === true);
1266
1300
  if (
@@ -1347,6 +1381,7 @@ export default function betterOpenAI(pi: ExtensionAPI): void {
1347
1381
  pi.on("session_shutdown", () => {
1348
1382
  invalidateContextUsage();
1349
1383
  invalidateSessionName();
1384
+ multiproviderRefreshCtx = undefined;
1350
1385
  usageController.shutdown();
1351
1386
  petController.shutdown();
1352
1387
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-better-openai",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
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",
package/src/codex-auth.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { CODEX_PROVIDER_ID, getActiveMultiproviderService } from "./multiprovider.ts";
4
5
  import { piAgentDir } from "./paths.ts";
5
6
 
6
7
  export const AUTH_FILE = join(piAgentDir(), "auth.json");
@@ -10,8 +11,13 @@ export type CodexCredentials = {
10
11
  accountId: string;
11
12
  };
12
13
 
14
+ export type CodexCredentialsContext = Pick<
15
+ ExtensionContext,
16
+ "modelRegistry" | "model" | "sessionManager"
17
+ >;
18
+
13
19
  export type CodexCredentialsWithSource = CodexCredentials & {
14
- source: "modelRegistry" | "authFile";
20
+ source: "multiprovider" | "modelRegistry" | "authFile";
15
21
  };
16
22
 
17
23
  function waitForSignal<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T> {
@@ -118,11 +124,28 @@ export function readCodexAuth(): CodexCredentials | undefined {
118
124
  }
119
125
 
120
126
  export async function getCodexCredentials(
121
- ctx?: Pick<ExtensionContext, "modelRegistry">,
127
+ ctx?: CodexCredentialsContext,
122
128
  signal?: AbortSignal,
123
129
  ): Promise<CodexCredentialsWithSource | undefined> {
124
130
  if (signal?.aborted) throw signal.reason ?? new Error("Operation was aborted.");
125
- const registryRequest = ctx?.modelRegistry?.getApiKeyForProvider("openai-codex");
131
+ // A pooled account pinned for this session (pi-multiprovider /switch-account)
132
+ // wins over pi's own credential: subscription usage is per-account.
133
+ const multiprovider = getActiveMultiproviderService();
134
+ if (multiprovider && ctx) {
135
+ try {
136
+ const resolved = await waitForSignal(
137
+ multiprovider.resolveActiveAccountAuth(CODEX_PROVIDER_ID, ctx, signal),
138
+ signal,
139
+ );
140
+ const accountId = resolved ? extractAccountIdFromJwt(resolved.accessToken) : undefined;
141
+ if (resolved && accountId) {
142
+ return { accessToken: resolved.accessToken, accountId, source: "multiprovider" };
143
+ }
144
+ } catch {
145
+ // Fall back to pi-owned credential resolution.
146
+ }
147
+ }
148
+ const registryRequest = ctx?.modelRegistry?.getApiKeyForProvider(CODEX_PROVIDER_ID);
126
149
  const registryToken = registryRequest
127
150
  ? await waitForSignal(
128
151
  registryRequest.catch(() => undefined),
@@ -0,0 +1,66 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ // Soft bridge to pi-multiprovider. When that extension is installed it emits
4
+ // this event with a service object; without it everything here stays inert and
5
+ // credential resolution keeps its standalone behavior.
6
+ export const MULTIPROVIDER_SERVICE_EVENT = "pi-multiprovider:service";
7
+ export const CODEX_PROVIDER_ID = "openai-codex";
8
+
9
+ export type MultiproviderActiveAccount = {
10
+ id: string;
11
+ label: string;
12
+ authKind: string;
13
+ };
14
+
15
+ export type MultiproviderAccountAuth = {
16
+ accessToken: string;
17
+ label: string;
18
+ source?: string;
19
+ };
20
+
21
+ export type MultiproviderAccountChangedEvent = {
22
+ providerId: string;
23
+ account: MultiproviderActiveAccount | undefined;
24
+ ctx: ExtensionContext;
25
+ };
26
+
27
+ export type MultiproviderServiceContext = Pick<
28
+ ExtensionContext,
29
+ "modelRegistry" | "model" | "sessionManager"
30
+ >;
31
+
32
+ export type MultiproviderService = {
33
+ getActiveAccount(
34
+ providerId: string,
35
+ ctx: MultiproviderServiceContext,
36
+ ): Promise<MultiproviderActiveAccount | undefined>;
37
+ resolveActiveAccountAuth(
38
+ providerId: string,
39
+ ctx: MultiproviderServiceContext,
40
+ signal?: AbortSignal,
41
+ ): Promise<MultiproviderAccountAuth | undefined>;
42
+ onActiveAccountChanged(
43
+ providerId: string,
44
+ callback: (event: MultiproviderAccountChangedEvent) => void,
45
+ ): () => void;
46
+ };
47
+
48
+ export function isMultiproviderService(value: unknown): value is MultiproviderService {
49
+ if (typeof value !== "object" || value === null) return false;
50
+ const candidate = value as Partial<MultiproviderService>;
51
+ return (
52
+ typeof candidate.getActiveAccount === "function" &&
53
+ typeof candidate.resolveActiveAccountAuth === "function" &&
54
+ typeof candidate.onActiveAccountChanged === "function"
55
+ );
56
+ }
57
+
58
+ let activeService: MultiproviderService | undefined;
59
+
60
+ export function setActiveMultiproviderService(service: MultiproviderService | undefined): void {
61
+ activeService = service;
62
+ }
63
+
64
+ export function getActiveMultiproviderService(): MultiproviderService | undefined {
65
+ return activeService;
66
+ }