pi update
This commit is contained in:
@@ -36,8 +36,8 @@ export function writeUsageCache(cache: UsageCache): void {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export type ProviderKey = "codex" | "claude" | "zai" | "gemini" | "antigravity";
|
||||
export type OAuthProviderId = "openai-codex" | "anthropic" | "google-gemini-cli" | "google-antigravity";
|
||||
export type ProviderKey = "codex" | "claude" | "zai" | "gemini" | "antigravity" | "opencode-go";
|
||||
export type OAuthProviderId = "openai-codex" | "anthropic" | "google-gemini-cli" | "google-antigravity" | "opencode-go";
|
||||
|
||||
export interface AuthData {
|
||||
"openai-codex"?: { access?: string; refresh?: string; expires?: number };
|
||||
@@ -45,6 +45,7 @@ export interface AuthData {
|
||||
zai?: { key?: string; access?: string; refresh?: string; expires?: number };
|
||||
"google-gemini-cli"?: { access?: string; refresh?: string; projectId?: string; expires?: number };
|
||||
"google-antigravity"?: { access?: string; refresh?: string; projectId?: string; expires?: number };
|
||||
"opencode-go"?: { key?: string; access?: string };
|
||||
}
|
||||
|
||||
export interface UsageData {
|
||||
@@ -61,6 +62,127 @@ export interface UsageData {
|
||||
|
||||
export type UsageByProvider = Record<ProviderKey, UsageData | null>;
|
||||
|
||||
// OpenCode Go usage tracking (local, since no API exists yet)
|
||||
export interface OpenCodeGoLocalUsage {
|
||||
/** Dollar value used in the 5-hour window */
|
||||
fiveHourUsed: number;
|
||||
/** Dollar value used in the weekly window */
|
||||
weeklyUsed: number;
|
||||
/** Dollar value used in the monthly window */
|
||||
monthlyUsed: number;
|
||||
/** Timestamp of the last update */
|
||||
lastUpdated: number;
|
||||
/** When the current 5-hour window started */
|
||||
fiveHourWindowStart: number;
|
||||
/** When the current week started (Unix ms) */
|
||||
weekStart: number;
|
||||
/** When the current month started (Unix ms) */
|
||||
monthStart: number;
|
||||
}
|
||||
|
||||
const OPENCODE_GO_USAGE_FILE = path.join(os.homedir(), ".pi", "agent", "opencode-go-usage.json");
|
||||
const OPENCODE_GO_FIVE_HOUR_LIMIT = 12;
|
||||
const OPENCODE_GO_WEEKLY_LIMIT = 30;
|
||||
const OPENCODE_GO_MONTHLY_LIMIT = 60;
|
||||
const OPENCODE_GO_FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
||||
|
||||
export function readOpenCodeGoUsage(): OpenCodeGoLocalUsage | null {
|
||||
try {
|
||||
const raw = fs.readFileSync(OPENCODE_GO_USAGE_FILE, "utf-8");
|
||||
const parsed = JSON.parse(raw);
|
||||
if (typeof parsed?.lastUpdated === "number") return parsed as OpenCodeGoLocalUsage;
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function writeOpenCodeGoUsage(usage: OpenCodeGoLocalUsage): void {
|
||||
try {
|
||||
const dir = path.dirname(OPENCODE_GO_USAGE_FILE);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
const tmp = `${OPENCODE_GO_USAGE_FILE}.tmp-${process.pid}-${Date.now()}`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(usage, null, 2));
|
||||
fs.renameSync(tmp, OPENCODE_GO_USAGE_FILE);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function resetOpenCodeGoUsageIfNeeded(existing: OpenCodeGoLocalUsage | null): OpenCodeGoLocalUsage {
|
||||
const now = Date.now();
|
||||
const nowDate = new Date(now);
|
||||
|
||||
// Start with defaults
|
||||
let usage: OpenCodeGoLocalUsage = existing ?? {
|
||||
fiveHourUsed: 0,
|
||||
weeklyUsed: 0,
|
||||
monthlyUsed: 0,
|
||||
lastUpdated: now,
|
||||
fiveHourWindowStart: now,
|
||||
weekStart: now,
|
||||
monthStart: now,
|
||||
};
|
||||
|
||||
// Reset 5-hour window if expired
|
||||
if (now - usage.fiveHourWindowStart >= OPENCODE_GO_FIVE_HOUR_MS) {
|
||||
usage.fiveHourUsed = 0;
|
||||
usage.fiveHourWindowStart = now;
|
||||
}
|
||||
|
||||
// Reset weekly window (Monday-based)
|
||||
const dayOfWeek = nowDate.getDay();
|
||||
const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
const thisMonday = new Date(nowDate);
|
||||
thisMonday.setDate(nowDate.getDate() - daysSinceMonday);
|
||||
thisMonday.setHours(0, 0, 0, 0);
|
||||
if (usage.weekStart < thisMonday.getTime()) {
|
||||
usage.weeklyUsed = 0;
|
||||
usage.weekStart = thisMonday.getTime();
|
||||
}
|
||||
|
||||
// Reset monthly window (1st of month)
|
||||
const thisMonthStart = new Date(nowDate.getFullYear(), nowDate.getMonth(), 1);
|
||||
if (usage.monthStart < thisMonthStart.getTime()) {
|
||||
usage.monthlyUsed = 0;
|
||||
usage.monthStart = thisMonthStart.getTime();
|
||||
}
|
||||
|
||||
usage.lastUpdated = now;
|
||||
return usage;
|
||||
}
|
||||
|
||||
export function addOpenCodeGoSpend(dollars: number): void {
|
||||
let usage = resetOpenCodeGoUsageIfNeeded(readOpenCodeGoUsage());
|
||||
usage.fiveHourUsed += dollars;
|
||||
usage.weeklyUsed += dollars;
|
||||
usage.monthlyUsed += dollars;
|
||||
usage.lastUpdated = Date.now();
|
||||
writeOpenCodeGoUsage(usage);
|
||||
}
|
||||
|
||||
export function getOpenCodeGoUsageData(): UsageData {
|
||||
const usage = resetOpenCodeGoUsageIfNeeded(readOpenCodeGoUsage());
|
||||
if (!usage) {
|
||||
return { session: 0, weekly: 0, error: "no local usage data" };
|
||||
}
|
||||
|
||||
const sessionPct = Math.min(100, (usage.fiveHourUsed / OPENCODE_GO_FIVE_HOUR_LIMIT) * 100);
|
||||
const weeklyPct = Math.min(100, (usage.weeklyUsed / OPENCODE_GO_WEEKLY_LIMIT) * 100);
|
||||
|
||||
// Calculate resets
|
||||
const fiveHourEnd = usage.fiveHourWindowStart + OPENCODE_GO_FIVE_HOUR_MS;
|
||||
const fiveHourRemaining = Math.max(0, fiveHourEnd - Date.now());
|
||||
|
||||
const weekEnd = usage.weekStart + 7 * 24 * 60 * 60 * 1000;
|
||||
const weekRemaining = Math.max(0, weekEnd - Date.now());
|
||||
|
||||
return {
|
||||
session: sessionPct,
|
||||
weekly: weeklyPct,
|
||||
sessionResetsIn: formatDuration(Math.round(fiveHourRemaining / 1000)),
|
||||
weeklyResetsIn: formatDuration(Math.round(weekRemaining / 1000)),
|
||||
extraSpend: usage.monthlyUsed,
|
||||
extraLimit: OPENCODE_GO_MONTHLY_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
export interface UsageEndpoints {
|
||||
zai: string;
|
||||
gemini: string;
|
||||
@@ -600,12 +722,14 @@ export function detectProvider(
|
||||
if (typeof model === "string") return null;
|
||||
|
||||
const provider = (model.provider || "").toLowerCase();
|
||||
const id = (model.id || "").toLowerCase();
|
||||
|
||||
if (provider === "openai-codex") return "codex";
|
||||
if (provider === "anthropic") return "claude";
|
||||
if (provider === "zai") return "zai";
|
||||
if (provider === "google-gemini-cli") return "gemini";
|
||||
if (provider === "google-antigravity") return "antigravity";
|
||||
if (provider === "opencode-go" || id.startsWith("opencode-go/")) return "opencode-go";
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -615,6 +739,7 @@ export function providerToOAuthProviderId(active: ProviderKey | null): OAuthProv
|
||||
if (active === "claude") return "anthropic";
|
||||
if (active === "gemini") return "google-gemini-cli";
|
||||
if (active === "antigravity") return "google-antigravity";
|
||||
if (active === "opencode-go") return "opencode-go";
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -629,6 +754,9 @@ export function canShowForProvider(active: ProviderKey | null, auth: AuthData |
|
||||
if (active === "antigravity") {
|
||||
return !!(auth["google-antigravity"]?.access || auth["google-antigravity"]?.refresh) && !!endpoints.antigravity;
|
||||
}
|
||||
if (active === "opencode-go") {
|
||||
return !!(auth["opencode-go"]?.key || auth["opencode-go"]?.access);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -654,6 +782,7 @@ export async function fetchAllUsages(config: FetchAllUsagesConfig = {}): Promise
|
||||
zai: null,
|
||||
gemini: null,
|
||||
antigravity: null,
|
||||
"opencode-go": null,
|
||||
};
|
||||
|
||||
if (!auth) return results;
|
||||
@@ -733,6 +862,11 @@ export async function fetchAllUsages(config: FetchAllUsagesConfig = {}): Promise
|
||||
}
|
||||
}
|
||||
|
||||
// OpenCode Go uses local tracking (no public API yet)
|
||||
if (authData["opencode-go"]?.key || authData["opencode-go"]?.access) {
|
||||
results["opencode-go"] = getOpenCodeGoUsageData();
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ const PROVIDER_LABELS: Record<ProviderKey, string> = {
|
||||
zai: "Z.AI",
|
||||
gemini: "Gemini",
|
||||
antigravity: "Antigravity",
|
||||
"opencode-go": "Go",
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -163,6 +164,7 @@ class UsageSelectorComponent extends Container implements Focusable {
|
||||
{ key: "zai", name: "Z.AI" },
|
||||
{ key: "gemini", name: "Gemini" },
|
||||
{ key: "antigravity", name: "Antigravity" },
|
||||
{ key: "opencode-go", name: "Go" },
|
||||
];
|
||||
this.allItems = [];
|
||||
for (const p of providers) {
|
||||
@@ -287,7 +289,7 @@ interface PollOptions {
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const endpoints = resolveUsageEndpoints();
|
||||
const state: UsageState = {
|
||||
codex: null, claude: null, zai: null, gemini: null, antigravity: null,
|
||||
codex: null, claude: null, zai: null, gemini: null, antigravity: null, "opencode-go": null,
|
||||
lastPoll: 0, activeProvider: null,
|
||||
};
|
||||
|
||||
@@ -433,6 +435,10 @@ export default function (pi: ExtensionAPI) {
|
||||
result = creds?.access
|
||||
? await fetchGoogleUsage(creds.access, endpoints.gemini, creds.projectId, "gemini", { endpoints })
|
||||
: { session: 0, weekly: 0, error: "missing access token (try /login again)" };
|
||||
} else if (active === "opencode-go") {
|
||||
// OpenCode Go uses local tracking (no public usage API yet)
|
||||
const { getOpenCodeGoUsageData } = await import("./core");
|
||||
result = getOpenCodeGoUsageData();
|
||||
} else {
|
||||
const creds = effectiveAuth["google-antigravity"];
|
||||
result = creds?.access
|
||||
@@ -561,6 +567,22 @@ export default function (pi: ExtensionAPI) {
|
||||
void poll({ forceFresh: true });
|
||||
});
|
||||
|
||||
// Listen for OpenCode Go spend events from other extensions
|
||||
pi.events.on("opencode-go:spend", async (amount: number) => {
|
||||
if (typeof amount === "number" && amount > 0) {
|
||||
const { addOpenCodeGoSpend } = await import("./core");
|
||||
addOpenCodeGoSpend(amount);
|
||||
// Invalidate cache and re-poll
|
||||
const cache = readUsageCache();
|
||||
if (cache?.data?.["opencode-go"]) {
|
||||
const nextCache: import("./core").UsageCache = { ...cache, data: { ...cache.data } };
|
||||
delete nextCache.data["opencode-go"];
|
||||
writeUsageCache(nextCache);
|
||||
}
|
||||
void poll({ forceFresh: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── /usage command ───────────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("usage", {
|
||||
|
||||
Reference in New Issue
Block a user