BIG pi update with claude chat
This commit is contained in:
@@ -55,6 +55,8 @@ export interface UsageData {
|
||||
/** Unix ms timestamp of when the session window resets (from the raw API response). */
|
||||
sessionResetsAt?: number;
|
||||
weeklyResetsIn?: string;
|
||||
/** Unix ms timestamp of when the weekly window resets. */
|
||||
weeklyResetsAt?: number;
|
||||
extraSpend?: number;
|
||||
extraLimit?: number;
|
||||
error?: string;
|
||||
@@ -321,13 +323,34 @@ export function formatResetsAt(isoDate: string, nowMs = Date.now()): string {
|
||||
return formatDuration(diffSeconds);
|
||||
}
|
||||
|
||||
const CLAUDE_CREDENTIALS_FILE = path.join(os.homedir(), ".claude", ".credentials.json");
|
||||
|
||||
export function readAuth(authFile = DEFAULT_AUTH_FILE): AuthData | null {
|
||||
let result: AuthData | null = null;
|
||||
|
||||
// Read pi auth.json for non-Claude providers
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(authFile, "utf-8"));
|
||||
return asObject(parsed) as AuthData;
|
||||
result = asObject(parsed) as AuthData;
|
||||
} catch {
|
||||
return null;
|
||||
result = {} as AuthData;
|
||||
}
|
||||
|
||||
// Read Claude credentials from ~/.claude/.credentials.json
|
||||
try {
|
||||
const claudeRaw = fs.readFileSync(CLAUDE_CREDENTIALS_FILE, "utf-8");
|
||||
const claudeCreds = JSON.parse(claudeRaw);
|
||||
const oauth = claudeCreds?.claudeAiOauth;
|
||||
if (oauth?.accessToken) {
|
||||
result!.anthropic = {
|
||||
access: oauth.accessToken,
|
||||
refresh: oauth.refreshToken,
|
||||
expires: typeof oauth.expiresAt === "number" ? oauth.expiresAt : undefined,
|
||||
};
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function writeAuth(auth: AuthData, authFile = DEFAULT_AUTH_FILE): boolean {
|
||||
@@ -647,6 +670,9 @@ export async function fetchClaudeUsage(token: string, config: RequestConfig = {}
|
||||
const sessionResetsAt = data?.five_hour?.resets_at
|
||||
? new Date(data.five_hour.resets_at).getTime()
|
||||
: undefined;
|
||||
const weeklyResetsAt = data?.seven_day?.resets_at
|
||||
? new Date(data.seven_day.resets_at).getTime()
|
||||
: undefined;
|
||||
|
||||
const usage: UsageData = {
|
||||
session: readPercentCandidate(data?.five_hour?.utilization) ?? 0,
|
||||
@@ -654,6 +680,7 @@ export async function fetchClaudeUsage(token: string, config: RequestConfig = {}
|
||||
sessionResetsIn: data?.five_hour?.resets_at ? formatResetsAt(data.five_hour.resets_at) : undefined,
|
||||
sessionResetsAt: Number.isFinite(sessionResetsAt) ? sessionResetsAt : undefined,
|
||||
weeklyResetsIn: data?.seven_day?.resets_at ? formatResetsAt(data.seven_day.resets_at) : undefined,
|
||||
weeklyResetsAt: Number.isFinite(weeklyResetsAt) ? weeklyResetsAt : undefined,
|
||||
};
|
||||
|
||||
if (data?.extra_usage?.is_enabled) {
|
||||
|
||||
@@ -306,14 +306,27 @@ export default function (pi: ExtensionAPI) {
|
||||
const active = state.activeProvider;
|
||||
const data = active ? state[active] : null;
|
||||
|
||||
// Always emit event for other extensions (e.g. footer-display)
|
||||
if (data && !data.error) {
|
||||
// Always emit Claude usage for other extensions (e.g. footer-display)
|
||||
// so S/W bars are visible regardless of active model.
|
||||
const claudeData = state.claude;
|
||||
if (claudeData && !claudeData.error) {
|
||||
pi.events.emit("usage:update", {
|
||||
session: claudeData.session,
|
||||
weekly: claudeData.weekly,
|
||||
sessionResetsIn: claudeData.sessionResetsIn,
|
||||
sessionResetsAt: claudeData.sessionResetsAt,
|
||||
weeklyResetsIn: claudeData.weeklyResetsIn,
|
||||
weeklyResetsAt: claudeData.weeklyResetsAt,
|
||||
});
|
||||
} else if (data && !data.error) {
|
||||
// Fallback to active provider data if Claude data unavailable
|
||||
pi.events.emit("usage:update", {
|
||||
session: data.session,
|
||||
weekly: data.weekly,
|
||||
sessionResetsIn: data.sessionResetsIn,
|
||||
sessionResetsAt: data.sessionResetsAt,
|
||||
weeklyResetsIn: data.weeklyResetsIn,
|
||||
weeklyResetsAt: data.weeklyResetsAt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,6 +385,48 @@ export default function (pi: ExtensionAPI) {
|
||||
const auth = readAuth();
|
||||
const active = state.activeProvider;
|
||||
|
||||
// Always try to fetch Claude data so S/W bars show regardless of active provider
|
||||
if (auth && canShowForProvider("claude", auth, endpoints)) {
|
||||
try {
|
||||
const cache = readUsageCache();
|
||||
const now = Date.now();
|
||||
const cacheTtl = options.cacheTtl ?? CACHE_TTL_MS;
|
||||
const claudeBlockedUntil = cache?.rateLimitedUntil?.claude ?? 0;
|
||||
if (now < claudeBlockedUntil) {
|
||||
if (cache?.data?.claude) state.claude = cache.data.claude;
|
||||
} else {
|
||||
const claudeCacheFresh = cache && now - cache.timestamp < cacheTtl && cache.data?.claude;
|
||||
if (claudeCacheFresh && !options.forceFresh) {
|
||||
state.claude = cache.data.claude;
|
||||
} else {
|
||||
const claudeAccess = auth.anthropic?.access;
|
||||
if (claudeAccess) {
|
||||
const claudeResult = await fetchClaudeUsage(claudeAccess);
|
||||
state.claude = claudeResult;
|
||||
if (!claudeResult.error) {
|
||||
const nextCache: import("./core").UsageCache = {
|
||||
timestamp: now,
|
||||
data: { ...(cache?.data ?? {}), claude: claudeResult },
|
||||
rateLimitedUntil: { ...(cache?.rateLimitedUntil ?? {}) },
|
||||
};
|
||||
delete nextCache.rateLimitedUntil!.claude;
|
||||
writeUsageCache(nextCache);
|
||||
} else if (claudeResult.error === "HTTP 429") {
|
||||
// Record backoff even when Claude is not the active provider —
|
||||
// without this the prefetch would hammer the API on every poll.
|
||||
const nextCache: import("./core").UsageCache = {
|
||||
timestamp: cache?.timestamp ?? now,
|
||||
data: { ...(cache?.data ?? {}) },
|
||||
rateLimitedUntil: { ...(cache?.rateLimitedUntil ?? {}), claude: now + RATE_LIMITED_BACKOFF_MS },
|
||||
};
|
||||
writeUsageCache(nextCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!canShowForProvider(active, auth, endpoints) || !auth || !active) {
|
||||
state.lastPoll = Date.now(); updateStatus(); return;
|
||||
}
|
||||
@@ -478,7 +533,9 @@ export default function (pi: ExtensionAPI) {
|
||||
await Promise.race([runPollInner(options), timeout]);
|
||||
}
|
||||
|
||||
const POLL_TIMEOUT_MS = 30_000;
|
||||
// Must be less than the 25 000 ms timeout inside runPoll so the guard fires
|
||||
// before runPoll's finally-block clears pollInFlight.
|
||||
const POLL_TIMEOUT_MS = 20_000;
|
||||
|
||||
async function poll(options: PollOptions = {}) {
|
||||
// If a previous poll has been running longer than POLL_TIMEOUT_MS, abandon it
|
||||
@@ -557,15 +614,6 @@ export default function (pi: ExtensionAPI) {
|
||||
await poll({ cacheTtl: ACTIVE_CACHE_TTL_MS });
|
||||
});
|
||||
|
||||
pi.events.on("claude-account:switched", () => {
|
||||
const cache = readUsageCache();
|
||||
if (cache?.data?.claude) {
|
||||
const nextCache: import("./core").UsageCache = { ...cache, data: { ...cache.data } };
|
||||
delete nextCache.data.claude;
|
||||
writeUsageCache(nextCache);
|
||||
}
|
||||
void poll({ forceFresh: true });
|
||||
});
|
||||
|
||||
// Listen for OpenCode Go spend events from other extensions
|
||||
pi.events.on("opencode-go:spend", async (amount: number) => {
|
||||
|
||||
Reference in New Issue
Block a user