@acarmisc/backstage-plugin-litellm 0.43.0 → 0.44.0

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.
package/dist/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type FetchApi, type DiscoveryApi } from '@backstage/frontend-plugin-api';
2
- import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamMemberUsage, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, SpendLogsParams, PaginatedSpendLogs, LiteLlmConfig, QualityResponse, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
2
+ import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamMemberUsage, TeamKeyUsage, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, SpendLogsParams, PaginatedSpendLogs, LiteLlmConfig, QualityResponse, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
3
3
  export declare class ApiError extends Error {
4
4
  body: unknown;
5
5
  status: number;
@@ -49,6 +49,8 @@ export interface LiteLlmApiInterface {
49
49
  getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
50
50
  /** Per-member breakdown; rejects (403/404) when the caller may not see it. */
51
51
  getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
52
+ /** A team's keys with per-key usage; same access rules as the per-member breakdown. */
53
+ getTeamKeyUsage(teamId: string, startDate: string, endDate: string): Promise<TeamKeyUsage>;
52
54
  getAuditLogs(params: AuditLogsParams): Promise<PaginatedAuditLogs>;
53
55
  getSpendLogs(params: SpendLogsParams): Promise<PaginatedSpendLogs>;
54
56
  /** Langfuse quality summary; `{configured:false}` when Langfuse is off. */
@@ -120,6 +122,7 @@ export declare class LiteLlmApi implements LiteLlmApiInterface {
120
122
  getUsage(startDate: string, endDate: string): Promise<UsageMetrics>;
121
123
  getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
122
124
  getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
125
+ getTeamKeyUsage(teamId: string, startDate: string, endDate: string): Promise<TeamKeyUsage>;
123
126
  getSpendLogs(params: SpendLogsParams): Promise<PaginatedSpendLogs>;
124
127
  getQuality(params: SpendLogsParams): Promise<QualityResponse>;
125
128
  getConfig(): Promise<LiteLlmConfig>;
@@ -1,5 +1,5 @@
1
1
  import { FC } from 'react';
2
- import { McpServerInfo, TeamInfo, TeamMemberUsage, UsageMetrics, VectorStoreInfo } from '../types';
2
+ import { McpServerInfo, TeamInfo, TeamKeyUsage, TeamMemberUsage, UsageMetrics, VectorStoreInfo } from '../types';
3
3
  /**
4
4
  * Per-member usage for the period, sorted by spend. Dollars are replaced by
5
5
  * the share of team spend when the backend hides them.
@@ -7,6 +7,13 @@ import { McpServerInfo, TeamInfo, TeamMemberUsage, UsageMetrics, VectorStoreInfo
7
7
  export declare const MemberUsageTable: FC<{
8
8
  usage: TeamMemberUsage;
9
9
  }>;
10
+ /**
11
+ * A team's keys with the period's usage, for team leads. The backend never
12
+ * sends secrets; dollars are dropped when it hides them.
13
+ */
14
+ export declare const TeamKeyUsageTable: FC<{
15
+ usage: TeamKeyUsage;
16
+ }>;
10
17
  interface TeamUsageProps {
11
18
  teams: TeamInfo[];
12
19
  loading: boolean;
@@ -15,6 +22,9 @@ interface TeamUsageProps {
15
22
  /** Per-member breakdown for a team; null hides the section. */
16
23
  getTeamMemberUsage?: (teamId: string) => TeamMemberUsage | null;
17
24
  getTeamMemberUsageLoading?: (teamId: string) => boolean;
25
+ /** Per-key usage for a team; null hides the section. */
26
+ getTeamKeyUsage?: (teamId: string) => TeamKeyUsage | null;
27
+ getTeamKeyUsageLoading?: (teamId: string) => boolean;
18
28
  canManage?: boolean;
19
29
  onEditTeam?: (team: TeamInfo) => void;
20
30
  /** When true (with `onCreateTeam`), a "Create Team" action renders in the section header. */
package/dist/index.cjs.js CHANGED
@@ -321,6 +321,12 @@ var init_api = __esm({
321
321
  end_date: endDate
322
322
  });
323
323
  }
324
+ async getTeamKeyUsage(teamId, startDate, endDate) {
325
+ return this.get(`/teams/${encodeURIComponent(teamId)}/usage/keys`, {
326
+ start_date: startDate,
327
+ end_date: endDate
328
+ });
329
+ }
324
330
  async getSpendLogs(params) {
325
331
  const strParams = {
326
332
  start_date: params.startDate,
@@ -5054,7 +5060,7 @@ function budgetStatValue(hidden, budget) {
5054
5060
  if (budget > 0) return fmtUsd2(budget);
5055
5061
  return "Unlimited";
5056
5062
  }
5057
- var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_Tooltip5, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, monoSx, MemberUsageTable, TeamCard, TeamUsage;
5063
+ var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_Tooltip5, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, monoSx, MemberUsageTable, TeamKeyUsageTable, TeamCard, TeamUsage;
5058
5064
  var init_TeamUsage = __esm({
5059
5065
  "src/components/TeamUsage.tsx"() {
5060
5066
  "use strict";
@@ -5117,7 +5123,47 @@ var init_TeamUsage = __esm({
5117
5123
  }) })
5118
5124
  ] }) });
5119
5125
  };
5120
- TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand, vectorStores, mcpServers }) => {
5126
+ TeamKeyUsageTable = ({ usage }) => {
5127
+ const hidden = usage.budget_hidden === true;
5128
+ if (!usage.keys.length) {
5129
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", color: "text.secondary", children: "This team has no keys." });
5130
+ }
5131
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableContainer4.default, { component: import_Paper8.default, variant: "outlined", sx: { borderRadius: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Table4.default, { size: "small", sx: dataTableSx, "aria-label": "Keys by member", children: [
5132
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableHead4.default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_TableRow4.default, { children: [
5133
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: "Key" }),
5134
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: "Owner" }),
5135
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: "Created" }),
5136
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: "Status" }),
5137
+ !hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Spend" }),
5138
+ !hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Budget" }),
5139
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Tokens" }),
5140
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Requests" })
5141
+ ] }) }),
5142
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableBody4.default, { children: usage.keys.map((k) => {
5143
+ const owner = k.display_name ?? k.user_email ?? k.user_id;
5144
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_TableRow4.default, { children: [
5145
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_TableCell4.default, { children: [
5146
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", children: k.key_alias ?? k.key }),
5147
+ k.key_alias && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "caption", color: "text.secondary", sx: monoSx, children: k.key })
5148
+ ] }),
5149
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: owner ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", color: "text.secondary", children: "\u2014" }) }),
5150
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: fmtDateShort(k.created_at) }),
5151
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5152
+ StatusPill,
5153
+ {
5154
+ label: k.blocked ? "Blocked" : "Active",
5155
+ tone: k.blocked ? "danger" : "success"
5156
+ }
5157
+ ) }),
5158
+ !hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: fmtUsd2(k.spend) }),
5159
+ !hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: k.max_budget === void 0 ? "Unlimited" : fmtUsd2(k.max_budget) }),
5160
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: fmtCompact(k.total_tokens) }),
5161
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: k.api_requests.toLocaleString() })
5162
+ ] }, k.key);
5163
+ }) })
5164
+ ] }) });
5165
+ };
5166
+ TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, keyUsage, keyUsageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand, vectorStores, mcpServers }) => {
5121
5167
  const [expanded, setExpanded] = (0, import_react10.useState)(false);
5122
5168
  const chart = useChartTheme();
5123
5169
  const handleExpandChange = (newExpanded) => {
@@ -5320,6 +5366,10 @@ var init_TeamUsage = __esm({
5320
5366
  sectionLabel("Usage by member"),
5321
5367
  memberUsage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MemberUsageTable, { usage: memberUsage }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Box14.default, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_CircularProgress4.default, { size: 22 }) })
5322
5368
  ] }),
