Files
Mohamed Boudra 05e94ab52e Keep usage logins visible and explain unavailable quota (#5876)
* fix(usage): keep existing logins visible and explain unavailable quota

Credential discovery must preserve accounts whose login exists even when it has expired. Keep vendor failures on the discovered account and isolate discovery failures without inventing provider cards.

* test(usage): align credential route tests with account discovery
2026-10-02 00:30:11 +01:00

132 lines
4.5 KiB
TypeScript

import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { z } from "zod";
import { routeSchema } from "../shared/input.js";
export interface StoreLookup {
home?: string;
env?: NodeJS.ProcessEnv;
platform?: NodeJS.Platform;
now?: () => number;
sqlite?: () => Sqlite | undefined;
}
interface Database {
prepare(sql: string): { all(...args: string[]): unknown[] };
close(): void;
}
export interface Sqlite {
DatabaseSync: new (path: string, options: { readOnly: boolean }) => Database;
}
export const oauthSchema = z.object({
access: z.string().min(1),
accountId: z.string().optional(),
expires: z.number().optional(),
});
const rowSchema = z.object({ id: z.number().int().positive(), data: z.string() });
export type Route = z.infer<typeof routeSchema>;
export async function readJson(path: string): Promise<unknown> {
try {
return JSON.parse(await readFile(path, "utf8"));
} catch {
return null;
}
}
export function piAuthPath(lookup: StoreLookup): string {
const home = lookup.home ?? homedir();
const env = lookup.env ?? process.env;
const configured = env.PI_CODING_AGENT_DIR?.trim();
if (!configured) return join(home, ".pi", "agent", "auth.json");
if (configured === "~") return join(home, "auth.json");
const directory = configured.startsWith("~/")
? join(home, configured.slice(2))
: resolve(configured);
return join(directory, "auth.json");
}
function ompDatabasePath(lookup: StoreLookup): string {
const home = lookup.home ?? homedir();
const env = lookup.env ?? process.env;
const platform = lookup.platform ?? process.platform;
const profile = (env.OMP_PROFILE ?? env.PI_PROFILE)?.trim() || "default";
const base = join(home, env.PI_CONFIG_DIR || ".omp");
const configRoot = profile === "default" ? base : join(base, "profiles", profile);
const defaultAgentDir = join(configRoot, "agent");
const agentDir =
profile === "default" && env.PI_CODING_AGENT_DIR
? resolve(env.PI_CODING_AGENT_DIR)
: defaultAgentDir;
if (
(platform === "linux" || platform === "darwin") &&
agentDir === defaultAgentDir &&
env.XDG_DATA_HOME
) {
const xdgBase = join(env.XDG_DATA_HOME, "omp");
const xdgRoot = profile === "default" ? xdgBase : join(xdgBase, "profiles", profile);
if (existsSync(xdgRoot)) return join(xdgRoot, "agent.db");
}
return join(agentDir, "agent.db");
}
function builtinSqlite(): Sqlite | undefined {
// Node 20 must still compile/load the plugin; only OMP needs this newer builtin.
const parsed = z
.object({
DatabaseSync: z.custom<Sqlite["DatabaseSync"]>((value) => typeof value === "function"),
})
.safeParse(process.getBuiltinModule?.("node:sqlite"));
return parsed.success ? parsed.data : undefined;
}
function ompRows(path: string, lookup: StoreLookup) {
let db: Database | undefined;
try {
const sqlite = (lookup.sqlite ?? builtinSqlite)();
if (!sqlite) return [];
db = new sqlite.DatabaseSync(path, { readOnly: true });
const rows = db
.prepare(
"SELECT id, data FROM auth_credentials WHERE provider = ? AND credential_type = 'oauth' AND disabled_cause IS NULL ORDER BY id",
)
.all("openai-codex");
return rows.flatMap((value) => {
const row = rowSchema.safeParse(value);
if (!row.success) return [];
try {
const oauth = oauthSchema.parse(JSON.parse(row.data.data));
return [{ id: row.data.id, oauth }];
} catch {
return [];
}
});
} catch {
return [];
} finally {
db?.close();
}
}
export function discoverOmp(lookup: StoreLookup): Route[] {
const path = ompDatabasePath(lookup);
return ompRows(path, lookup).map(({ id }) => ({ store: "omp", path, credentialId: id }));
}
export function readOmp(route: Extract<Route, { store: "omp" }>, lookup: StoreLookup) {
return ompRows(route.path, lookup).find((row) => row.id === route.credentialId)?.oauth ?? null;
}
export async function readHarness(
route: Extract<Route, { path: string }>,
lookup: StoreLookup = {},
) {
if (route.store === "omp") return readOmp(route, lookup);
const provider = route.store === "opencode" ? "openai" : "openai-codex";
const parsed = z.record(z.string(), z.unknown()).safeParse(await readJson(route.path));
if (!parsed.success) return null;
const oauth = oauthSchema.extend({ type: z.literal("oauth") }).safeParse(parsed.data[provider]);
return oauth.success ? oauth.data : null;
}