switchroom 0.21.1 → 0.21.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20351,6 +20351,8 @@ var EXTERNAL_SPEND_TOP_N = 3;
20351
20351
  var DEFAULT_LITELLM_BASE = "http://127.0.0.1:4010";
20352
20352
  var EXTERNAL_SPEND_CACHE_TTL_MS = 90000;
20353
20353
  var EXTERNAL_SPEND_FETCH_TIMEOUT_MS = 5000;
20354
+ var EXTERNAL_SPEND_PAGE_SIZE = 1000;
20355
+ var EXTERNAL_SPEND_MAX_PAGES = 50;
20354
20356
  var LITELLM_MASTER_KEY_STATE_BASENAME = "litellm-master-key";
20355
20357
  var BARE_EXTERNAL_NEEDLES = [
20356
20358
  "gpt-oss",
@@ -20447,54 +20449,72 @@ function summarizeExternalSpend(days, now = new Date) {
20447
20449
  const top = Object.entries(byModel).filter(([, usd]) => usd > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, EXTERNAL_SPEND_TOP_N).map(([name, usd]) => ({ label: shortModelLabel(name), usd }));
20448
20450
  return { day24hUsd, day7dUsd, top };
20449
20451
  }
20450
- function normalizeSpendLogRows(body) {
20451
- if (Array.isArray(body))
20452
- return body;
20453
- if (body && typeof body === "object") {
20454
- const data = body.data;
20455
- if (Array.isArray(data))
20456
- return data;
20452
+ function normalizeDailyActivityRows(body) {
20453
+ const results = body && typeof body === "object" && Array.isArray(body.results) ? body.results : [];
20454
+ const rows = [];
20455
+ for (const day of results) {
20456
+ if (!day || typeof day !== "object")
20457
+ continue;
20458
+ const models = {};
20459
+ const breakdown = day.breakdown?.models ?? {};
20460
+ for (const [name, entry] of Object.entries(breakdown)) {
20461
+ const raw = entry?.metrics?.spend;
20462
+ const n = typeof raw === "number" ? raw : Number(raw);
20463
+ if (!Number.isFinite(n))
20464
+ continue;
20465
+ models[name] = (models[name] ?? 0) + n;
20466
+ }
20467
+ rows.push({ startTime: day.date, models });
20457
20468
  }
20458
- return [];
20469
+ return rows;
20459
20470
  }
20460
20471
  async function fetchAndSummarizeExternalSpend(opts) {
20461
20472
  const now = opts.now ?? new Date;
20462
20473
  const base = (opts.baseUrl ?? DEFAULT_LITELLM_BASE).replace(/\/+$/, "");
20463
20474
  const today = utcDateString(now);
20464
20475
  const start = addUtcDays(today, -6);
20465
- const end = addUtcDays(today, 1);
20466
- const url = `${base}/spend/logs?start_date=${encodeURIComponent(start)}` + `&end_date=${encodeURIComponent(end)}`;
20467
20476
  const fetchImpl = opts.fetchImpl ?? fetch;
20468
20477
  const timeoutMs = opts.timeoutMs ?? EXTERNAL_SPEND_FETCH_TIMEOUT_MS;
20469
- const ac = new AbortController;
20470
- const timer = setTimeout(() => ac.abort(), timeoutMs);
20471
- try {
20472
- const res = await fetchImpl(url, {
20473
- method: "GET",
20474
- headers: {
20475
- Authorization: `Bearer ${opts.adminKey}`,
20476
- Accept: "application/json"
20477
- },
20478
- signal: ac.signal
20479
- });
20480
- if (!res.ok) {
20481
- console.warn(`external-spend: LiteLLM /spend/logs returned HTTP ${res.status} — ` + `External /usage row will be blank (check master key / proxy)`);
20482
- return null;
20483
- }
20484
- let body;
20478
+ const rows = [];
20479
+ for (let page = 1;page <= EXTERNAL_SPEND_MAX_PAGES; page++) {
20480
+ const url = `${base}/user/daily/activity?start_date=${encodeURIComponent(start)}` + `&end_date=${encodeURIComponent(today)}` + `&page=${page}&page_size=${EXTERNAL_SPEND_PAGE_SIZE}&timezone=0`;
20481
+ const ac = new AbortController;
20482
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
20485
20483
  try {
20486
- body = await res.json();
20484
+ const res = await fetchImpl(url, {
20485
+ method: "GET",
20486
+ headers: {
20487
+ Authorization: `Bearer ${opts.adminKey}`,
20488
+ Accept: "application/json"
20489
+ },
20490
+ signal: ac.signal
20491
+ });
20492
+ if (!res.ok) {
20493
+ console.warn(`external-spend: LiteLLM /user/daily/activity returned HTTP ${res.status} — ` + `External /usage row will be blank (check master key / proxy)`);
20494
+ return null;
20495
+ }
20496
+ let body;
20497
+ try {
20498
+ body = await res.json();
20499
+ } catch (err) {
20500
+ console.warn(`external-spend: failed to parse /user/daily/activity JSON: ${err?.message ?? err}`);
20501
+ return null;
20502
+ }
20503
+ rows.push(...normalizeDailyActivityRows(body));
20504
+ const meta = body && typeof body === "object" ? body.metadata : undefined;
20505
+ if (!meta?.has_more)
20506
+ break;
20507
+ if (page === EXTERNAL_SPEND_MAX_PAGES) {
20508
+ console.warn(`external-spend: LiteLLM /user/daily/activity still reports has_more at ` + `page ${EXTERNAL_SPEND_MAX_PAGES} (pagination cap) — External /usage ` + `total is under-reported (collected ${rows.length} day rows)`);
20509
+ }
20487
20510
  } catch (err) {
20488
- console.warn(`external-spend: failed to parse /spend/logs JSON: ${err?.message ?? err}`);
20511
+ console.warn(`external-spend: LiteLLM /user/daily/activity fetch failed: ${err?.message ?? err}`);
20489
20512
  return null;
20513
+ } finally {
20514
+ clearTimeout(timer);
20490
20515
  }
20491
- return summarizeExternalSpend(normalizeSpendLogRows(body), now);
20492
- } catch (err) {
20493
- console.warn(`external-spend: LiteLLM /spend/logs fetch failed: ${err?.message ?? err}`);
20494
- return null;
20495
- } finally {
20496
- clearTimeout(timer);
20497
20516
  }
20517
+ return summarizeExternalSpend(rows, now);
20498
20518
  }
20499
20519
 
20500
20520
  // src/auth/broker/mirror-write.ts