From c4df01f707551852ea24969a77435280237d651f Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Wed, 26 Aug 2026 19:29:04 +0300 Subject: [PATCH] chore(quota): drop the Command Code usage provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command Code's official API has no usage endpoints; the old usage source was the unofficial studio API reached through a now-archived plugin, so the tile could only ever fail for officially configured users. Removed across server, shared UI, and the VS Code extension; the provider logo fallback stays — it serves the model picker, not usage. --- CHANGELOG.md | 1 + packages/ui/src/lib/quota/providers/index.ts | 1 - packages/ui/src/types/quota.ts | 1 - packages/vscode/src/commandCodeQuota.ts | 66 -------------- packages/vscode/src/quotaProviders.test.ts | 52 ----------- packages/vscode/src/quotaProviders.ts | 16 ---- .../lib/quota/providers/command-code.js | 90 ------------------- .../lib/quota/providers/command-code.test.js | 85 ------------------ .../web/server/lib/quota/providers/index.js | 22 +---- 9 files changed, 5 insertions(+), 329 deletions(-) delete mode 100644 packages/vscode/src/commandCodeQuota.ts delete mode 100644 packages/web/server/lib/quota/providers/command-code.js delete mode 100644 packages/web/server/lib/quota/providers/command-code.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7de239..d9cd0f87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file. - Chat: a "Follow new content while streaming" checkbox (Settings → Chat → Streaming, on by default) turns automatic following off entirely; with it off, the scroll-to-bottom pill now appears as soon as the reply grows past the visible area. - Command palette: rarely used commands (pin session, copy session ID, multi-run launcher, archived sessions, notes, todos, status, theme) are found by typing but stay off the first screen. - Mobile: narrowing a browser window past phone size switches into the mobile layout (and back when widened); the old/new mobile layout setting is gone. +- Usage: the Command Code tile is gone — their official API exposes no usage data, so the tile could only fail. - Auth: an expired OpenChamber login is announced within seconds by a banner with a Log in button, instead of being discovered through failing actions. Sending pauses until login, and a conversation that failed to load reloads itself afterwards. - Chat: a failed send returns your typed prompt to the input — whatever the reason — instead of losing it to an error toast; a mid-send session switch lands it in that session's draft. - Chat: opening a session or resizing panels could strand the view in a large empty space below the last message; the list now returns to the real end, and a width resize keeps a reader who was at the bottom at the bottom. diff --git a/packages/ui/src/lib/quota/providers/index.ts b/packages/ui/src/lib/quota/providers/index.ts index 4c6067e0..96a4906c 100644 --- a/packages/ui/src/lib/quota/providers/index.ts +++ b/packages/ui/src/lib/quota/providers/index.ts @@ -8,7 +8,6 @@ export interface QuotaProviderMeta { export const QUOTA_PROVIDERS: QuotaProviderMeta[] = [ { id: 'claude', name: 'Claude' }, { id: 'codex', name: 'Codex' }, - { id: 'command-code', name: 'Command Code' }, { id: 'cursor', name: 'Cursor' }, { id: 'github-copilot', name: 'GitHub Copilot' }, { id: 'google', name: 'Google' }, diff --git a/packages/ui/src/types/quota.ts b/packages/ui/src/types/quota.ts index fc00b633..059e4b98 100644 --- a/packages/ui/src/types/quota.ts +++ b/packages/ui/src/types/quota.ts @@ -1,7 +1,6 @@ export type QuotaProviderId = | 'openai' | 'codex' - | 'command-code' | 'cursor' | 'claude' | 'github-copilot' diff --git a/packages/vscode/src/commandCodeQuota.ts b/packages/vscode/src/commandCodeQuota.ts deleted file mode 100644 index 264e932d..00000000 --- a/packages/vscode/src/commandCodeQuota.ts +++ /dev/null @@ -1,66 +0,0 @@ -type CommandCodeCredits = { - credits?: { monthlyCredits?: number; purchasedCredits?: number; freeCredits?: number }; - windowLimits?: { - fiveHour?: { used?: number; cap?: number; resetAt?: number }; - weekly?: { used?: number; cap?: number; resetAt?: number }; - }; -}; - -type WindowData = { usedPercent: number | null; resetAt: number | null; windowSeconds: number | null; valueLabel: string }; - -const toWindow = (data: WindowData) => ({ - usedPercent: data.usedPercent, - remainingPercent: data.usedPercent === null ? null : Math.max(0, 100 - data.usedPercent), - windowSeconds: data.windowSeconds, - resetAfterSeconds: data.resetAt === null ? null : Math.max(0, Math.floor((data.resetAt - Date.now()) / 1000)), - resetAt: data.resetAt, - resetAtFormatted: null, - resetAfterFormatted: null, - valueLabel: data.valueLabel, -}); - -const isFiniteNumber = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value); -const formatCredits = (value: number): string => String(Math.round((value + Number.EPSILON) * 100) / 100); - -const parseCredits = (value: unknown): CommandCodeCredits | null => { - if (!value || typeof value !== 'object') return null; - const payload = value as CommandCodeCredits; - return payload; -}; - -const parseOrgId = (value: unknown): string | null | undefined => { - if (!value || typeof value !== 'object') return undefined; - const org = (value as { org?: { id?: unknown } }).org; - return typeof org?.id === 'string' && org.id.trim() ? org.id.trim() : null; -}; - -const parseCommandCodeCredits = (payload: CommandCodeCredits) => { - const windows: Record> = {}; - for (const [label, value] of [['monthly_credits', payload.credits?.monthlyCredits], ['purchased_credits', payload.credits?.purchasedCredits], ['free_credits', payload.credits?.freeCredits]] as const) { - if (isFiniteNumber(value)) windows[label] = toWindow({ usedPercent: null, resetAt: null, windowSeconds: null, valueLabel: formatCredits(value) }); - } - for (const [label, limit, seconds] of [['5h', payload.windowLimits?.fiveHour, 5 * 60 * 60], ['weekly', payload.windowLimits?.weekly, 7 * 24 * 60 * 60]] as const) { - if (!isFiniteNumber(limit?.used) || !isFiniteNumber(limit.cap) || limit.cap <= 0) continue; - const resetAt = isFiniteNumber(limit.resetAt) ? (limit.resetAt < 1_000_000_000_000 ? limit.resetAt * 1000 : limit.resetAt) : null; - windows[label] = toWindow({ usedPercent: Math.min(100, Math.max(0, limit.used / limit.cap * 100)), resetAt, windowSeconds: seconds, valueLabel: `${formatCredits(limit.used)} / ${formatCredits(limit.cap)}` }); - } - return windows; -}; - -const requestJson = async (path: string, apiKey: string): Promise => { - const response = await fetch(`https://api.commandcode.ai${path}`, { headers: { Accept: 'application/json', Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(15_000) }); - if (response.status === 401 || response.status === 403) throw new Error('Command Code authentication failed'); - if (!response.ok) throw new Error(`Command Code usage API returned HTTP ${response.status}`); - return response.json().catch(() => null); -}; - -export const fetchCommandCodeUsage = async (apiKey: string) => { - const orgId = parseOrgId(await requestJson('/alpha/whoami', apiKey)); - if (orgId === undefined) throw new Error('Command Code account could not be determined'); - const creditsPath = orgId ? `/alpha/billing/credits?orgId=${encodeURIComponent(orgId)}` : '/alpha/billing/credits'; - const payload = parseCredits(await requestJson(creditsPath, apiKey)); - if (!payload) throw new Error('Command Code usage data could not be parsed'); - const windows = parseCommandCodeCredits(payload); - if (!Object.keys(windows).length) throw new Error('Command Code usage data could not be parsed'); - return windows; -}; diff --git a/packages/vscode/src/quotaProviders.test.ts b/packages/vscode/src/quotaProviders.test.ts index ee88916c..0ab8938c 100644 --- a/packages/vscode/src/quotaProviders.test.ts +++ b/packages/vscode/src/quotaProviders.test.ts @@ -17,7 +17,6 @@ const AUTH = JSON.stringify({ crof: { key: 'test-token' }, neuralwatt: { key: 'test-token' }, 'opencode-go': { key: 'test-token' }, - 'command-code': { type: 'oauth', access: 'test-token' }, 'zai-coding-plan': { key: 'test-token' }, deepseek: { key: 'test-token' }, anthropic: { access: 'test-token', refresh: 'test-refresh' }, @@ -104,57 +103,6 @@ describe('OpenCode Go quota provider (VS Code parity)', () => { }); }); -describe('Command Code quota provider (VS Code parity)', () => { - test('uses the OAuth access token and resolves server-backed limits', async () => { - const requests: Array<{ url: string; init?: RequestInit }> = []; - globalThis.fetch = (async (url: string, init?: RequestInit) => { - requests.push({ url, init }); - return mockResponse(url.endsWith('/alpha/whoami') - ? { org: { id: 'org/a' } } - : { credits: { monthlyCredits: 120 }, windowLimits: { fiveHour: { used: 25, cap: 100, resetAt: 1_776_000_000 } } }); - }) as typeof fetch; - - const result = await fetchQuotaForProvider('command-code'); - - assert.equal(result.ok, true); - assert.deepEqual(requests.map(({ url }) => url), [ - 'https://api.commandcode.ai/alpha/whoami', - 'https://api.commandcode.ai/alpha/billing/credits?orgId=org%2Fa', - ]); - assert.equal((requests[0].init?.headers as Record).Authorization, 'Bearer test-token'); - assert.equal(result.usage!.windows['5h']!.usedPercent, 25); - assert.equal(result.usage!.windows.monthly_credits!.valueLabel, '120'); - }); - - test('omits orgId for personal accounts', async () => { - const urls: string[] = []; - globalThis.fetch = (async (url: string) => { - urls.push(url); - return mockResponse(url.endsWith('/alpha/whoami') - ? { user: { id: 'user-1' }, org: null } - : { credits: { monthlyCredits: 120 } }); - }) as typeof fetch; - - const result = await fetchQuotaForProvider('command-code'); - - assert.equal(result.ok, true); - assert.deepEqual(urls, [ - 'https://api.commandcode.ai/alpha/whoami', - 'https://api.commandcode.ai/alpha/billing/credits', - ]); - }); - - test('formats fractional credit values for display', async () => { - globalThis.fetch = (async (url: string) => mockResponse(url.endsWith('/alpha/whoami') - ? { org: null } - : { credits: { monthlyCredits: 69.7947070034 }, windowLimits: { fiveHour: { used: 0.2052929966, cap: 14 } } })) as typeof fetch; - - const result = await fetchQuotaForProvider('command-code'); - - assert.equal(result.usage!.windows.monthly_credits!.valueLabel, '69.79'); - assert.equal(result.usage!.windows['5h']!.valueLabel, '0.21 / 14'); - }); -}); describe('Crof quota provider (VS Code parity)', () => { test('reports credits balance as valueLabel with null percent', async () => { diff --git a/packages/vscode/src/quotaProviders.ts b/packages/vscode/src/quotaProviders.ts index 042fc5d4..c70b43b7 100644 --- a/packages/vscode/src/quotaProviders.ts +++ b/packages/vscode/src/quotaProviders.ts @@ -2,7 +2,6 @@ import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { fetchOpenCodeGoUsage } from './opencodeGoQuota'; -import { fetchCommandCodeUsage } from './commandCodeQuota'; import { deleteLegacyOpenCodeGoCredential, readCredential } from './quotaCredentials'; import { getProviderAuth, updateProviderAuth } from './opencodeAuth'; @@ -773,9 +772,6 @@ export const listConfiguredQuotaProviders = () => { const configured = new Set(); const openCodeGoAuth = normalizeAuthEntry(getAuthEntry(auth, ['opencode-go'])); if (openCodeGoAuth && (typeof openCodeGoAuth.key === 'string' || typeof openCodeGoAuth.token === 'string')) configured.add('opencode-go'); - const commandCodeAuth = normalizeAuthEntry(getAuthEntry(auth, ['command-code'])); - if (commandCodeAuth && (typeof commandCodeAuth.key === 'string' || typeof commandCodeAuth.access === 'string' || typeof commandCodeAuth.token === 'string')) configured.add('command-code'); - if (process.env.COMMAND_CODE_API_KEY?.trim()) configured.add('command-code'); if (readCredential('ollama-cloud')) configured.add('ollama-cloud'); if (readCredential('cursor')) configured.add('cursor'); @@ -2875,18 +2871,6 @@ const fetchQuotaForProviderUncoalesced = async (providerId: string): Promise { - const entry = normalizeAuthEntry(getAuthEntry(auth, aliases)); - const stored = entry?.key ?? entry?.access ?? entry?.token; - return (typeof stored === 'string' ? stored.trim() : '') || process.env.COMMAND_CODE_API_KEY?.trim() || null; -}; - -const requestJson = async (path, apiKey, fetchImpl) => { - const response = await fetchImpl(`${API_BASE_URL}${path}`, { - headers: { - Accept: 'application/json', - Authorization: `Bearer ${apiKey}`, - 'User-Agent': 'OpenChamber quota provider', - }, - signal: AbortSignal.timeout(15_000), - }); - if (response.status === 401 || response.status === 403) throw new Error('Command Code authentication failed'); - if (!response.ok) throw new Error(`Command Code usage API returned HTTP ${response.status}`); - return response.json().catch(() => null); -}; - -const formatCredits = (value) => String(Math.round((value + Number.EPSILON) * 100) / 100); - -const toBalanceWindow = (value) => toUsageWindow({ - usedPercent: null, - windowSeconds: null, - resetAt: null, - valueLabel: formatCredits(value), -}); - -export const parseCommandCodeCredits = (payload) => { - const root = asObject(payload); - const credits = asObject(root?.credits); - const limits = asObject(root?.windowLimits); - const windows = {}; - - for (const [label, field] of [['monthly_credits', 'monthlyCredits'], ['purchased_credits', 'purchasedCredits'], ['free_credits', 'freeCredits']]) { - const value = toNumber(credits?.[field]); - if (value !== null) windows[label] = toBalanceWindow(value); - } - - for (const [label, field, windowSeconds] of [['5h', 'fiveHour', 5 * 60 * 60], ['weekly', 'weekly', 7 * 24 * 60 * 60]]) { - const limit = asObject(limits?.[field]); - const used = toNumber(limit?.used); - const cap = toNumber(limit?.cap); - if (used === null || cap === null || cap <= 0) continue; - const resetAt = toNumber(limit?.resetAt); - windows[label] = toUsageWindow({ - usedPercent: Math.min(100, Math.max(0, used / cap * 100)), - windowSeconds, - resetAt: resetAt === null ? null : resetAt < 1_000_000_000_000 ? resetAt * 1000 : resetAt, - valueLabel: `${formatCredits(used)} / ${formatCredits(cap)}`, - }); - } - - return windows; -}; - -export const fetchCommandCodeUsage = async (apiKey, fetchImpl = fetch) => { - const identity = asObject(await requestJson('/alpha/whoami', apiKey, fetchImpl)); - const org = asObject(identity?.org); - const orgId = typeof org?.id === 'string' ? org.id.trim() : ''; - const creditsPath = orgId - ? `/alpha/billing/credits?orgId=${encodeURIComponent(orgId)}` - : '/alpha/billing/credits'; - const credits = await requestJson(creditsPath, apiKey, fetchImpl); - const windows = parseCommandCodeCredits(credits); - if (Object.keys(windows).length === 0) throw new Error('Command Code usage data could not be parsed'); - return windows; -}; - -export const isConfigured = () => Boolean(getApiKey()); - -export const fetchQuota = async (auth = readAuthFile()) => { - const apiKey = getApiKey(auth); - if (!apiKey) return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' }); - try { - return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows: await fetchCommandCodeUsage(apiKey) } }); - } catch (error) { - return buildResult({ providerId, providerName, ok: false, configured: true, error: error instanceof Error ? error.message : 'Request failed' }); - } -}; diff --git a/packages/web/server/lib/quota/providers/command-code.test.js b/packages/web/server/lib/quota/providers/command-code.test.js deleted file mode 100644 index e2dc1dde..00000000 --- a/packages/web/server/lib/quota/providers/command-code.test.js +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; - -import { fetchCommandCodeUsage, fetchQuota, parseCommandCodeCredits } from './command-code.js'; - -const creditsPayload = { - credits: { monthlyCredits: 120, purchasedCredits: 30, freeCredits: 5 }, - windowLimits: { - fiveHour: { used: 25, cap: 100, resetAt: 1_776_000_000 }, - weekly: { used: 70, cap: 200, resetAt: 1_776_604_800 }, - }, -}; - -describe('Command Code quota provider', () => { - it('parses balances and rate-limit windows', () => { - const windows = parseCommandCodeCredits(creditsPayload); - expect(windows.monthly_credits).toMatchObject({ usedPercent: null, valueLabel: '120' }); - expect(windows.purchased_credits).toMatchObject({ usedPercent: null, valueLabel: '30' }); - expect(windows.free_credits).toMatchObject({ usedPercent: null, valueLabel: '5' }); - expect(windows['5h']).toMatchObject({ usedPercent: 25, valueLabel: '25 / 100', resetAt: 1_776_000_000_000 }); - expect(windows.weekly.usedPercent).toBe(35); - }); - - it('formats fractional credit values for display', () => { - const windows = parseCommandCodeCredits({ - credits: { monthlyCredits: 69.7947070034 }, - windowLimits: { fiveHour: { used: 0.2052929966, cap: 14 } }, - }); - expect(windows.monthly_credits.valueLabel).toBe('69.79'); - expect(windows['5h'].valueLabel).toBe('0.21 / 14'); - }); - - it('resolves the organization before fetching credits', async () => { - const requests = []; - const windows = await fetchCommandCodeUsage('secret', async (url, options) => { - requests.push({ url, options }); - return new Response(JSON.stringify(url.endsWith('/alpha/whoami') ? { org: { id: 'org/a' } } : creditsPayload)); - }); - expect(requests.map(({ url }) => url)).toEqual([ - 'https://api.commandcode.ai/alpha/whoami', - 'https://api.commandcode.ai/alpha/billing/credits?orgId=org%2Fa', - ]); - expect(requests[0].options.headers.Authorization).toBe('Bearer secret'); - expect(windows['5h'].usedPercent).toBe(25); - }); - - it('fetches account-scoped credits without orgId for personal accounts', async () => { - const urls = []; - await fetchCommandCodeUsage('secret', async (url) => { - urls.push(url); - return new Response(JSON.stringify(url.endsWith('/alpha/whoami') ? { user: { id: 'user-1' }, org: null } : creditsPayload)); - }); - expect(urls).toEqual([ - 'https://api.commandcode.ai/alpha/whoami', - 'https://api.commandcode.ai/alpha/billing/credits', - ]); - }); - - it('does not expose credentials in authentication errors', async () => { - await expect(fetchCommandCodeUsage('secret', async () => new Response('', { status: 401 }))).rejects.toThrow('authentication failed'); - }); - - it('reads OAuth access credentials from the OpenCode auth file', async () => { - const fetchMock = vi.fn() - .mockResolvedValueOnce(new Response(JSON.stringify({ org: { id: 'org-1' } }))) - .mockResolvedValueOnce(new Response(JSON.stringify(creditsPayload))); - vi.stubGlobal('fetch', fetchMock); - const result = await fetchQuota({ 'command-code': { type: 'oauth', access: 'test-token' } }); - expect(result).toMatchObject({ providerId: 'command-code', ok: true, configured: true }); - expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer test-token'); - vi.unstubAllGlobals(); - }); - - it('recognizes Command Code auth entries under supported provider ID variants', async () => { - for (const providerId of ['commandcode', 'command_code', 'command code']) { - const fetchMock = vi.fn() - .mockResolvedValueOnce(new Response(JSON.stringify({ org: { id: 'org-1' } }))) - .mockResolvedValueOnce(new Response(JSON.stringify(creditsPayload))); - vi.stubGlobal('fetch', fetchMock); - - const result = await fetchQuota({ [providerId]: { type: 'oauth', access: 'test-token' } }); - expect(result).toMatchObject({ providerId: 'command-code', ok: true, configured: true }); - vi.unstubAllGlobals(); - } - }); -}); diff --git a/packages/web/server/lib/quota/providers/index.js b/packages/web/server/lib/quota/providers/index.js index 1f97d159..3ae4cc99 100644 --- a/packages/web/server/lib/quota/providers/index.js +++ b/packages/web/server/lib/quota/providers/index.js @@ -9,7 +9,6 @@ import { buildResult } from '../utils/index.js'; import * as claude from './claude/index.js'; import * as codex from './codex.js'; -import * as commandCode from './command-code.js'; import * as copilot from './copilot.js'; import * as crof from './crof.js'; import * as cursor from './cursor.js'; @@ -30,12 +29,6 @@ import * as opencodeGo from './opencode-go.js'; import * as xai from './xai.js'; const registry = { - 'command-code': { - providerId: commandCode.providerId, - providerName: commandCode.providerName, - isConfigured: commandCode.isConfigured, - fetchQuota: commandCode.fetchQuota - }, claude: { providerId: claude.providerId, providerName: claude.providerName, @@ -160,12 +153,6 @@ const registry = { const pendingFetches = new Map(); -const normalizeQuotaProviderId = (providerId) => { - if (typeof providerId !== 'string') return providerId; - return ['command-code', 'commandcode', 'command_code', 'command code'].includes(providerId.trim().toLowerCase()) - ? 'command-code' - : providerId; -}; export const listConfiguredQuotaProviders = () => { const configured = []; @@ -210,14 +197,13 @@ const fetchQuotaForProviderUncoalesced = async (providerId) => { }; export const fetchQuotaForProvider = (providerId) => { - const normalizedProviderId = normalizeQuotaProviderId(providerId); - const existing = pendingFetches.get(normalizedProviderId); + const existing = pendingFetches.get(providerId); if (existing) return existing; - const pending = fetchQuotaForProviderUncoalesced(normalizedProviderId).finally(() => { - if (pendingFetches.get(normalizedProviderId) === pending) pendingFetches.delete(normalizedProviderId); + const pending = fetchQuotaForProviderUncoalesced(providerId).finally(() => { + if (pendingFetches.get(providerId) === pending) pendingFetches.delete(providerId); }); - pendingFetches.set(normalizedProviderId, pending); + pendingFetches.set(providerId, pending); return pending; };