Skip to content

Conversation

@phj1081
Copy link

@phj1081 phj1081 commented Jan 5, 2026

Summary

Adds real-time usage display for Anthropic and OpenAI OAuth accounts in both the status dialog (/status) and the sidebar.

Features

  • Anthropic Usage: Shows 5-hour and 7-day rate limit windows with utilization % and reset times
  • OpenAI Usage: Shows primary/secondary rate limit windows, plan type (Free/Plus/Pro/etc), and credits balance
  • Visual Progress Bars: Color-coded bars (green → yellow → red) based on utilization level
  • Reset Time Display: Human-readable countdown (e.g., "2h 15m", "5d 3h")

Screenshots

Status Dialog (/status or Ctrl+X S)

Anthropic Usage
5h:  ██░░░░░░░░░░░░░░░░░░ 9%   (reset: 2h 15m)
7d:  ████░░░░░░░░░░░░░░░░ 21%  (reset: 5d 3h)

OpenAI Usage (Pro)
5h:  ░░░░░░░░░░░░░░░░░░░░ 0%   (reset: 4h 59m)
7d:  ██████░░░░░░░░░░░░░░ 30%  (reset: 4d 2h)
Credits: $1000.00

Sidebar (collapsible Usage section)

▼ Usage
Anthropic
5h █░░░░░░░░░ 9% (2h 15m)
7d ██░░░░░░░░ 21% (5d 3h)
OpenAI (Pro)
5h ░░░░░░░░░░ 0% (4h 59m)
7d ███░░░░░░░ 30% (4d 2h)

Implementation

New Files

  • src/usage/anthropic.ts: Fetches from api.anthropic.com/api/oauth/usage
  • src/usage/openai.ts: Fetches from chatgpt.com/backend-api/wham/usage

Modified Files

  • dialog-status.tsx: Added usage sections with progress bars
  • sidebar.tsx: Added collapsible Usage section

API Details

Provider Endpoint Auth
Anthropic GET api.anthropic.com/api/oauth/usage OAuth token + anthropic-beta: oauth-2025-04-20
OpenAI GET chatgpt.com/backend-api/wham/usage OAuth Bearer token

Related

Notes

  • Only shows usage when OAuth is configured (falls back gracefully if not)
  • Uses Zod for runtime validation of API responses
  • Sidebar uses compact 10-char progress bar; status dialog uses 20-char bar

…idebar

- Add usage/anthropic.ts: fetch usage from Anthropic OAuth API
- Add usage/openai.ts: fetch usage from OpenAI/ChatGPT backend API
- Update dialog-status.tsx: show usage bars with reset times
- Update sidebar.tsx: show compact usage display with progress bars

Shows rate limit utilization percentages and reset times for:
- Anthropic: 5-hour and 7-day windows
- OpenAI: primary/secondary windows with plan type and credits

Related to anomalyco#6298 (plan usage tracking)
Copilot AI review requested due to automatic review settings January 5, 2026 04:34
@github-actions
Copy link
Contributor

github-actions bot commented Jan 5, 2026

The following comment was made by an LLM, it may be inaccurate:

Summary

I found 1 related PR that may be worth considering:

Related PR:

No Direct Duplicates Found:

The main PR (#6905) appears to be the only one actively addressing OAuth usage display for Anthropic and OpenAI specifically. The searches did not reveal any other open PRs duplicating this exact feature.

Recommendation: The PR is likely ready to proceed, but keep PR #6298 in mind for potential future refactoring to unify the usage tracking patterns across different providers.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds real-time OAuth usage display for Anthropic and OpenAI accounts, showing rate limits, utilization percentages, and reset times in both the status dialog (/status) and sidebar. The implementation introduces two new usage modules that fetch data from provider APIs and integrates them into existing UI components with color-coded progress bars.

Key Changes:

  • Added usage fetching modules for Anthropic and OpenAI OAuth APIs with Zod validation
  • Integrated collapsible usage sections in sidebar and status dialog with visual progress bars
  • Implemented utility functions for formatting durations, reset times, and credits display

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 13 comments.

File Description
packages/opencode/src/usage/openai.ts New module for fetching OpenAI usage data with plan type detection, rate limit windows, and credit balance formatting
packages/opencode/src/usage/anthropic.ts New module for fetching Anthropic usage data with 5-hour and 7-day rate limit windows
packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx Added collapsible Usage section with compact 10-char progress bars for both providers
packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx Added usage display with 20-char progress bars and UsageBar component

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 52 to 71
export function formatResetTime(isoString: string | null): string {
if (!isoString) return "N/A"
const date = new Date(isoString)
const now = new Date()
const diffMs = date.getTime() - now.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMins / 60)

if (diffHours >= 24) {
const days = Math.floor(diffHours / 24)
return `${days}d ${diffHours % 24}h`
}
if (diffHours > 0) {
return `${diffHours}h ${diffMins % 60}m`
}
if (diffMins > 0) {
return `${diffMins}m`
}
return "soon"
}
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatResetTime function does not handle negative time differences (when the reset time is in the past). This could display incorrect values like negative minutes or hours if the timestamp is stale. Consider adding a check to return a more appropriate message like "expired" or "refreshing" for past timestamps.

