|
| 1 | +--- |
| 2 | +shaping: true |
| 3 | +--- |
| 4 | + |
| 5 | +# Multi-Provider Sessions — Shaping |
| 6 | + |
| 7 | +Handle session usage tracking when users can switch between API providers (Anthropic, OpenAI, etc.) and account types (API keys vs OAuth plans). |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Frame |
| 12 | + |
| 13 | +### Source |
| 14 | + |
| 15 | +> how should we handle the session feature with opencode? with codex it is a single provider. with opencode users could be using api or various different providers. |
| 16 | +
|
| 17 | +Screenshot shows "Session · Resets 2 hours" and "Weekly · Resets 4 days" usage meters at the bottom of the UI. |
| 18 | + |
| 19 | +### Problem |
| 20 | + |
| 21 | +Codex assumes a single provider (OpenAI) with a fixed rate limit structure (session + weekly windows). OpenCode supports multiple providers (Anthropic, OpenAI, OpenRouter, etc.) with different billing models: |
| 22 | + |
| 23 | +1. **OAuth-based plans** (e.g., Anthropic Max, OpenAI ChatGPT) — have rate limit windows (session/weekly) |
| 24 | +2. **API keys** — pay-per-use, no rate limits, just credit balance |
| 25 | +3. **Self-hosted** — no limits at all |
| 26 | + |
| 27 | +The current UI shows "Session" and "Weekly" meters assuming everyone has time-based rate limits. This is incorrect for API key users and multi-provider setups. |
| 28 | + |
| 29 | +### Outcome |
| 30 | + |
| 31 | +Usage display that accurately reflects the user's actual billing model per provider. API key users see spend/credits. OAuth users see rate limits. Multi-provider users see usage for their active provider. |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## Requirements (R) |
| 36 | + |
| 37 | +| ID | Requirement | Status | |
| 38 | +|----|-------------|--------| |
| 39 | +| R0 | Show usage information relevant to the user's current billing model | Core goal | |
| 40 | +| R1 | API key users see credit balance or spend, not rate limit windows | Must-have | |
| 41 | +| R2 | OAuth/plan users see rate limit windows (session/weekly) when available | Must-have | |
| 42 | +| R3 | Usage display updates when user switches providers or models | Must-have | |
| 43 | +| R4 | No misleading UI — don't show "Session: 0%" to API key users | Must-have | |
| 44 | +| R5 | Support providers that don't expose usage data at all (graceful degradation) | Must-have | |
| 45 | +| R6 | Usage is scoped to the active provider, not aggregated across all providers | Undecided | |
| 46 | +| R7 | Token usage per-turn/per-thread still works regardless of billing model | Must-have | |
| 47 | +| R8 | Local usage tracking (JSONL scanning) continues to work for historical view | Nice-to-have | |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## Shapes |
| 52 | + |
| 53 | +### CURRENT: Single-provider rate limits |
| 54 | + |
| 55 | +| Part | Mechanism | |
| 56 | +|------|-----------| |
| 57 | +| **CUR1** | `RateLimitSnapshot` has `primary` (session) and `secondary` (weekly) windows | |
| 58 | +| **CUR2** | `usageLabels.ts` computes percentages assuming both windows exist | |
| 59 | +| **CUR3** | UI always shows "Session" and "Weekly" meters | |
| 60 | +| **CUR4** | `account/rateLimits/updated` event pushes updates | |
| 61 | +| **CUR5** | `AccountSnapshot.type` distinguishes `chatgpt` vs `apikey` but UI treats them the same | |
| 62 | +| **CUR6** | `CreditsSnapshot` exists but is secondary to rate limits | |
| 63 | + |
| 64 | +### A: Provider-aware usage display |
| 65 | + |
| 66 | +The usage display adapts based on the active provider's billing model. Rate limit windows shown for OAuth plans, credit balance for API keys, nothing for self-hosted. |
| 67 | + |
| 68 | +| Part | Mechanism | |
| 69 | +|------|-----------| |
| 70 | +| **A1** | `AccountSnapshot` gains `billingModel: "rate_limited" | "pay_per_use" | "unlimited"` derived from provider auth method | |
| 71 | +| **A2** | `usageLabels.ts` returns different label shapes based on billing model | |
| 72 | +| **A3** | UI conditionally renders rate limit meters OR credit balance OR nothing | |
| 73 | +| **A4** | When user switches provider/model, re-fetch usage info for new provider | |
| 74 | +| **A5** | `RateLimitSnapshot` becomes optional — null for pay-per-use and unlimited | |
| 75 | +| **A6** | `CreditsSnapshot` expanded to show balance, spend-this-session, and cost-per-token hints | |
| 76 | + |
| 77 | +### B: Unified usage abstraction |
| 78 | + |
| 79 | +Abstract all billing models into a single "usage" concept that the UI displays uniformly. |
| 80 | + |
| 81 | +| Part | Mechanism | |
| 82 | +|------|-----------| |
| 83 | +| **B1** | Normalize all billing models into `UsageSnapshot { percent?: number, label: string, sublabel?: string }` | |
| 84 | +| **B2** | Rate limits → percent; API credits → "X credits remaining"; Unlimited → "Unlimited" | |
| 85 | +| **B3** | Single meter component that displays whatever the backend provides | |
| 86 | +| **B4** | Backend computes the normalized snapshot, frontend just renders | |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +## Open Questions |
| 91 | + |
| 92 | +| # | Question | Status | |
| 93 | +|---|----------|--------| |
| 94 | +| Q1 | Does OpenCode's REST API expose rate limits and/or credit balance per provider? | Needs spike | |
| 95 | +| Q2 | Which providers have rate limits vs pay-per-use vs neither? | Needs spike | |
| 96 | +| Q3 | Should we show usage for all connected providers or just the active one? | Undecided | |
| 97 | +| Q4 | How does OpenCode report usage for API key providers? | Needs spike | |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +## Spike: OpenCode Usage API |
| 102 | + |
| 103 | +### Context |
| 104 | + |
| 105 | +We need to understand what usage information OpenCode exposes per provider before we can design the UI. |
| 106 | + |
| 107 | +### Questions and Answers |
| 108 | + |
| 109 | +| # | Question | Answer | |
| 110 | +|---|----------|--------| |
| 111 | +| **S1-Q1** | What does `GET /provider` return? Does it include rate limits or credit info? | No. Returns `{ all: Provider[], default: {...}, connected: string[] }`. Provider includes models and auth methods but no usage/rate data. | |
| 112 | +| **S1-Q2** | What does `AccountSnapshot` look like in OpenCode's type system? | Not exposed. The OpenCode REST API has no account/usage endpoint. | |
| 113 | +| **S1-Q3** | Is there a per-provider or per-auth-method usage endpoint? | No. The OpenCode server API has no rate limit, credits, or billing endpoints. | |
| 114 | +| **S1-Q4** | What events does OpenCode emit for usage updates? | Only per-message token counts via `message.updated` events. No rate limit or account-level usage events. | |
| 115 | + |
| 116 | +### Finding |
| 117 | + |
| 118 | +**OpenCode's REST API does not expose rate limit or credit information.** This is fundamentally different from Codex, which had account-level rate limit data. |
| 119 | + |
| 120 | +What OpenCode DOES provide: |
| 121 | +- Per-message token usage (`message.updated` → `tokens: { input, output, cache, reasoning }`) |
| 122 | +- Provider/model list with auth method types (`GET /provider/auth`) |
| 123 | +- Local session logs that can be scanned for historical token usage |
| 124 | + |
| 125 | +What OpenCode does NOT provide: |
| 126 | +- Rate limit windows (session/weekly) |
| 127 | +- Credit balances |
| 128 | +- Account billing status |
| 129 | +- Provider-specific usage quotas |
| 130 | + |
| 131 | +### Implication |
| 132 | + |
| 133 | +The "Session: X%" / "Weekly: X%" UI as designed for Codex **cannot be implemented with OpenCode** because the underlying data doesn't exist. We need a different approach. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +## Revised Requirements |
| 138 | + |
| 139 | +Based on spike findings, requirements need updating: |
| 140 | + |
| 141 | +| ID | Requirement | Status | |
| 142 | +|----|-------------|--------| |
| 143 | +| R0 | Show usage information relevant to the user's current session | Core goal | |
| 144 | +| R1 | ~~API key users see credit balance~~ **Dropped** — OpenCode doesn't expose this | Out | |
| 145 | +| R2 | ~~OAuth/plan users see rate limit windows~~ **Dropped** — OpenCode doesn't expose this | Out | |
| 146 | +| R3 | 🟡 Usage display reflects token consumption this session | Must-have | |
| 147 | +| R4 | No misleading UI — don't show rate limit meters that have no backing data | Must-have | |
| 148 | +| R5 | Support providers that don't expose usage data (graceful degradation) | Must-have | |
| 149 | +| R6 | 🟡 Per-thread token usage continues to work (already implemented) | Must-have | |
| 150 | +| R7 | 🟡 Local usage tracking shows historical token consumption | Nice-to-have | |
| 151 | +| R8 | 🟡 Model context window shown relative to current token usage | Nice-to-have | |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Revised Shapes |
| 156 | + |
| 157 | +### C: Token-based usage display (no rate limits) |
| 158 | + |
| 159 | +Since OpenCode doesn't expose rate limits, replace the Session/Weekly meters with token-based metrics derived from what we actually have. |
| 160 | + |
| 161 | +| Part | Mechanism | |
| 162 | +|------|-----------| |
| 163 | +| **C1** | Remove `RateLimitSnapshot` fetching — it's always empty | |
| 164 | +| **C2** | Primary metric: tokens used this thread (already have via `thread/tokenUsage/updated`) | |
| 165 | +| **C3** | Secondary metric: context window usage percent (tokens / model.limit.context) | |
| 166 | +| **C4** | Show "X / Y tokens" or "X% context" instead of "Session: X%" | |
| 167 | +| **C5** | Remove "Resets in X hours" — no reset concept for API usage | |
| 168 | +| **C6** | Local usage view shows historical token consumption (existing `LocalUsageSnapshot`) | |
| 169 | + |
| 170 | +### D: Hybrid — preserve UI if provider exposes limits |
| 171 | + |
| 172 | +If OpenCode adds rate limit support in the future (or if we detect specific providers that have it), conditionally show the old UI. |
| 173 | + |
| 174 | +| Part | Mechanism | Flag | |
| 175 | +|------|-----------|:----:| |
| 176 | +| **D1** | Attempt to fetch rate limits via hypothetical endpoint | ⚠️ | |
| 177 | +| **D2** | If rate limits exist, show Session/Weekly meters (CURRENT behavior) | | |
| 178 | +| **D3** | If no rate limits, fall back to Shape C token display | | |
| 179 | +| **D4** | Provider detection: check auth method type to predict billing model | ⚠️ | |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## Fit Check |
| 184 | + |
| 185 | +| Req | Requirement | Status | CURRENT | C | D | |
| 186 | +|-----|-------------|--------|:-------:|:-:|:-:| |
| 187 | +| R0 | Show usage information relevant to the user's current session | Core goal | ❌ | ✅ | ✅ | |
| 188 | +| R3 | Usage display reflects token consumption this session | Must-have | ✅ | ✅ | ✅ | |
| 189 | +| R4 | No misleading UI — don't show empty rate limit meters | Must-have | ❌ | ✅ | ✅ | |
| 190 | +| R5 | Support providers with no usage data (graceful degradation) | Must-have | ❌ | ✅ | ✅ | |
| 191 | +| R6 | Per-thread token usage continues to work | Must-have | ✅ | ✅ | ✅ | |
| 192 | +| R7 | Local usage tracking shows historical token consumption | Nice-to-have | ✅ | ✅ | ✅ | |
| 193 | +| R8 | Model context window shown relative to current token usage | Nice-to-have | ❌ | ✅ | ✅ | |
| 194 | + |
| 195 | +**Notes:** |
| 196 | +- CURRENT fails R0/R4: Shows "Session: 0%" and "Weekly: 0%" with "Resets X hours" which is misleading — there's no rate limit data backing it |
| 197 | +- D has flagged unknowns (D1, D4) — depends on OpenCode adding rate limit endpoints in the future |
| 198 | +- C is fully implementable with current OpenCode capabilities |
| 199 | + |
| 200 | +**Recommendation:** Shape C is the pragmatic choice. It uses data we actually have and provides meaningful usage feedback. |
| 201 | + |
| 202 | +--- |
| 203 | + |
| 204 | +## Shape C Detail |
| 205 | + |
| 206 | +### Reference: OpenCode TUI |
| 207 | + |
| 208 | +Screenshot shows OpenCode's native UI displays: |
| 209 | +``` |
| 210 | +Context |
| 211 | +68,107 tokens |
| 212 | +17% used |
| 213 | +$0.00 spent |
| 214 | +``` |
| 215 | + |
| 216 | +### What our UI would show |
| 217 | + |
| 218 | +**Current (misleading):** |
| 219 | +``` |
| 220 | +Session · Resets 2 hours 0% |
| 221 | +Weekly · Resets 4 days 14% |
| 222 | +``` |
| 223 | + |
| 224 | +**Proposed (Shape C) — match OpenCode UI:** |
| 225 | +``` |
| 226 | +Context |
| 227 | +68,107 tokens · 17% used |
| 228 | +``` |
| 229 | + |
| 230 | +Or compact version for status bar: |
| 231 | +``` |
| 232 | +68.1k tokens · 17% |
| 233 | +``` |
| 234 | + |
| 235 | +The "$0.00 spent" line is omitted — OpenCode calculates this client-side from token counts × model pricing, but this pricing data isn't exposed via REST API. |
| 236 | + |
| 237 | +### Parts Breakdown |
| 238 | + |
| 239 | +| Part | Mechanism | |
| 240 | +|------|-----------| |
| 241 | +| **C1** | `usageLabels.ts` → `getContextUsageLabels()` takes `ThreadTokenUsage` | |
| 242 | +| **C2** | Compute `contextPercent = (total.totalTokens / modelContextWindow) * 100` | |
| 243 | +| **C3** | Format tokens with locale separator: `68,107 tokens` | |
| 244 | +| **C4** | Show percent used when `modelContextWindow` is known | |
| 245 | +| **C5** | Remove rate limit meters, replace with context display | |
| 246 | +| **C6** | Keep `LocalUsageSnapshot` for historical view | |
| 247 | + |
| 248 | +### Migration Path |
| 249 | + |
| 250 | +1. Remove `RateLimitSnapshot` from thread state |
| 251 | +2. Remove `account_rate_limits_core` stub and related event handling |
| 252 | +3. Update `ComposerMetaBar` to show context usage instead of rate limits |
| 253 | +4. Add context window percentage computation |
| 254 | +5. Update `usageLabels.ts` with new formatting functions |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +## Open Questions |
| 259 | + |
| 260 | +| # | Question | Status | |
| 261 | +|---|----------|--------| |
| 262 | +| Q1 | Should we show cumulative tokens across all threads or just active thread? | **Active thread only** — matches OpenCode TUI | |
| 263 | +| Q2 | How prominent should context window usage be? | **Primary display** — matches OpenCode TUI | |
| 264 | +| Q3 | Should we surface local historical usage (last 7 days) in the status bar? | **No** — keep footer simple, historical view accessible elsewhere | |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +## Slices |
| 269 | + |
| 270 | +| # | Slice | Demo | Status | |
| 271 | +|---|-------|------|--------| |
| 272 | +| V1 | Remove SidebarFooter usage display, add token tooltip to ComposerMetaBar | Hover context ring → "68,107 of 200,000 tokens" | **Done** | |
| 273 | +| V2 | Remove rate limit infrastructure | N/A | **Skipped** | |
| 274 | + |
| 275 | +### V1: Final Implementation |
| 276 | + |
| 277 | +**Approach changed:** Instead of replacing the sidebar footer display, we removed it entirely. Context info is already displayed in the `ComposerMetaBar` ("Context free 30%"). Added token count tooltip on hover. |
| 278 | + |
| 279 | +**Changes:** |
| 280 | + |
| 281 | +| File | Change | |
| 282 | +|------|--------| |
| 283 | +| `src/features/app/components/Sidebar.tsx` | Removed SidebarFooter, removed `activeTokenUsage` prop | |
| 284 | +| `src/features/layout/hooks/layoutNodes/buildPrimaryNodes.tsx` | Removed `activeTokenUsage` prop | |
| 285 | +| `src/features/layout/hooks/layoutNodes/types.ts` | Moved `activeTokenUsage` to keep single definition | |
| 286 | +| `src/features/composer/components/ComposerMetaBar.tsx` | Added `contextTooltip` showing token counts on hover | |
| 287 | +| `src/App.tsx` | Cleaned up unused rate limit references | |
| 288 | +| `src/features/app/components/SidebarFooter.tsx` | Reverted to original (unused, kept for upstream compat) | |
| 289 | +| `src/features/app/utils/usageLabels.ts` | Reverted to original (kept for upstream compat) | |
| 290 | + |
| 291 | +**UI Result:** |
| 292 | +- Sidebar footer: **removed** (no misleading rate limit display) |
| 293 | +- ComposerMetaBar: Shows "Context free 30%" with tooltip "68,107 of 200,000 tokens" on hover |
| 294 | + |
| 295 | +### V2 Decision: Skipped |
| 296 | + |
| 297 | +**Rationale:** Keep rate limit infrastructure dormant to minimize upstream merge conflicts. |
| 298 | + |
| 299 | +- Upstream CodexMonitor uses rate limits (OpenAI/Codex has them) |
| 300 | +- Removing creates conflicts in reducer, types, hooks across many files |
| 301 | +- Dead code costs nothing at runtime |
| 302 | +- If OpenCode adds rate limit APIs later, infrastructure is ready |
| 303 | + |
| 304 | +**Demo:** App still works, no rate limit fetching or state, cleaner codebase. |
0 commit comments