5369
+ (keyUsageLoading || keyUsage) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Box14.default, { mb: 2.5, children: [
5370
+ sectionLabel("Keys by member"),
5371
+ keyUsage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TeamKeyUsageTable, { usage: keyUsage }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Box14.default, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_CircularProgress4.default, { size: 22 }) })
5372
+ ] }),
5323
5373
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Box14.default, { children: [
5324
5374
  sectionLabel("Members"),
5325
5375
  team.members_with_roles?.length ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -5376,6 +5426,8 @@ var init_TeamUsage = __esm({
5376
5426
  getTeamUsageLoading,
5377
5427
  getTeamMemberUsage,
5378
5428
  getTeamMemberUsageLoading,
5429
+ getTeamKeyUsage,
5430
+ getTeamKeyUsageLoading,
5379
5431
  canManage,
5380
5432
  onEditTeam,
5381
5433
  canCreate,
@@ -5439,6 +5491,8 @@ var init_TeamUsage = __esm({
5439
5491
  usageLoading: getTeamUsageLoading(team.team_id),
5440
5492
  memberUsage: getTeamMemberUsage?.(team.team_id) ?? null,
5441
5493
  memberUsageLoading: getTeamMemberUsageLoading?.(team.team_id) ?? false,
5494
+ keyUsage: getTeamKeyUsage?.(team.team_id) ?? null,
5495
+ keyUsageLoading: getTeamKeyUsageLoading?.(team.team_id) ?? false,
5442
5496
  canManage,
5443
5497
  onEditTeam,
5444
5498
  canManageMembers: canManageMembers?.(team),
@@ -6385,6 +6439,8 @@ var init_LiteLLMPage = __esm({
6385
6439
  const [teamUsageLoading, setTeamUsageLoading] = (0, import_react13.useState)({});
6386
6440
  const [memberUsageCache, setMemberUsageCache] = (0, import_react13.useState)({});
6387
6441
  const [memberUsageLoading, setMemberUsageLoading] = (0, import_react13.useState)({});
6442
+ const [keyUsageCache, setKeyUsageCache] = (0, import_react13.useState)({});
6443
+ const [keyUsageLoading, setKeyUsageLoading] = (0, import_react13.useState)({});
6388
6444
  const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
6389
6445
  () => api.getUserInfo(),
6390
6446
  [api]
@@ -6451,6 +6507,8 @@ var init_LiteLLMPage = __esm({
6451
6507
  setTeamUsageLoading({});
6452
6508
  setMemberUsageCache({});
6453
6509
  setMemberUsageLoading({});
6510
+ setKeyUsageCache({});
6511
+ setKeyUsageLoading({});
6454
6512
  }, [setDateRange, setCurrentPreset]);
6455
6513
  const loadTeamUsage = (0, import_react13.useCallback)(async (teamId) => {
6456
6514
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
@@ -6480,6 +6538,20 @@ var init_LiteLLMPage = __esm({
6480
6538
  setMemberUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6481
6539
  }
6482
6540
  }, [api, dateRange, memberUsageCache, memberUsageLoading]);
6541
+ const loadKeyUsage = (0, import_react13.useCallback)(async (teamId) => {
6542
+ if (keyUsageCache[teamId] !== void 0 || keyUsageLoading[teamId]) return;
6543
+ setKeyUsageLoading((prev) => ({ ...prev, [teamId]: true }));
6544
+ try {
6545
+ const startDate = toLocalDay(dateRange.start);
6546
+ const endDate = toLocalDay(dateRange.end);
6547
+ const data = await api.getTeamKeyUsage(teamId, startDate, endDate);
6548
+ setKeyUsageCache((prev) => ({ ...prev, [teamId]: data }));
6549
+ } catch {
6550
+ setKeyUsageCache((prev) => ({ ...prev, [teamId]: null }));
6551
+ } finally {
6552
+ setKeyUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6553
+ }
6554
+ }, [api, dateRange, keyUsageCache, keyUsageLoading]);
6483
6555
  const allowedModels = (0, import_react13.useMemo)(() => {
6484
6556
  if (!allModels?.length) return [];
6485
6557
  const userModels = userInfo?.models;
@@ -6777,6 +6849,8 @@ var init_LiteLLMPage = __esm({
6777
6849
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
6778
6850
  getTeamMemberUsage: (teamId) => memberUsageCache[teamId] ?? null,
6779
6851
  getTeamMemberUsageLoading: (teamId) => memberUsageLoading[teamId] ?? false,
6852
+ getTeamKeyUsage: (teamId) => keyUsageCache[teamId] ?? null,
6853
+ getTeamKeyUsageLoading: (teamId) => keyUsageLoading[teamId] ?? false,
6780
6854
  canManage: teamMgmtEnabled && canManageTeam && !readOnly,
6781
6855
  onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
6782
6856
  canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,
@@ -6788,7 +6862,10 @@ var init_LiteLLMPage = __esm({
6788
6862
  readOnly,
6789
6863
  onTeamExpand: (team) => {
6790
6864
  loadTeamUsage(team.team_id);
6791
- if (liteLlmConfig?.teamUsage?.memberBreakdown) loadMemberUsage(team.team_id);
6865
+ if (liteLlmConfig?.teamUsage?.memberBreakdown) {
6866
+ loadMemberUsage(team.team_id);
6867
+ loadKeyUsage(team.team_id);
6868
+ }
6792
6869
  }
6793
6870
  }
6794
6871
  );