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:
Bohdan Triapitsyn
2026-08-12 01:49:22 +03:00
parent d1224213ca
commit 5917325d49
29 changed files with 192 additions and 182 deletions
+34 -1
View File
@@ -1,6 +1,12 @@
import { afterEach, beforeEach, describe, test } from 'node:test';
import { after, afterEach, beforeEach, describe, test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const previousQuotaDataDirectory = process.env.OPENCHAMBER_DATA_DIR;
const temporaryQuotaDataDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-vscode-quota-'));
process.env.OPENCHAMBER_DATA_DIR = temporaryQuotaDataDirectory;
// readAuthFile reads ~/.local/share/opencode/auth.json via fs.readFileSync.
// Stub fs to serve a known auth entry so the providers treat themselves as
@@ -10,6 +16,7 @@ const AUTH = JSON.stringify({
openai: { access: 'test-token' },
crof: { key: 'test-token' },
neuralwatt: { key: 'test-token' },
'opencode-go': { key: 'test-token' },
'zai-coding-plan': { key: 'test-token' },
deepseek: { key: 'test-token' },
});
@@ -20,6 +27,12 @@ import { fetchQuotaForProvider } from './quotaProviders';
type MockResponseInit = { ok?: boolean; status?: number };
after(() => {
if (previousQuotaDataDirectory === undefined) delete process.env.OPENCHAMBER_DATA_DIR;
else process.env.OPENCHAMBER_DATA_DIR = previousQuotaDataDirectory;
fs.rmSync(temporaryQuotaDataDirectory, { recursive: true, force: true });
});
const mockResponse = (body: unknown, init: MockResponseInit = {}): Response => ({
ok: 'ok' in init ? init.ok! : true,
status: init.status ?? 200,
@@ -69,6 +82,26 @@ const stubFetchFailing = (json: () => Promise<unknown>, init: MockResponseInit):
globalThis.fetch = (async () => ({ json, ...init }) as unknown as Response) as typeof fetch;
};
describe('OpenCode Go quota provider (VS Code parity)', () => {
test('uses the opencode-go key from auth.json', async () => {
let request: RequestInit | undefined;
const legacyPath = path.join(temporaryQuotaDataDirectory, 'quota', 'opencode-go.json');
fs.mkdirSync(path.dirname(legacyPath), { recursive: true });
fs.writeFileSync(legacyPath, '{not valid json');
globalThis.fetch = (async (_url: string, init?: RequestInit) => {
request = init;
return mockResponse({ usage: { rolling: { percent: 25, resetsAt: '2026-08-12T12:00:00.000Z' } } });
}) as typeof fetch;
const result = await fetchQuotaForProvider('opencode-go');
assert.equal(result.ok, true);
assert.equal((request?.headers as Record<string, string>).Authorization, 'Bearer test-token');
assert.equal(result.usage!.windows['5h']!.usedPercent, 25);
assert.throws(() => fs.statSync(legacyPath));
});
});
describe('Crof quota provider (VS Code parity)', () => {
test('reports credits balance as valueLabel with null percent', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({ usable_requests: 450, credits: 12.3456 })));