Copilot uses AI. Check for mistakes.

const usageBar = (percent: number) => {
const width = 10
const filled = Math.round((percent / 100) * width)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usageBar function does not handle negative or values greater than 100. If the API returns invalid percentage values, this could result in incorrect visual representation or runtime errors from negative repeat counts. Consider adding validation to clamp the percent value between 0 and 100.

Suggested change
const filled = Math.round((percent / 100) * width)
const clampedPercent = Math.max(0, Math.min(100, percent))
const filled = Math.round((clampedPercent / 100) * width)

Copilot uses AI. Check for mistakes.
Comment on lines 10 to 19
function UsageBar(props: { percent: number; fg: RGBA; bgMuted: RGBA }) {
const width = 20
const filled = Math.round((props.percent / 100) * width)
const empty = width - filled
return (
<text>
<span style={{ fg: props.fg }}>{"█".repeat(filled)}</span>
<span style={{ fg: props.bgMuted }}>{"░".repeat(empty)}</span>
</text>
)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UsageBar component does not validate that percent is within the valid range (0-100). If the API returns invalid percentage values, this could result in incorrect visual representation or runtime errors from negative repeat counts. Consider adding validation to clamp the percent value between 0 and 100.

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 142
export function getPlanDisplayName(planType: PlanType): string {
const names: Record<PlanType, string> = {
free: "Free",
plus: "Plus",
pro: "Pro",
team: "Team",
business: "Business",
enterprise: "Enterprise",
edu: "Education",
education: "Education",
guest: "Guest",
go: "Go",
free_workspace: "Free Workspace",
quorum: "Quorum",
k12: "K-12",
}
return names[planType] || planType
}

export async function fetch(): Promise<UsageData | null> {
const auth = await Auth.get("openai")
if (!auth || auth.type !== "oauth") {
return null
}

try {
const response = await globalThis.fetch("https://chatgpt.com/backend-api/wham/usage", {
method: "GET",
headers: {
Authorization: `Bearer ${auth.access}`,
Accept: "application/json",
"User-Agent": "opencode-cli",
},
})

if (!response.ok) {
console.error(`OpenAI usage API error: ${response.status}`)
return null
}

const data = await response.json()
const parsed = UsageData.safeParse(data)
if (!parsed.success) {
console.error("Failed to parse OpenAI usage data:", parsed.error)
return null
}

return parsed.data
} catch (error) {
console.error("Failed to fetch OpenAI usage:", error)
return null
}
}

export function formatWindowDuration(seconds: number): string {
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)

if (days >= 1) {
return `${days}d`
}
if (hours >= 1) {
return `${hours}h`
}
return `${minutes}m`
}

export function formatResetTime(unixTimestamp: number): string {
const date = new Date(unixTimestamp * 1000)
const now = new Date()
const diffMs = date.getTime() - now.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMins / 60)

if (diffHours >= 24) {
const days = Math.floor(diffHours / 24)
return `${days}d ${diffHours % 24}h`
}
if (diffHours > 0) {
return `${diffHours}h ${diffMins % 60}m`
}
if (diffMins > 0) {
return `${diffMins}m`
}
return "soon"
}

export function formatCredits(balance: string | number | null | undefined): string {
if (balance === null || balance === undefined) {
return "N/A"
}
const num = typeof balance === "string" ? parseFloat(balance) : balance
return `$${num.toFixed(2)}`
}
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These utility functions (getPlanDisplayName, formatWindowDuration, formatResetTime, formatCredits) lack documentation. Adding JSDoc comments would help other developers understand their purpose, parameters, and return values, especially since they handle various edge cases and formatting logic.

