chore: satisfy lint
This commit is contained in:
@@ -20,6 +20,10 @@ import {
|
|||||||
saveSessionStore,
|
saveSessionStore,
|
||||||
} from "../../config/sessions.js";
|
} from "../../config/sessions.js";
|
||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
|
import {
|
||||||
|
formatUsageSummaryLine,
|
||||||
|
loadProviderUsageSummary,
|
||||||
|
} from "../../infra/provider-usage.js";
|
||||||
import { triggerClawdbotRestart } from "../../infra/restart.js";
|
import { triggerClawdbotRestart } from "../../infra/restart.js";
|
||||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||||
@@ -38,10 +42,6 @@ import {
|
|||||||
formatContextUsageShort,
|
formatContextUsageShort,
|
||||||
formatTokenCount,
|
formatTokenCount,
|
||||||
} from "../status.js";
|
} from "../status.js";
|
||||||
import {
|
|
||||||
formatUsageSummaryLine,
|
|
||||||
loadProviderUsageSummary,
|
|
||||||
} from "../../infra/provider-usage.js";
|
|
||||||
import type { MsgContext } from "../templating.js";
|
import type { MsgContext } from "../templating.js";
|
||||||
import type {
|
import type {
|
||||||
ElevatedLevel,
|
ElevatedLevel,
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import {
|
|||||||
resolveStorePath,
|
resolveStorePath,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
|
import { callGateway } from "../gateway/call.js";
|
||||||
|
import { info } from "../globals.js";
|
||||||
|
import { buildProviderSummary } from "../infra/provider-summary.js";
|
||||||
import {
|
import {
|
||||||
formatUsageReportLines,
|
formatUsageReportLines,
|
||||||
loadProviderUsageSummary,
|
loadProviderUsageSummary,
|
||||||
} from "../infra/provider-usage.js";
|
} from "../infra/provider-usage.js";
|
||||||
import { callGateway } from "../gateway/call.js";
|
|
||||||
import { info } from "../globals.js";
|
|
||||||
import { buildProviderSummary } from "../infra/provider-summary.js";
|
|
||||||
import { peekSystemEvents } from "../infra/system-events.js";
|
import { peekSystemEvents } from "../infra/system-events.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { resolveWhatsAppAccount } from "../web/accounts.js";
|
import { resolveWhatsAppAccount } from "../web/accounts.js";
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ export const usageHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, summary, undefined);
|
respond(true, summary, undefined);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ describe("provider usage formatting", () => {
|
|||||||
{
|
{
|
||||||
provider: "anthropic",
|
provider: "anthropic",
|
||||||
displayName: "Claude",
|
displayName: "Claude",
|
||||||
windows: [
|
windows: [{ label: "5h", usedPercent: 20, resetAt: now + 60_000 }],
|
||||||
{ label: "5h", usedPercent: 20, resetAt: now + 60_000 },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -69,40 +67,49 @@ describe("provider usage formatting", () => {
|
|||||||
|
|
||||||
describe("provider usage loading", () => {
|
describe("provider usage loading", () => {
|
||||||
it("loads usage snapshots with injected auth", async () => {
|
it("loads usage snapshots with injected auth", async () => {
|
||||||
const makeResponse = (status: number, body: unknown) =>
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
({
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
ok: status >= 200 && status < 300,
|
const headers =
|
||||||
status,
|
typeof body === "string"
|
||||||
json: async () => body,
|
? undefined
|
||||||
}) as any;
|
: { "Content-Type": "application/json" };
|
||||||
|
return new Response(payload, { status, headers });
|
||||||
|
};
|
||||||
|
|
||||||
const mockFetch = vi.fn(async (input: any) => {
|
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(
|
||||||
const url = String(input);
|
async (input) => {
|
||||||
if (url.includes("api.anthropic.com")) {
|
const url =
|
||||||
return makeResponse(200, {
|
typeof input === "string"
|
||||||
five_hour: { utilization: 20, resets_at: "2026-01-07T01:00:00Z" },
|
? input
|
||||||
});
|
: input instanceof URL
|
||||||
}
|
? input.toString()
|
||||||
if (url.includes("api.z.ai")) {
|
: input.url;
|
||||||
return makeResponse(200, {
|
if (url.includes("api.anthropic.com")) {
|
||||||
success: true,
|
return makeResponse(200, {
|
||||||
code: 200,
|
five_hour: { utilization: 20, resets_at: "2026-01-07T01:00:00Z" },
|
||||||
data: {
|
});
|
||||||
planName: "Pro",
|
}
|
||||||
limits: [
|
if (url.includes("api.z.ai")) {
|
||||||
{
|
return makeResponse(200, {
|
||||||
type: "TOKENS_LIMIT",
|
success: true,
|
||||||
percentage: 25,
|
code: 200,
|
||||||
unit: 3,
|
data: {
|
||||||
number: 6,
|
planName: "Pro",
|
||||||
nextResetTime: "2026-01-07T06:00:00Z",
|
limits: [
|
||||||
},
|
{
|
||||||
],
|
type: "TOKENS_LIMIT",
|
||||||
},
|
percentage: 25,
|
||||||
});
|
unit: 3,
|
||||||
}
|
number: 6,
|
||||||
return makeResponse(404, "not found");
|
nextResetTime: "2026-01-07T06:00:00Z",
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return makeResponse(404, "not found");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const summary = await loadProviderUsageSummary({
|
const summary = await loadProviderUsageSummary({
|
||||||
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
now: Date.UTC(2026, 0, 7, 0, 0, 0),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { loadConfig } from "../config/config.js";
|
|
||||||
import {
|
import {
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
listProfilesForProvider,
|
listProfilesForProvider,
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
resolveEnvApiKey,
|
resolveEnvApiKey,
|
||||||
} from "../agents/model-auth.js";
|
} from "../agents/model-auth.js";
|
||||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||||
|
import { loadConfig } from "../config/config.js";
|
||||||
|
|
||||||
export type UsageWindow = {
|
export type UsageWindow = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -42,6 +42,59 @@ export type UsageProviderId =
|
|||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "zai";
|
| "zai";
|
||||||
|
|
||||||
|
type ClaudeUsageResponse = {
|
||||||
|
five_hour?: { utilization?: number; resets_at?: string };
|
||||||
|
seven_day?: { utilization?: number; resets_at?: string };
|
||||||
|
seven_day_sonnet?: { utilization?: number };
|
||||||
|
seven_day_opus?: { utilization?: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
type CopilotUsageResponse = {
|
||||||
|
quota_snapshots?: {
|
||||||
|
premium_interactions?: { percent_remaining?: number | null };
|
||||||
|
chat?: { percent_remaining?: number | null };
|
||||||
|
};
|
||||||
|
copilot_plan?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GeminiUsageResponse = {
|
||||||
|
buckets?: Array<{ modelId?: string; remainingFraction?: number }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodexUsageResponse = {
|
||||||
|
rate_limit?: {
|
||||||
|
primary_window?: {
|
||||||
|
limit_window_seconds?: number;
|
||||||
|
used_percent?: number;
|
||||||
|
reset_at?: number;
|
||||||
|
};
|
||||||
|
secondary_window?: {
|
||||||
|
limit_window_seconds?: number;
|
||||||
|
used_percent?: number;
|
||||||
|
reset_at?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
plan_type?: string;
|
||||||
|
credits?: { balance?: number | string | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
type ZaiUsageResponse = {
|
||||||
|
success?: boolean;
|
||||||
|
code?: number;
|
||||||
|
msg?: string;
|
||||||
|
data?: {
|
||||||
|
planName?: string;
|
||||||
|
plan?: string;
|
||||||
|
limits?: Array<{
|
||||||
|
type?: string;
|
||||||
|
percentage?: number;
|
||||||
|
unit?: number;
|
||||||
|
number?: number;
|
||||||
|
nextResetTime?: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type ProviderAuth = {
|
type ProviderAuth = {
|
||||||
provider: UsageProviderId;
|
provider: UsageProviderId;
|
||||||
token: string;
|
token: string;
|
||||||
@@ -121,8 +174,10 @@ function formatResetRemaining(targetMs?: number, now?: number): string | null {
|
|||||||
const days = Math.floor(hours / 24);
|
const days = Math.floor(hours / 24);
|
||||||
if (days < 7) return `${days}d ${hours % 24}h`;
|
if (days < 7) return `${days}d ${hours % 24}h`;
|
||||||
|
|
||||||
return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" })
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
.format(new Date(targetMs));
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
}).format(new Date(targetMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickPrimaryWindow(windows: UsageWindow[]): UsageWindow | undefined {
|
function pickPrimaryWindow(windows: UsageWindow[]): UsageWindow | undefined {
|
||||||
@@ -148,11 +203,13 @@ export function formatUsageSummaryLine(
|
|||||||
.slice(0, opts?.maxProviders ?? summary.providers.length);
|
.slice(0, opts?.maxProviders ?? summary.providers.length);
|
||||||
if (providers.length === 0) return null;
|
if (providers.length === 0) return null;
|
||||||
|
|
||||||
const parts = providers.map((entry) => {
|
const parts = providers
|
||||||
const window = pickPrimaryWindow(entry.windows);
|
.map((entry) => {
|
||||||
if (!window) return null;
|
const window = pickPrimaryWindow(entry.windows);
|
||||||
return `${entry.displayName} ${formatWindowShort(window, opts?.now)}`;
|
if (!window) return null;
|
||||||
}).filter(Boolean) as string[];
|
return `${entry.displayName} ${formatWindowShort(window, opts?.now)}`;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
if (parts.length === 0) return null;
|
if (parts.length === 0) return null;
|
||||||
return `📊 Usage: ${parts.join(" · ")}`;
|
return `📊 Usage: ${parts.join(" · ")}`;
|
||||||
@@ -244,7 +301,7 @@ async function fetchClaudeUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as ClaudeUsageResponse;
|
||||||
const windows: UsageWindow[] = [];
|
const windows: UsageWindow[] = [];
|
||||||
|
|
||||||
if (data.five_hour?.utilization !== undefined) {
|
if (data.five_hour?.utilization !== undefined) {
|
||||||
@@ -310,12 +367,12 @@ async function fetchCopilotUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as CopilotUsageResponse;
|
||||||
const windows: UsageWindow[] = [];
|
const windows: UsageWindow[] = [];
|
||||||
|
|
||||||
if (data.quota_snapshots?.premium_interactions) {
|
if (data.quota_snapshots?.premium_interactions) {
|
||||||
const remaining = data.quota_snapshots.premium_interactions
|
const remaining =
|
||||||
.percent_remaining;
|
data.quota_snapshots.premium_interactions.percent_remaining;
|
||||||
windows.push({
|
windows.push({
|
||||||
label: "Premium",
|
label: "Premium",
|
||||||
usedPercent: clampPercent(100 - (remaining ?? 0)),
|
usedPercent: clampPercent(100 - (remaining ?? 0)),
|
||||||
@@ -367,7 +424,7 @@ async function fetchGeminiUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as GeminiUsageResponse;
|
||||||
const quotas: Record<string, number> = {};
|
const quotas: Record<string, number> = {};
|
||||||
|
|
||||||
for (const bucket of data.buckets || []) {
|
for (const bucket of data.buckets || []) {
|
||||||
@@ -395,7 +452,10 @@ async function fetchGeminiUsage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasPro) {
|
if (hasPro) {
|
||||||
windows.push({ label: "Pro", usedPercent: clampPercent((1 - proMin) * 100) });
|
windows.push({
|
||||||
|
label: "Pro",
|
||||||
|
usedPercent: clampPercent((1 - proMin) * 100),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (hasFlash) {
|
if (hasFlash) {
|
||||||
windows.push({
|
windows.push({
|
||||||
@@ -445,7 +505,7 @@ async function fetchCodexUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as CodexUsageResponse;
|
||||||
const windows: UsageWindow[] = [];
|
const windows: UsageWindow[] = [];
|
||||||
|
|
||||||
if (data.rate_limit?.primary_window) {
|
if (data.rate_limit?.primary_window) {
|
||||||
@@ -513,7 +573,7 @@ async function fetchZaiUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (await res.json()) as any;
|
const data = (await res.json()) as ZaiUsageResponse;
|
||||||
if (!data.success || data.code !== 200) {
|
if (!data.success || data.code !== 200) {
|
||||||
return {
|
return {
|
||||||
provider: "zai",
|
provider: "zai",
|
||||||
@@ -562,8 +622,7 @@ async function fetchZaiUsage(
|
|||||||
|
|
||||||
function resolveZaiApiKey(): string | undefined {
|
function resolveZaiApiKey(): string | undefined {
|
||||||
const envDirect =
|
const envDirect =
|
||||||
process.env.ZAI_API_KEY?.trim() ||
|
process.env.ZAI_API_KEY?.trim() || process.env.Z_AI_API_KEY?.trim();
|
||||||
process.env.Z_AI_API_KEY?.trim();
|
|
||||||
if (envDirect) return envDirect;
|
if (envDirect) return envDirect;
|
||||||
|
|
||||||
const envResolved = resolveEnvApiKey("zai");
|
const envResolved = resolveEnvApiKey("zai");
|
||||||
@@ -571,8 +630,7 @@ function resolveZaiApiKey(): string | undefined {
|
|||||||
|
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const key =
|
const key =
|
||||||
getCustomProviderApiKey(cfg, "zai") ||
|
getCustomProviderApiKey(cfg, "zai") || getCustomProviderApiKey(cfg, "z-ai");
|
||||||
getCustomProviderApiKey(cfg, "z-ai");
|
|
||||||
if (key) return key;
|
if (key) return key;
|
||||||
|
|
||||||
const store = ensureAuthProfileStore();
|
const store = ensureAuthProfileStore();
|
||||||
@@ -637,9 +695,7 @@ async function resolveOAuthToken(params: {
|
|||||||
? (cred as { accountId?: string }).accountId
|
? (cred as { accountId?: string }).accountId
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -648,9 +704,7 @@ async function resolveOAuthToken(params: {
|
|||||||
function resolveOAuthProviders(): UsageProviderId[] {
|
function resolveOAuthProviders(): UsageProviderId[] {
|
||||||
const store = ensureAuthProfileStore();
|
const store = ensureAuthProfileStore();
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
const providers = usageProviders.filter((provider) =>
|
const providers = usageProviders.filter((provider) => provider !== "zai");
|
||||||
provider !== "zai",
|
|
||||||
);
|
|
||||||
return providers.filter((provider) => {
|
return providers.filter((provider) => {
|
||||||
const profiles = listProfilesForProvider(store, provider).filter((id) => {
|
const profiles = listProfilesForProvider(store, provider).filter((id) => {
|
||||||
const cred = store.profiles[id];
|
const cred = store.profiles[id];
|
||||||
@@ -659,7 +713,9 @@ function resolveOAuthProviders(): UsageProviderId[] {
|
|||||||
if (profiles.length > 0) return true;
|
if (profiles.length > 0) return true;
|
||||||
const normalized = normalizeProviderId(provider);
|
const normalized = normalizeProviderId(provider);
|
||||||
const configuredProfiles = Object.entries(cfg.auth?.profiles ?? {})
|
const configuredProfiles = Object.entries(cfg.auth?.profiles ?? {})
|
||||||
.filter(([, profile]) => normalizeProviderId(profile.provider) === normalized)
|
.filter(
|
||||||
|
([, profile]) => normalizeProviderId(profile.provider) === normalized,
|
||||||
|
)
|
||||||
.map(([id]) => id)
|
.map(([id]) => id)
|
||||||
.filter((id) => store.profiles[id]?.type === "oauth");
|
.filter((id) => store.profiles[id]?.type === "oauth");
|
||||||
return configuredProfiles.length > 0;
|
return configuredProfiles.length > 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user