opencode icon indicating copy to clipboard operation
opencode copied to clipboard

feat: Add (z.ai) plan usage tracking in the sidebar

Open albu opened this issue 3 weeks ago • 0 comments

Problem

When using LLM providers with quota limits (like zai-coding-plan), users have no visibility into their remaining quota or when it resets. This makes it difficult to track usage and avoid unexpected service interruptions.

Solution

Added plan usage display to the TUI sidebar that shows:

  • Current usage percentage (e.g., "65% used")
  • Time until quota resets (e.g., "Resets in 2h 15m")

Implementation

Core abstraction (src/provider/plan-usage.ts)

Generic PlanUsage interface that all providers implement:

export interface PlanUsage {
  used: number
  total: number
  resetAt?: Date
  percentage?: number  // Provider-supplied percentage if more accurate
}

Features:

  • Registry pattern - Provider handlers register themselves via registerProvider(id, handler)
  • Built-in caching - 60s TTL to avoid excessive API calls
  • Helpers - getPercentage(), formatResetTime()

Provider implementation (src/provider/plan-usage/zai.ts)

  • Fetches quota from /api/monitor/usage/quota/limit endpoint
  • Handles zai-specific response format with multiple limit types
  • Only displays TOKENS_LIMIT (ignores time-based limits)
  • Registers itself as zai-coding-plan handler

UI integration (sidebar.tsx)

  • Shows plan usage in session sidebar below context info
  • Auto-refreshes when new messages are exchanged (user or assistant)

Adding support for other providers

The pattern uses side-effect imports. The core module imports provider modules, which register themselves on load.

Step 1: Add import to src/provider/plan-usage.ts:

// Load built-in providers
import("./plan-usage/zai")
import("./plan-usage/your-provider")  // ← Add this

Step 2: Create src/provider/plan-usage/your-provider.ts:

import { registerProvider, type PlanUsageHandler } from "../plan-usage"

const handler: PlanUsageHandler = async ({ token, baseURL, timeout }) => {
  const response = await fetch(`${baseURL}/your-endpoint`, {
    headers: { Authorization: token },
    signal: AbortSignal.timeout(timeout)
  })

  if (!response.ok) {
    return { error: `HTTP ${response.status}` }
  }

  const data = await response.json()
  return {
    planUsage: {
      used: data.used,
      total: data.limit,
      resetAt: new Date(data.resetsAt),
    }
  }
}

registerProvider("your-provider-id", handler)  // ← Executes on import

The handler receives:

  • token - API key from auth
  • baseURL - Provider's base URL from config
  • timeout - Request timeout in ms (default 5000)

And returns:

{ planUsage: PlanUsage } | { error: string }

albu avatar Dec 28 '25 13:12 albu