Copilot uses AI. Check for mistakes.
Authorization: `Bearer ${auth.access}`,
Accept: "application/json",
"User-Agent": "opencode-cli",
},
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetch calls do not include a timeout, which could cause the UI to hang indefinitely if the API endpoints are unresponsive. Consider adding a timeout using AbortSignal.timeout() similar to how it's used elsewhere in the codebase (e.g., in provider/models.ts with a 10-second timeout).

Suggested change
},
},
signal: AbortSignal.timeout(10_000),

Copilot uses AI. Check for mistakes.
Comment on lines 36 to 40
const getUsageColor = (percent: number) => {
if (percent >= 90) return theme.error
if (percent >= 70) return theme.warning
return theme.success
}
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getUsageColor function logic is duplicated between sidebar.tsx and dialog-status.tsx. Consider extracting this into a shared utility function to maintain consistency and reduce code duplication.

Copilot uses AI. Check for mistakes.
Comment on lines 33 to 34
const [anthropicUsage] = createResource(() => AnthropicUsage.fetch(), { initialValue: null })
const [openaiUsage] = createResource(() => OpenAIUsage.fetch(), { initialValue: null })
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createResource calls will fetch usage data on every component render without any caching mechanism. This could result in excessive API calls to OpenAI and Anthropic endpoints. Consider implementing caching similar to PR #6298's approach with a 60-second TTL, or adding a refetch mechanism tied to specific user actions rather than continuous polling.

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 36
const [anthropicUsage, { refetch: refetchAnthropicUsage }] = createResource(
() => AnthropicUsage.fetch(),
{ initialValue: null }
)

const [openaiUsage, { refetch: refetchOpenAIUsage }] = createResource(
() => OpenAIUsage.fetch(),
{ initialValue: null }
)
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createResource calls will fetch usage data on every component render without any caching mechanism. This could result in excessive API calls to OpenAI and Anthropic endpoints. Consider implementing caching similar to PR #6298's approach with a 60-second TTL, or adding a refresh mechanism that's manually triggered rather than automatic on each render.

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 33
const [anthropicUsage, { refetch: refetchAnthropicUsage }] = createResource(
() => AnthropicUsage.fetch(),
{ initialValue: null }
)

const [openaiUsage, { refetch: refetchOpenAIUsage }] = createResource(
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refetch functions are destructured but never used. Either implement a refresh mechanism (e.g., a button or periodic refresh) or remove these unused destructured variables to keep the code clean.

Suggested change
const [anthropicUsage, { refetch: refetchAnthropicUsage }] = createResource(
() => AnthropicUsage.fetch(),
{ initialValue: null }
)
const [openaiUsage, { refetch: refetchOpenAIUsage }] = createResource(
const [anthropicUsage] = createResource(
() => AnthropicUsage.fetch(),
{ initialValue: null }
)
const [openaiUsage] = createResource(

Copilot uses AI. Check for mistakes.
Comment on lines 116 to 134
export function formatResetTime(unixTimestamp: number): string {
const date = new Date(unixTimestamp * 1000)
const now = new Date()
const diffMs = date.getTime() - now.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMins / 60)

if (diffHours >= 24) {
const days = Math.floor(diffHours / 24)
return `${days}d ${diffHours % 24}h`
}
if (diffHours > 0) {
return `${diffHours}h ${diffMins % 60}m`
}
if (diffMins > 0) {
return `${diffMins}m`
}
return "soon"
}
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatResetTime function does not handle negative time differences (when the reset time is in the past). This could display incorrect values like negative minutes or hours if the timestamp is stale. Consider adding a check to return a more appropriate message like "expired" or "refreshing" for past timestamps.

Copilot uses AI. Check for mistakes.
@phj1081 phj1081 closed this Jan 5, 2026
@phj1081 phj1081 reopened this Jan 5, 2026
- Add negative time handling in formatResetTime (return 'refreshing')
- Add clamp for percent values in usage bars (0-100)
- Add 10s timeout to fetch calls (AbortSignal.timeout)
- Add NaN handling in formatCredits
- Extract shared utils (getUsageColor, clampPercent, usageBarString)
- Add refetch on message change for sidebar usage display
- Remove unused refetch destructuring in dialog-status
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant