openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
150 lines (149 loc) • 5.97 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { s as asFiniteNumber, y as parseDateStringTimestampMs } from "./number-coercion-CuVhdCBs.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { m as readProviderJsonResponse } from "./provider-http-errors-DmewLLH7.mjs";
import { r as clampPercent, t as PROVIDER_LABELS } from "./provider-usage.shared-B2Q7uCr7.mjs";
import { a as readUsageJson, i as fetchUsageJson, n as buildUsageHttpErrorSnapshot, r as fetchJson } from "./provider-usage.fetch.shared-DCZdARu5.mjs";
//#region src/infra/provider-usage.fetch.claude.ts
function readClaudeWindow(value, label, includeReset = false) {
const window = isRecord(value) ? value : void 0;
const utilization = asFiniteNumber(window?.utilization);
if (utilization === void 0) return;
return {
label,
usedPercent: clampPercent(utilization),
...includeReset ? { resetAt: parseDateStringTimestampMs(window?.resets_at) } : {}
};
}
function parseClaudeUsage(value) {
const usage = isRecord(value) ? value : {};
const windows = [
readClaudeWindow(usage.five_hour, "5h", true),
readClaudeWindow(usage.seven_day, "Week", true),
readClaudeWindow(usage.seven_day_sonnet, "Sonnet") ?? readClaudeWindow(usage.seven_day_opus, "Opus")
].filter((window) => window !== void 0);
const knownLabels = new Set(windows.map((window) => window.label.toLowerCase()));
for (const limit of Array.isArray(usage.limits) ? usage.limits : []) {
if (!isRecord(limit)) continue;
const percent = asFiniteNumber(limit.percent);
if (limit.is_active === false || percent === void 0) continue;
const scope = isRecord(limit.scope) ? limit.scope : void 0;
const model = scope && isRecord(scope.model) ? scope.model : void 0;
const label = normalizeOptionalString(model?.display_name) ?? normalizeOptionalString(model?.id);
if (!label || knownLabels.has(label.toLowerCase())) continue;
knownLabels.add(label.toLowerCase());
windows.push({
label,
usedPercent: clampPercent(percent),
resetAt: parseDateStringTimestampMs(limit.resets_at)
});
}
const extra = isRecord(usage.extra_usage) ? usage.extra_usage : void 0;
return {
windows,
extra_usage: extra ? {
is_enabled: extra.is_enabled === true,
monthly_limit: asFiniteNumber(extra.monthly_limit),
used_credits: asFiniteNumber(extra.used_credits),
utilization: asFiniteNumber(extra.utilization),
currency: normalizeOptionalString(extra.currency)
} : void 0
};
}
function buildClaudeUsageWindows(usage, options) {
const { extra_usage: extraUsage } = usage;
if (!options?.skipExtraUsage && extraUsage?.is_enabled === true && extraUsage.utilization !== void 0) return [...usage.windows, {
label: "Extra usage",
usedPercent: clampPercent(extraUsage.utilization)
}];
return usage.windows;
}
function resolveClaudeWebSessionKey() {
const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ?? process.env.CLAUDE_WEB_SESSION_KEY?.trim();
if (direct?.startsWith("sk-ant-")) return direct;
const cookieHeader = process.env.CLAUDE_WEB_COOKIE?.trim();
if (!cookieHeader) return;
const value = cookieHeader.replace(/^cookie:\s*/i, "").match(/(?:^|;\s*)sessionKey=([^;\s]+)/i)?.[1]?.trim();
return value?.startsWith("sk-ant-") ? value : void 0;
}
async function fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn) {
const headers = {
Cookie: `sessionKey=${sessionKey}`,
Accept: "application/json"
};
const parsedOrgs = await fetchUsageJson({
provider: "anthropic",
url: "https://claude.ai/api/organizations",
init: { headers },
timeoutMs,
fetchFn
});
if (!parsedOrgs.ok) return null;
const firstOrg = Array.isArray(parsedOrgs.data) ? parsedOrgs.data[0] : void 0;
const orgId = isRecord(firstOrg) ? normalizeOptionalString(firstOrg.uuid) : void 0;
if (!orgId) return null;
const parsedUsage = await fetchUsageJson({
provider: "anthropic",
url: `https://claude.ai/api/organizations/${orgId}/usage`,
init: { headers },
timeoutMs,
fetchFn
});
if (!parsedUsage.ok) return null;
const windows = buildClaudeUsageWindows(parseClaudeUsage(parsedUsage.data));
if (windows.length === 0) return null;
return {
provider: "anthropic",
displayName: PROVIDER_LABELS.anthropic,
windows
};
}
async function fetchClaudeUsage(token, timeoutMs, fetchFn) {
const res = await fetchJson("https://api.anthropic.com/api/oauth/usage", { headers: {
Authorization: `Bearer ${token}`,
"User-Agent": "openclaw",
Accept: "application/json",
"anthropic-version": "2023-06-01",
"anthropic-beta": "oauth-2025-04-20"
} }, timeoutMs, fetchFn);
if (!res.ok) {
let message;
try {
const raw = (await readProviderJsonResponse(res, "Anthropic usage error"))?.error?.message;
if (typeof raw === "string" && raw.trim()) message = raw.trim();
} catch {}
if (res.status === 403 && message?.includes("scope requirement user:profile")) {
const sessionKey = resolveClaudeWebSessionKey();
if (sessionKey) {
const web = await fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn);
if (web) return web;
}
}
return buildUsageHttpErrorSnapshot({
provider: "anthropic",
status: res.status,
message
});
}
const parsed = await readUsageJson("anthropic", res);
if (!parsed.ok) return parsed.snapshot;
const usage = parseClaudeUsage(parsed.data);
const extra = usage.extra_usage;
const unit = extra?.currency?.toUpperCase() || "USD";
const billing = extra?.is_enabled === true && extra.used_credits !== void 0 && extra.used_credits >= 0 && extra.monthly_limit !== void 0 && extra.monthly_limit >= 0 ? [{
type: "budget",
used: extra.used_credits / 100,
limit: extra.monthly_limit / 100,
unit,
period: "month"
}] : void 0;
const windows = buildClaudeUsageWindows(usage, { skipExtraUsage: Boolean(billing) });
return {
provider: "anthropic",
displayName: PROVIDER_LABELS.anthropic,
windows,
...billing ? { billing } : {}
};
}
//#endregion
export { fetchClaudeUsage };