From 27ae14dbbbb4c599083031586bc41e4725805266 Mon Sep 17 00:00:00 2001 From: Ivan Kolesnikov Date: Sat, 18 Apr 2026 16:59:57 +0400 Subject: [PATCH 1/2] Honor models.json openai-codex overrides --- provider.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/provider.ts b/provider.ts index 7f04e74..e1402cc 100644 --- a/provider.ts +++ b/provider.ts @@ -1,4 +1,7 @@ import { getApiProvider } from "@mariozechner/pi-ai"; +import { existsSync, readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; import { mirrorProvider } from "pi-provider-utils/providers"; import type { AccountManager } from "./account-manager"; import { createStreamWrapper } from "./stream-wrapper"; @@ -20,6 +23,65 @@ export interface ProviderModelDef { maxTokens: number; } +type OpenAICodexModelOverride = Partial< + Pick< + ProviderModelDef, + "name" | "reasoning" | "input" | "cost" | "contextWindow" | "maxTokens" + > +>; + +function getAgentDir(): string { + return process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent"); +} + +function readOpenAICodexModelOverrides(): Map { + const modelsJsonPath = join(getAgentDir(), "models.json"); + if (!existsSync(modelsJsonPath)) { + return new Map(); + } + try { + const parsed = JSON.parse(readFileSync(modelsJsonPath, "utf-8")) as { + providers?: { + "openai-codex"?: { + modelOverrides?: Record; + }; + }; + }; + return new Map( + Object.entries( + parsed.providers?.["openai-codex"]?.modelOverrides ?? {}, + ), + ); + } catch { + return new Map(); + } +} + +function applyOpenAICodexOverride( + model: ProviderModelDef, + override: OpenAICodexModelOverride | undefined, +): ProviderModelDef { + if (!override) { + return model; + } + return { + ...model, + name: override.name ?? model.name, + reasoning: override.reasoning ?? model.reasoning, + input: override.input ? [...override.input] : model.input, + cost: override.cost + ? { + input: override.cost.input ?? model.cost.input, + output: override.cost.output ?? model.cost.output, + cacheRead: override.cost.cacheRead ?? model.cost.cacheRead, + cacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite, + } + : model.cost, + contextWindow: override.contextWindow ?? model.contextWindow, + maxTokens: override.maxTokens ?? model.maxTokens, + }; +} + export function getOpenAICodexMirror(): { baseUrl: string; models: ProviderModelDef[]; @@ -28,17 +90,23 @@ export function getOpenAICodexMirror(): { if (!mirror) { return { baseUrl: "https://chatgpt.com/backend-api", models: [] }; } + const overrides = readOpenAICodexModelOverrides(); return { baseUrl: mirror.baseUrl, - models: mirror.models.map((m) => ({ - id: m.id, - name: m.name, - reasoning: m.reasoning, - input: [...m.input], - cost: { ...m.cost }, - contextWindow: m.contextWindow, - maxTokens: m.maxTokens, - })), + models: mirror.models.map((m) => + applyOpenAICodexOverride( + { + id: m.id, + name: m.name, + reasoning: m.reasoning, + input: [...m.input], + cost: { ...m.cost }, + contextWindow: m.contextWindow, + maxTokens: m.maxTokens, + }, + overrides.get(m.id), + ), + ), }; } From ad162c47ed89a3ca0c4596698918800bcd947f70 Mon Sep 17 00:00:00 2001 From: Ivan Kolesnikov Date: Sat, 18 Apr 2026 17:45:45 +0400 Subject: [PATCH 2/2] Align account usage display with footer --- commands.ts | 56 +++++++++++++++++++++++++++++++++++------------------ status.ts | 16 +++++++++++++++ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/commands.ts b/commands.ts index ecfbcec..10e9fbf 100644 --- a/commands.ts +++ b/commands.ts @@ -19,9 +19,14 @@ import { getAgentSettingsPath } from "pi-provider-utils/agent-paths"; import { normalizeUnknownError } from "pi-provider-utils/streams"; import type { AccountManager } from "./account-manager"; import { openLoginInBrowser } from "./browser"; -import type { createUsageStatusController } from "./status"; +import { + formatUsageSummaryText, + loadFooterPreferences, + type PercentDisplayMode, + type createUsageStatusController, +} from "./status"; import { type Account, STORAGE_FILE } from "./storage"; -import { formatResetAt, isUsageUntouched } from "./usage"; +import { isUsageUntouched } from "./usage"; const SETTINGS_FILE = getAgentSettingsPath(); const NO_ACCOUNTS_MESSAGE = @@ -116,28 +121,32 @@ function getAccountTags( function formatUsageSummary( accountManager: AccountManager, account: Account, + usageMode: PercentDisplayMode, ): string { - const usage = accountManager.getCachedUsage(account.email); - const primaryUsed = usage?.primary?.usedPercent; - const secondaryUsed = usage?.secondary?.usedPercent; - const primaryReset = usage?.primary?.resetAt; - const secondaryReset = usage?.secondary?.resetAt; - const primaryLabel = - primaryUsed === undefined ? "unknown" : `${Math.round(primaryUsed)}%`; - const secondaryLabel = - secondaryUsed === undefined ? "unknown" : `${Math.round(secondaryUsed)}%`; - return `5h ${primaryLabel} reset:${formatResetAt(primaryReset)} | weekly ${secondaryLabel} reset:${formatResetAt(secondaryReset)}`; + return formatUsageSummaryText( + accountManager.getCachedUsage(account.email), + usageMode, + ); } function formatAccountStatusLine( accountManager: AccountManager, email: string, + usageMode: PercentDisplayMode, ): string { const account = accountManager.getAccount(email); if (!account) return email; const tags = getAccountTags(accountManager, account).join(", "); const suffix = tags ? ` (${tags})` : ""; - return `${account.email}${suffix} - ${formatUsageSummary(accountManager, account)}`; + return `${account.email}${suffix} - ${formatUsageSummary(accountManager, account, usageMode)}`; +} + +async function loadUsageMode(): Promise { + try { + return (await loadFooterPreferences()).usageMode; + } catch { + return "left"; + } } function getSubcommandCompletions(prefix: string): AutocompleteItem[] | null { @@ -278,6 +287,7 @@ async function refreshSingleAccount( ctx: ExtensionCommandContext, accountManager: AccountManager, email: string, + usageMode: PercentDisplayMode, ): Promise { const account = accountManager.getAccount(email); if (!account) { @@ -297,7 +307,7 @@ async function refreshSingleAccount( await accountManager.refreshUsageForAccount(account, { force: true }); ctx.ui.notify( - `refreshed ${formatAccountStatusLine(accountManager, email)}`, + `refreshed ${formatAccountStatusLine(accountManager, email, usageMode)}`, "info", ); } @@ -344,6 +354,7 @@ async function promptForNewAccountIdentifier( async function openAccountManagementPanel( ctx: ExtensionCommandContext, accountManager: AccountManager, + usageMode: PercentDisplayMode, ): Promise { const accounts = accountManager.getAccounts(); @@ -405,7 +416,7 @@ async function openAccountManagementPanel( : "dim"; const secondary = theme.fg( summaryColor, - formatUsageSummary(accountManager, account), + formatUsageSummary(accountManager, account, usageMode), ); return [primary, truncateToWidth(` ${secondary}`, width, "")]; } @@ -585,6 +596,7 @@ async function openAccountManagementFlow( accountManager: AccountManager, statusController: ReturnType, ): Promise { + const usageMode = await loadUsageMode(); while (true) { const accounts = accountManager.getAccounts(); if (accounts.length === 0) { @@ -595,7 +607,7 @@ async function openAccountManagementFlow( continue; } - const result = await openAccountManagementPanel(ctx, accountManager); + const result = await openAccountManagementPanel(ctx, accountManager, usageMode); if (!result) return; if (result.action === "add") { @@ -613,7 +625,7 @@ async function openAccountManagementFlow( } if (result.action === "refresh") { - await refreshSingleAccount(ctx, accountManager, result.email); + await refreshSingleAccount(ctx, accountManager, result.email, usageMode); await statusController.refreshFor(ctx); continue; } @@ -669,8 +681,9 @@ async function runAccountsSubcommand( } if (!ctx.hasUI) { + const usageMode = await loadUsageMode(); const lines = accounts.map((account) => - formatAccountStatusLine(accountManager, account.email), + formatAccountStatusLine(accountManager, account.email, usageMode), ); ctx.ui.notify(lines.join("\n"), "info"); return; @@ -872,7 +885,12 @@ async function runRefreshSubcommand( await openAccountManagementFlow(pi, ctx, accountManager, statusController); return; } - await refreshSingleAccount(ctx, accountManager, rest); + await refreshSingleAccount( + ctx, + accountManager, + rest, + await loadUsageMode(), + ); await statusController.refreshFor(ctx); } diff --git a/status.ts b/status.ts index 12e35e6..f316b32 100644 --- a/status.ts +++ b/status.ts @@ -174,6 +174,22 @@ function formatPercent( return `${Math.round(clampPercent(displayPercent))}% ${mode}`; } +export function formatUsageSummaryText( + usage: CodexUsageSnapshot | undefined, + mode: PercentDisplayMode = "left", +): string { + const primaryDisplay = usedToDisplayPercent(usage?.primary?.usedPercent, mode); + const secondaryDisplay = usedToDisplayPercent( + usage?.secondary?.usedPercent, + mode, + ); + const primaryLabel = + primaryDisplay === undefined ? "unknown" : formatPercent(primaryDisplay, mode); + const secondaryLabel = + secondaryDisplay === undefined ? "unknown" : formatPercent(secondaryDisplay, mode); + return `5h ${primaryLabel} reset:${formatResetAt(usage?.primary?.resetAt)} | weekly ${secondaryLabel} reset:${formatResetAt(usage?.secondary?.resetAt)}`; +} + function formatResetCountdown(resetAt: number | undefined): string | undefined { if (typeof resetAt !== "number" || Number.isNaN(resetAt)) return undefined; const totalSeconds = Math.max(0, Math.round((resetAt - Date.now()) / 1000));