[codex] add OpenCode TUI prompt status#146
Conversation
📝 WalkthroughWalkthroughThis PR adds TUI (terminal user interface) support for displaying Codex quota status with new modules for quota snapshot caching, usage payload parsing, and TUI-specific formatting. The existing Codex limits tool is refactored to delegate usage concerns to a centralized module, and installation/dependency management is updated. Changes
Sequence Diagram(s)sequenceDiagram
participant TUI as TUI Plugin
participant Cache as Quota Cache
participant Usage as Usage Module
participant API as Codex API
participant Storage as Account Storage
TUI->>Cache: Load cached snapshot
alt Snapshot found
TUI->>TUI: Render with cached quota
else Cache miss or stale
TUI->>Usage: Ensure access token (refresh if needed)
Usage->>Storage: Check/update credentials
Storage-->>Usage: Account data
Usage->>API: GET /wham/usage
API-->>Usage: Usage payload + headers
Usage->>Usage: Parse payload → limits
Usage->>Cache: Write snapshot to disk
Usage-->>TUI: Parsed quota limits
TUI->>TUI: Render with new quota
end
Note over TUI: Periodic poll & debounced event triggers
sequenceDiagram
participant Client as Account Switch
participant TUI as TUI Plugin
participant Cache as Quota Cache
Client->>TUI: Account selection persisted
TUI->>Cache: Clear snapshot (try/catch wrapped)
Cache-->>TUI: Cleared or error logged
TUI->>TUI: Return success (cache clear non-blocking)
Note over Client,Cache: Next quota fetch recomputes from API
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (9)
scripts/install-oc-codex-multi-auth-core.js (1)
144-152: LGTM — merge helper is correct and consistent.
mergeTuiConfigcorrectly preserves unknown top-level keys (notablyplugin_enabled), backfills a default$schema, and routes the plugin array through the samenormalizePluginListused foropencode.json, so legacyoc-chatgpt-multi-authentries,@versionpins, and absolutefile:///node_modulespaths are all collapsed to the canonicaloc-codex-multi-authentry. Using the same normalizer here is sound because the TUI plugin is a named export of the same package.Optional micro-refactor: the
existing+nextpair performs two spreads when one would do. Feel free to ignore.♻️ Optional simplification
function mergeTuiConfig(existingConfig) { - const existing = isPlainObject(existingConfig) ? { ...existingConfig } : {}; - const next = { ...existing }; + const next = isPlainObject(existingConfig) ? { ...existingConfig } : {}; if (typeof next.$schema !== "string" || !next.$schema.trim()) { next.$schema = "https://opencode.ai/tui.json"; } - next.plugin = normalizePluginList(existing.plugin); + next.plugin = normalizePluginList(next.plugin); return next; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/install-oc-codex-multi-auth-core.js` around lines 144 - 152, The function mergeTuiConfig currently creates two spreads (const existing = ... ? { ...existingConfig } : {}; const next = { ...existing };) which is redundant; simplify by eliminating one spread and deriving next directly from existingConfig (or from existing when preserving the plain-object guard) while keeping the same behavior: preserve unknown top-level keys, backfill $schema when missing, and pass the plugin field through normalizePluginList; update mergeTuiConfig to reference the same symbols ($schema, plugin, normalizePluginList) but remove the unnecessary double-copy.test/tui-refresh-events.test.ts (2)
32-32: Hardcoded Windows paths may read odd on Unix CI.
path: { cwd: "C:\\repo", root: "C:\\repo" }is fine because the field isn't asserted on, but using a neutral path (e.g./repo) keeps fixtures platform-agnostic and matches typical CI runners.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/tui-refresh-events.test.ts` at line 32, The test fixture in test/tui-refresh-events.test.ts uses hardcoded Windows-style paths in the object property path (path: { cwd: "C:\\repo", root: "C:\\repo" }); change those to a platform-agnostic path (e.g., cwd: "/repo", root: "/repo") so the fixture reads naturally on Unix CI and avoids platform-specific artifacts while keeping behavior identical since the field isn't asserted on.
17-123: Consider adding coverage forsession.idle,session.error, and toolerrorstates.The implementation of
shouldRefreshQuotaForEvent(per the context snippet) also returnstrueforsession.idle,session.error, andmessage.part.updatedtool parts withstate.status === "error". The current tests only exercisesession.status(idle/busy), step-finish parts, and toolcompleted. A regression that, say, flippedsession.errorto not refresh would slip through.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/tui-refresh-events.test.ts` around lines 17 - 123, Add tests that assert shouldRefreshQuotaForEvent returns true for session statuses of type "idle" and "error" and for tool parts whose state.status === "error"; specifically, in the same test suite that contains the existing cases, create a session.status event with properties.status.type = "idle" and another with properties.status.type = "error" and assert shouldRefreshQuotaForEvent(...) === true, and create a message.part.updated event for a tool part where part.state.status === "error" (mirroring the existing toolCompleted shape) and assert it returns true; reference the shouldRefreshQuotaForEvent function and reuse the event object shapes (session.status and message.part.updated) used elsewhere in the file so the new assertions cover these missing branches.lib/tools/codex-switch.ts (1)
150-156: Duplicated cache-clear logic with inconsistent log level.This block re-implements the same try/catch pattern as
clearPromptQuotaCacheinindex.ts(lines 602–610), but logs atwarnlevel here vsdebugthere. Two concerns:
- Log-level divergence: the same failure is treated as a warning in one path and noise in the other — consumers can't reason about severity consistently.
- Duplication: both call sites have identical error-swallowing intent. Consider exposing
clearPromptQuotaCache(or a thin helper) throughToolContextso both thecodex-switchtool and theaccount.selectevent handler share one implementation.♻️ Proposed fix: align log level and delegate to a shared helper
- try { - await clearTuiQuotaSnapshot(); - } catch (cacheError) { - logWarn("Failed to clear TUI quota cache after account switch", { - error: String(cacheError), - }); - } + // Delegate to the shared helper exposed via ToolContext to keep the + // tool path and the account.select event handler in sync. + await ctx.clearPromptQuotaCache?.();Then expose
clearPromptQuotaCachethroughToolContextinindex.tsandlib/tools/index.ts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/tools/codex-switch.ts` around lines 150 - 156, The try/catch in codex-switch.ts that calls clearTuiQuotaSnapshot duplicates the error-swallowing logic from clearPromptQuotaCache (index.ts) and uses a different log level; remove the duplicate by delegating to a single shared function: expose clearPromptQuotaCache (or a thin wrapper) on ToolContext so the codex-switch tool and the account.select handler both call ToolContext.clearPromptQuotaCache, and ensure both callers use the same log level (change the codex-switch call to rely on the helper's debug-level logging rather than logging a warn locally). Update codex-switch.ts to call the exposed ToolContext method and delete the local try/catch around clearTuiQuotaSnapshot.package.json (1)
5-16: Consider mirroringmain/typesvia the exports map only.Given the
exportsfield now authoritatively defines the.entry, the top-levelmain/typesfields become redundant for Node resolvers that honorexports, but are still useful as a fallback for older tooling/bundlers. No change required — flagging so you can decide whether to keep both or drop one pair once minimum consumer versions are known.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 5 - 16, The package.json currently has top-level "main" and "types" alongside an "exports" map that defines the "." entry; update package.json so the top-level "main" and "types" either mirror the same targets as the "." export (i.e., set "main" -> "./dist/index.js" and "types" -> "./dist/index.d.ts" to match the exports) or remove the top-level "main"/"types" pair entirely if you decide to rely solely on the "exports" map, making sure the "./tui" export remains unchanged; reference the "main", "types", "exports", "." and "./tui" keys when applying the change.index.ts (1)
602-631: Helpers look correct; minor log-level consistency suggestion.
clearPromptQuotaCacheandrecordPromptQuotaHeadersboth defensively swallow errors and log atdebug, which is appropriate for a non-critical telemetry path. Note thatlib/tools/codex-switch.ts(line 152–155) logs the same clear failure atwarn— please pick one level and align both sites. See my comment oncodex-switch.tsfor a broader DRY suggestion.Also,
TuiQuotaAccountis declared viaParameters<typeof createUsageAccountFingerprint>[0] & { index: number; email?: string; accountLabel?: string }. IfcreateUsageAccountFingerprint's parameter already includesaccountLabel, the intersection is redundant; if it doesn't, widening here is fine. Worth a quick check.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@index.ts` around lines 602 - 631, The helper functions clearPromptQuotaCache and recordPromptQuotaHeaders currently log errors with logDebug while the similar clear logic in codex-switch (clearFailure path) uses logWarn; choose one level and make them consistent (e.g., change logDebug -> logWarn in clearPromptQuotaCache/recordPromptQuotaHeaders or change the codex-switch usage to logDebug) so both sites use the same severity; update the calls in clearPromptQuotaCache and recordPromptQuotaHeaders (and the corresponding clear path in codex-switch) accordingly. Also inspect the TuiQuotaAccount type (declared as Parameters<typeof createUsageAccountFingerprint>[0] & { index: number; email?: string; accountLabel?: string }) and remove redundant email/accountLabel from the intersection if createUsageAccountFingerprint already includes them, or keep the widening only if those props are missing. Ensure you update only the referenced functions/types: clearPromptQuotaCache, recordPromptQuotaHeaders, and the TuiQuotaAccount declaration/createUsageAccountFingerprint usage.lib/tui-status.ts (1)
279-301:formatResetduplicatesformatUsageResetfromlib/codex-usage.ts.This helper is identical to
formatUsageReset(lib/codex-usage.ts Lines 112–137). Import the canonical version instead of maintaining two copies — divergence here would cause inconsistency between the tools-tab reset strings and the TUI details dialog strings.♻️ Suggested change
import type { Config } from "@opencode-ai/sdk/v2"; +import { formatUsageReset } from "./codex-usage.js"; @@ -function formatReset(resetAtMs: number | undefined): string | undefined { - if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0) { - return undefined; - } - const date = new Date(resetAtMs); - if (!Number.isFinite(date.getTime())) return undefined; - const now = new Date(); - const sameDay = - now.getFullYear() === date.getFullYear() && - now.getMonth() === date.getMonth() && - now.getDate() === date.getDate(); - const time = date.toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); - if (sameDay) return time; - const day = date.toLocaleDateString(undefined, { - month: "short", - day: "2-digit", - }); - return `${time} on ${day}`; -} +const formatReset = formatUsageReset;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/tui-status.ts` around lines 279 - 301, The function formatReset in lib/tui-status.ts duplicates formatUsageReset from lib/codex-usage.ts; remove the local formatReset implementation and import the canonical formatUsageReset from lib/codex-usage.ts, then replace usages of formatReset with formatUsageReset (or re-export/alias it as formatReset if the local name must be preserved) to avoid duplication and keep reset-string formatting consistent across the codebase.tui.ts (1)
288-295: 1s account polling reads accounts from disk every second.
ACCOUNT_POLL_INTERVAL_MS = 1_000drivesloadAccounts()on every tick for the lifetime of the TUI session (~3600 disk reads/hour per active slot). OS page cache largely absorbs this, but it's still wasted work compared to an event-driven path or a slower poll cadence (e.g., 5–10s would still feel responsive for account-switch detection, and the explicitcodex.switchflow could additionally clear/touch the snapshot file to trigger faster convergence).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tui.ts` around lines 288 - 295, The 1s polling loop driven by ACCOUNT_POLL_INTERVAL_MS causes frequent disk reads via resolveActiveQuotaFingerprint()/loadAccounts(); change the polling to a less aggressive default (e.g., 5_000–10_000 ms) or make ACCOUNT_POLL_INTERVAL_MS configurable, and prefer an event-driven approach (use a filesystem watcher or trigger a touch from the explicit codex.switch flow) so resolveActiveQuotaFingerprint() is only run when necessary; update the setInterval that creates accountInterval and the code paths that call refresh()/setQuota() to rely on the new cadence or the explicit trigger.lib/tui-quota-cache.ts (1)
97-114: DeduplicateformatWindowLabel/getLeftPercentwithlib/codex-usage.ts.These helpers are verbatim copies of
formatUsageWindowLabel(lib/codex-usage.ts Lines 97–110) andgetUsageLeftPercent(lib/codex-usage.ts Lines 89–95). Keeping two implementations in sync is easy to get wrong; one should import from the shared module.♻️ Suggested deduplication
import { renameWithWindowsRetry } from "./storage/atomic-write.js"; +import { + formatUsageWindowLabel, + getUsageLeftPercent, +} from "./codex-usage.js"; import type { CompactQuotaLimit } from "./tui-status.js"; @@ -function formatWindowLabel(windowMinutes: number | undefined): string { - if ( - !windowMinutes || - !Number.isFinite(windowMinutes) || - windowMinutes <= 0 - ) { - return "quota"; - } - if (windowMinutes % 1440 === 0) return `${windowMinutes / 1440}d`; - if (windowMinutes % 60 === 0) return `${windowMinutes / 60}h`; - return `${windowMinutes}m`; -} - -function getLeftPercent(usedPercent: number | undefined): number | null { - return typeof usedPercent === "number" && Number.isFinite(usedPercent) - ? Math.max(0, Math.min(100, Math.round(100 - usedPercent))) - : null; -} +const getLeftPercent = (usedPercent: number | undefined): number | null => + getUsageLeftPercent(usedPercent) ?? null;Then replace the
formatWindowLabelcall site on Line 138 withformatUsageWindowLabel.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/tui-quota-cache.ts` around lines 97 - 114, Remove the duplicated helpers formatWindowLabel and getLeftPercent and instead import the canonical functions formatUsageWindowLabel and getUsageLeftPercent from the codex-usage module; update all local call sites that currently use formatWindowLabel to call formatUsageWindowLabel and those using getLeftPercent to call getUsageLeftPercent, and ensure the imports are added to the top of the file and any local references/variable names adjusted accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@index.ts`:
- Line 2147: The call to recordPromptQuotaHeaders is awaiting a function that
already handles its own errors, adding unnecessary latency to the request hot
path; remove the await and invoke it in a fire-and-forget manner (ensure the
returned promise is handled to avoid unhandled rejections) so responses aren't
blocked by disk I/O. Also update writeTuiQuotaSnapshot to reduce race/overwrite
noise under concurrency by adding a short debounce/throttle or "skip-if-recent"
guard (use an in-memory last-write timestamp keyed by account or global) so
rapid parallel requests don't each perform an atomic write and cause
last-write-wins churn.
---
Nitpick comments:
In `@index.ts`:
- Around line 602-631: The helper functions clearPromptQuotaCache and
recordPromptQuotaHeaders currently log errors with logDebug while the similar
clear logic in codex-switch (clearFailure path) uses logWarn; choose one level
and make them consistent (e.g., change logDebug -> logWarn in
clearPromptQuotaCache/recordPromptQuotaHeaders or change the codex-switch usage
to logDebug) so both sites use the same severity; update the calls in
clearPromptQuotaCache and recordPromptQuotaHeaders (and the corresponding clear
path in codex-switch) accordingly. Also inspect the TuiQuotaAccount type
(declared as Parameters<typeof createUsageAccountFingerprint>[0] & { index:
number; email?: string; accountLabel?: string }) and remove redundant
email/accountLabel from the intersection if createUsageAccountFingerprint
already includes them, or keep the widening only if those props are missing.
Ensure you update only the referenced functions/types: clearPromptQuotaCache,
recordPromptQuotaHeaders, and the TuiQuotaAccount
declaration/createUsageAccountFingerprint usage.
In `@lib/tools/codex-switch.ts`:
- Around line 150-156: The try/catch in codex-switch.ts that calls
clearTuiQuotaSnapshot duplicates the error-swallowing logic from
clearPromptQuotaCache (index.ts) and uses a different log level; remove the
duplicate by delegating to a single shared function: expose
clearPromptQuotaCache (or a thin wrapper) on ToolContext so the codex-switch
tool and the account.select handler both call ToolContext.clearPromptQuotaCache,
and ensure both callers use the same log level (change the codex-switch call to
rely on the helper's debug-level logging rather than logging a warn locally).
Update codex-switch.ts to call the exposed ToolContext method and delete the
local try/catch around clearTuiQuotaSnapshot.
In `@lib/tui-quota-cache.ts`:
- Around line 97-114: Remove the duplicated helpers formatWindowLabel and
getLeftPercent and instead import the canonical functions formatUsageWindowLabel
and getUsageLeftPercent from the codex-usage module; update all local call sites
that currently use formatWindowLabel to call formatUsageWindowLabel and those
using getLeftPercent to call getUsageLeftPercent, and ensure the imports are
added to the top of the file and any local references/variable names adjusted
accordingly.
In `@lib/tui-status.ts`:
- Around line 279-301: The function formatReset in lib/tui-status.ts duplicates
formatUsageReset from lib/codex-usage.ts; remove the local formatReset
implementation and import the canonical formatUsageReset from
lib/codex-usage.ts, then replace usages of formatReset with formatUsageReset (or
re-export/alias it as formatReset if the local name must be preserved) to avoid
duplication and keep reset-string formatting consistent across the codebase.
In `@package.json`:
- Around line 5-16: The package.json currently has top-level "main" and "types"
alongside an "exports" map that defines the "." entry; update package.json so
the top-level "main" and "types" either mirror the same targets as the "."
export (i.e., set "main" -> "./dist/index.js" and "types" -> "./dist/index.d.ts"
to match the exports) or remove the top-level "main"/"types" pair entirely if
you decide to rely solely on the "exports" map, making sure the "./tui" export
remains unchanged; reference the "main", "types", "exports", "." and "./tui"
keys when applying the change.
In `@scripts/install-oc-codex-multi-auth-core.js`:
- Around line 144-152: The function mergeTuiConfig currently creates two spreads
(const existing = ... ? { ...existingConfig } : {}; const next = { ...existing
};) which is redundant; simplify by eliminating one spread and deriving next
directly from existingConfig (or from existing when preserving the plain-object
guard) while keeping the same behavior: preserve unknown top-level keys,
backfill $schema when missing, and pass the plugin field through
normalizePluginList; update mergeTuiConfig to reference the same symbols
($schema, plugin, normalizePluginList) but remove the unnecessary double-copy.
In `@test/tui-refresh-events.test.ts`:
- Line 32: The test fixture in test/tui-refresh-events.test.ts uses hardcoded
Windows-style paths in the object property path (path: { cwd: "C:\\repo", root:
"C:\\repo" }); change those to a platform-agnostic path (e.g., cwd: "/repo",
root: "/repo") so the fixture reads naturally on Unix CI and avoids
platform-specific artifacts while keeping behavior identical since the field
isn't asserted on.
- Around line 17-123: Add tests that assert shouldRefreshQuotaForEvent returns
true for session statuses of type "idle" and "error" and for tool parts whose
state.status === "error"; specifically, in the same test suite that contains the
existing cases, create a session.status event with properties.status.type =
"idle" and another with properties.status.type = "error" and assert
shouldRefreshQuotaForEvent(...) === true, and create a message.part.updated
event for a tool part where part.state.status === "error" (mirroring the
existing toolCompleted shape) and assert it returns true; reference the
shouldRefreshQuotaForEvent function and reuse the event object shapes
(session.status and message.part.updated) used elsewhere in the file so the new
assertions cover these missing branches.
In `@tui.ts`:
- Around line 288-295: The 1s polling loop driven by ACCOUNT_POLL_INTERVAL_MS
causes frequent disk reads via resolveActiveQuotaFingerprint()/loadAccounts();
change the polling to a less aggressive default (e.g., 5_000–10_000 ms) or make
ACCOUNT_POLL_INTERVAL_MS configurable, and prefer an event-driven approach (use
a filesystem watcher or trigger a touch from the explicit codex.switch flow) so
resolveActiveQuotaFingerprint() is only run when necessary; update the
setInterval that creates accountInterval and the code paths that call
refresh()/setQuota() to rely on the new cadence or the explicit trigger.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9803d516-e98c-40fc-b46a-6586c471bf00
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (16)
eslint.config.jsindex.tslib/codex-usage.tslib/tools/codex-limits.tslib/tools/codex-switch.tslib/tui-quota-cache.tslib/tui-status.tspackage.jsonscripts/install-oc-codex-multi-auth-core.jstest/codex-usage.test.tstest/install-oc-codex-multi-auth.test.tstest/tui-quota-cache.test.tstest/tui-refresh-events.test.tstest/tui-status.test.tstsconfig.jsontui.ts
Summary
Validation
Summary by CodeRabbit
New Features
Tests
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
this pr adds a new
tui.tsplugin export that displays real-time codex quota in the opencodesession_prompt_rightslot, backed by a shared disk cache (tui-quota-cache.ts) updated from response headers on every api call. inline usage logic fromcodex-limits.tsis cleanly extracted intolib/codex-usage.tsand reused by both paths.Confidence Score: 5/5
safe to merge — all remaining findings are p2 style/cleanup; no correctness bugs found
codex-usage.ts extraction is faithful, atomic writes use the established windows-retry helper, the header parse path is fire-and-forget with full error suppression, and the duplicate function copies are cosmetic
tui.ts (1Hz disk polling, module-level inFlightRefresh, missing integration test coverage) and lib/tui-quota-cache.ts (formatWindowLabel duplication)
Important Files Changed
Sequence Diagram
sequenceDiagram participant API as Codex API participant idx as index.ts (plugin) participant cache as tui-quota-cache.ts participant tui as tui.ts (TUI plugin) participant slot as session_prompt_right slot idx->>API: fetch (completion request) API-->>idx: response + x-codex-* headers idx->>cache: parseTuiQuotaSnapshotFromHeaders() cache-->>idx: TuiQuotaSnapshot idx->>cache: writeTuiQuotaSnapshot() [atomic rename, windows retry] tui->>tui: pollSharedQuotaCache() [1Hz] tui->>cache: readTuiQuotaSnapshot() cache-->>tui: TuiQuotaSnapshot (fingerprint matched) tui->>slot: applyQuota() then formatPromptStatusText() tui->>API: refreshQuotaStatusInner() [5min or event-driven] API-->>tui: UsagePayload tui->>cache: writeTuiQuotaSnapshot() [source: usage] tui->>slot: update prompt-right text + fg color (tone)Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "fix: avoid blocking on quota cache write..." | Re-trigger Greptile