fix(usage): discover migrated database logins and relative paths

Match the source application’s migration and database-path rules so usage discovery does not keep reporting a stale file login after migration.

Co-authored-by: Jiayang Wu <651475459@qq.com>
This commit is contained in:
paseo-bot[bot]
2026-11-03 08:29:14 +00:00
co-authored by Jiayang Wu
parent d5f19aed37
commit bd5671fd49
3 changed files with 107 additions and 22 deletions
@@ -224,7 +224,7 @@ async function fetchApi(url, init) {
return new Response(JSON.stringify({ account: { uuid: second ? 'claude-two' : 'claude-one' }, organization: { uuid: second ? 'org-two' : 'org-one' } }));
}
if (token === 'Bearer fixture-revoked') return new Response(null, { status: 401 });
const used = { 'Bearer fixture-codex': 11, 'Bearer fixture-opencode': 22, 'Bearer fixture-pi-codex': 33, 'Bearer fixture-omp-codex': 44, 'Bearer fixture-omp-claude': 66, 'Bearer fixture-pi-claude': 77 }[token] || 55;
const used = { 'Bearer fixture-codex': 11, 'Bearer fixture-opencode': 22, 'Bearer fixture-opencode-db': 23, 'Bearer fixture-pi-codex': 33, 'Bearer fixture-omp-codex': 44, 'Bearer fixture-omp-claude': 66, 'Bearer fixture-pi-claude': 77 }[token] || 55;
return new Response(JSON.stringify({ rate_limit: { primary_window: { used_percent: used } }, five_hour: { utilization: used } }));
}
export default function contribute(server) {
@@ -270,6 +270,31 @@ export default function contribute(server) {
(await client.listUsageReports({ reportIds: ["codex:chatgpt-one"], forceRefresh: true }))
.reports[0];
expect((await refresh())?.report.windows[0]?.usedPct).toBe(22);
const openCodeDatabase = path.join(path.dirname(openCodePath), "opencode.db");
const openCodeDb = new DatabaseSync(openCodeDatabase);
openCodeDb.exec(
"CREATE TABLE credential (id TEXT PRIMARY KEY, integration_id TEXT, label TEXT, value TEXT, active INTEGER, time_created INTEGER, time_updated INTEGER)",
);
// OpenCode's v1 migration leaves active NULL and does not update auth.json.
openCodeDb.prepare("INSERT INTO credential VALUES (?, ?, ?, ?, NULL, 1, 1)").run(
"migrated-login",
"openai",
"OpenAI",
JSON.stringify({
type: "oauth",
access: "fixture-opencode-db",
expires: Date.now() + 60_000,
metadata: { accountID: "chatgpt-one" },
}),
);
openCodeDb.close();
const rediscover = async () =>
(await client.listUsageReports({ forceRefresh: true })).reports.find(
(entry) => entry.id === "codex:chatgpt-one",
);
expect((await rediscover())!.report.windows[0]!.usedPct).toBe(23);
await rm(openCodeDatabase);
expect((await rediscover())!.report.windows[0]!.usedPct).toBe(22);
await rm(openCodePath);
expect((await refresh())?.report.windows[0]?.usedPct).toBe(33);
await json(piPath, {
@@ -470,22 +470,80 @@ test.each([
expect(await logins(lookup(env))).toEqual([{ route: { store: "opencode", path: file } }]);
});
test("OpenCode v2 database path honours OPENCODE_DB", async () => {
const databasePath = join(home, "custom-opencode.db");
const db = await opencodeDatabase(databasePath);
db.prepare("INSERT INTO credential VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(
"cred_1",
"openai",
"OpenAI",
opencodeOauth("fixture-db", 2000, "db-account"),
null,
"chatgpt-headless",
1,
1,
1,
);
db.close();
expect(await logins(lookup({ OPENCODE_DB: databasePath }))).toEqual([
{ route: { store: "opencode-db", path: databasePath, credentialId: "cred_1" } },
]);
});
test.each(["absolute", "relative"])(
"OpenCode v2 database path honours %s OPENCODE_DB",
async (kind) => {
const data = join(home, "data");
const databasePath = join(data, "opencode", "custom-opencode.db");
const db = await opencodeDatabase(databasePath);
db.prepare("INSERT INTO credential VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(
"cred_1",
"openai",
"OpenAI",
opencodeOauth("fixture-db", 2000, "db-account"),
null,
"chatgpt-headless",
1,
1,
1,
);
db.close();
expect(
await logins(
lookup({
XDG_DATA_HOME: data,
OPENCODE_DB: kind === "absolute" ? databasePath : "custom-opencode.db",
}),
),
).toEqual([{ route: { store: "opencode-db", path: databasePath, credentialId: "cred_1" } }]);
},
);
test.each([false, true])(
"OpenCode v2 selects its current login after legacy migration (explicit selection: %s)",
async (selected) => {
const data = join(home, "data");
const env = { XDG_DATA_HOME: data };
const databasePath = join(data, "opencode", "opencode.db");
const db = await opencodeDatabase(databasePath);
const insert = db.prepare("INSERT INTO credential VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
// The v1 migration leaves active NULL; OpenCode uses creation order until a login is selected.
insert.run(
"older",
"openai",
"OpenAI",
opencodeOauth("fixture-older", 2000, "older-account"),
null,
"chatgpt-headless",
selected ? 1 : null,
1,
3,
);
insert.run(
"newer",
"openai",
"OpenAI",
opencodeOauth("fixture-newer", 2000, "newer-account"),
null,
"chatgpt-headless",
null,
2,
2,
);
db.close();
const inputs = await logins(lookup(env));
expect(inputs).toEqual([
{
route: {
store: "opencode-db",
path: databasePath,
credentialId: selected ? "older" : "newer",
},
},
]);
expect(await accountIdentity(inputs[0]!, env)).toEqual({
key: selected ? "older-account" : "newer-account",
});
},
);
+4 -2
View File
@@ -73,7 +73,7 @@ export function opencodeStorePaths(env: NodeJS.ProcessEnv, home: string): Openco
const directory = join(dataHome, "opencode");
return {
file: join(directory, "auth.json"),
database: env.OPENCODE_DB ? resolve(env.OPENCODE_DB) : join(directory, "opencode.db"),
database: resolve(directory, env.OPENCODE_DB || "opencode.db"),
};
}
@@ -176,7 +176,9 @@ export function readOpencodeOauth(path: string, lookup: StoreLookup = {}): Openc
}
const rows = db
.prepare(
"SELECT id, value FROM credential WHERE integration_id = ? AND active = 1 ORDER BY time_updated DESC, id DESC",
// Migrated v1 rows have no active flag. OpenCode selects the newest
// created login until an explicit selection pins another one.
"SELECT id, value FROM credential WHERE integration_id = ? AND (active = 1 OR active IS NULL) ORDER BY active DESC, time_created DESC, id DESC LIMIT 1",
)
.all("openai");
return rows.flatMap((value) => {