feat: switch OpenCode Go usage to API
OpenCode Go now reads quota usage with a bearer API key from OpenCode auth.json Removes the old workspace ID and browser cookie credential flow Deletes legacy OpenCode Go credential files during upgrade
This commit is contained in:
@@ -1,27 +1,25 @@
|
||||
type OpenCodeGoCredential = { workspaceId: string; authCookie: string };
|
||||
type OpenCodeGoCredential = { apiKey: string };
|
||||
|
||||
const toWindow = (usedPercent: number, resetInSec: number) => ({
|
||||
const toWindow = (usedPercent: number, resetAt: string) => ({
|
||||
usedPercent: Math.min(100, Math.max(0, usedPercent)),
|
||||
remainingPercent: 100 - Math.min(100, Math.max(0, usedPercent)),
|
||||
windowSeconds: null,
|
||||
resetAfterSeconds: Math.max(0, resetInSec),
|
||||
resetAt: Date.now() + Math.max(0, resetInSec) * 1000,
|
||||
resetAfterSeconds: Math.max(0, Math.floor((new Date(resetAt).getTime() - Date.now()) / 1000)),
|
||||
resetAt: new Date(resetAt).getTime(),
|
||||
resetAtFormatted: null,
|
||||
resetAfterFormatted: null,
|
||||
});
|
||||
|
||||
export const fetchOpenCodeGoUsage = async (credential: OpenCodeGoCredential) => {
|
||||
const response = await fetch(`https://opencode.ai/workspace/${encodeURIComponent(credential.workspaceId)}/go`, { headers: { Accept: 'text/html', Cookie: `auth=${credential.authCookie}` }, redirect: 'manual', signal: AbortSignal.timeout(15_000) });
|
||||
const response = await fetch('https://opencode.ai/zen/go/v1/usage', { headers: { Accept: 'application/json', Authorization: `Bearer ${credential.apiKey}` }, signal: AbortSignal.timeout(15_000) });
|
||||
if (response.status === 401 || response.status === 403 || (response.status >= 300 && response.status < 400)) throw new Error('OpenCode Go authentication failed');
|
||||
if (!response.ok) throw new Error(`OpenCode Go dashboard returned HTTP ${response.status}`);
|
||||
const html = (await response.text()).replaceAll('"', '"').replaceAll('"', '"').replaceAll('\\u0022', '"').replaceAll('\\"', '"');
|
||||
if (!response.ok) throw new Error(`OpenCode Go usage API returned HTTP ${response.status}`);
|
||||
const payload = await response.json().catch(() => null) as { usage?: Record<string, { percent?: unknown; resetsAt?: unknown }> } | null;
|
||||
const windows: Record<string, ReturnType<typeof toWindow>> = {};
|
||||
for (const [key, field] of Object.entries({ '5h': 'rollingUsage', weekly: 'weeklyUsage', monthly: 'monthlyUsage' })) {
|
||||
const body = html.match(new RegExp(`["']?${field}["']?\\s*:\\s*(?:\\$R\\[\\d+\\]\\s*=\\s*)?\\{([^{}]*)\\}`, 's'))?.[1];
|
||||
if (!body) continue;
|
||||
const used = Number(body.match(/usagePercent\s*:\s*["']?(-?\d+(?:\.\d+)?)/)?.[1]);
|
||||
const reset = Number(body.match(/resetInSec\s*:\s*["']?(-?\d+(?:\.\d+)?)/)?.[1]);
|
||||
if (Number.isFinite(used) && Number.isFinite(reset)) windows[key] = toWindow(used, reset);
|
||||
for (const [key, apiKey] of Object.entries({ '5h': 'rolling', weekly: 'weekly', monthly: 'monthly' })) {
|
||||
const entry = payload?.usage?.[apiKey];
|
||||
if (typeof entry?.percent !== 'number' || !Number.isFinite(entry.percent) || typeof entry.resetsAt !== 'string' || !Number.isFinite(new Date(entry.resetsAt).getTime())) continue;
|
||||
windows[key] = toWindow(entry.percent, entry.resetsAt);
|
||||
}
|
||||
if (!Object.keys(windows).length) throw new Error('OpenCode Go usage data could not be parsed');
|
||||
return windows;
|
||||
|
||||
Reference in New Issue
Block a user