@acarmisc/backstage-plugin-litellm-backend 0.12.0 → 0.13.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/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { resolveUserId, resolveUserProfile, toLiteLLMUserId, getOrProvisionUser,
7
7
  export type { BackstageUserProfile } from './provisioning';
8
8
  export { readTeamAdminConfig, isTeamManagementEnabled, assertTeamAdmin, validateTeamWriteInput, validateTeamPatchInput, } from './teamAdmin';
9
9
  export type { TeamAdminConfig, TeamAdminCheck, TeamWriteInput, TeamWriteValidation, TeamPatchValidation } from './teamAdmin';
10
+ export { readTeamBudgetVisibility, redactTeamBudget, redactTeamUsage, teamBudgetStatusFor, } from './teamBudgetVisibility';
11
+ export type { TeamBudgetVisibility, TeamBudgetStatus } from './teamBudgetVisibility';
10
12
  export { KeycloakJWTVerifier, newDefaultVerifier, readBridgeConfig, resolveBridgeUserId, getOrProvisionUserFromClaims, bridgeListKeys, bridgeGenerateKey, } from './bridge';
11
13
  export type { BridgeClaims, TokenVerifier, BridgeConfig, KeycloakJWTVerifierOptions, } from './bridge';
12
14
  export { litellmPermissions, litellmKeyCreatePermission, litellmKeyRevokePermission, litellmKeyManagePermission, litellmAuditReadPermission, litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission, litellmTeamDeletePermission, } from './permissions';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = exports.litellmPermissions = exports.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = exports.validateTeamPatchInput = exports.validateTeamWriteInput = exports.assertTeamAdmin = exports.isTeamManagementEnabled = exports.readTeamAdminConfig = exports.ProvisioningError = exports.resolveUserRole = exports.applyRoleOverrides = exports.readRoleConfigs = exports.readProvisioningDefaults = exports.provisionUser = exports.getOrProvisionUser = exports.toLiteLLMUserId = exports.resolveUserProfile = exports.resolveUserId = exports.LiteLLMClient = exports.createRouter = exports.default = exports.litellmPlugin = void 0;
17
+ exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = exports.litellmPermissions = exports.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = exports.teamBudgetStatusFor = exports.redactTeamUsage = exports.redactTeamBudget = exports.readTeamBudgetVisibility = exports.validateTeamPatchInput = exports.validateTeamWriteInput = exports.assertTeamAdmin = exports.isTeamManagementEnabled = exports.readTeamAdminConfig = exports.ProvisioningError = exports.resolveUserRole = exports.applyRoleOverrides = exports.readRoleConfigs = exports.readProvisioningDefaults = exports.provisionUser = exports.getOrProvisionUser = exports.toLiteLLMUserId = exports.resolveUserProfile = exports.resolveUserId = exports.LiteLLMClient = exports.createRouter = exports.default = exports.litellmPlugin = void 0;
18
18
  var plugin_1 = require("./plugin");
19
19
  Object.defineProperty(exports, "litellmPlugin", { enumerable: true, get: function () { return plugin_1.litellmPlugin; } });
20
20
  var plugin_2 = require("./plugin");
@@ -41,6 +41,11 @@ Object.defineProperty(exports, "isTeamManagementEnabled", { enumerable: true, ge
41
41
  Object.defineProperty(exports, "assertTeamAdmin", { enumerable: true, get: function () { return teamAdmin_1.assertTeamAdmin; } });
42
42
  Object.defineProperty(exports, "validateTeamWriteInput", { enumerable: true, get: function () { return teamAdmin_1.validateTeamWriteInput; } });
43
43
  Object.defineProperty(exports, "validateTeamPatchInput", { enumerable: true, get: function () { return teamAdmin_1.validateTeamPatchInput; } });
44
+ var teamBudgetVisibility_1 = require("./teamBudgetVisibility");
45
+ Object.defineProperty(exports, "readTeamBudgetVisibility", { enumerable: true, get: function () { return teamBudgetVisibility_1.readTeamBudgetVisibility; } });
46
+ Object.defineProperty(exports, "redactTeamBudget", { enumerable: true, get: function () { return teamBudgetVisibility_1.redactTeamBudget; } });
47
+ Object.defineProperty(exports, "redactTeamUsage", { enumerable: true, get: function () { return teamBudgetVisibility_1.redactTeamUsage; } });
48
+ Object.defineProperty(exports, "teamBudgetStatusFor", { enumerable: true, get: function () { return teamBudgetVisibility_1.teamBudgetStatusFor; } });
44
49
  var bridge_1 = require("./bridge");
45
50
  Object.defineProperty(exports, "KeycloakJWTVerifier", { enumerable: true, get: function () { return bridge_1.KeycloakJWTVerifier; } });
46
51
  Object.defineProperty(exports, "newDefaultVerifier", { enumerable: true, get: function () { return bridge_1.newDefaultVerifier; } });
package/dist/openapi.js CHANGED
@@ -66,6 +66,10 @@ exports.openApiSpec = {
66
66
  allowUnlimitedBudget: { type: 'boolean' },
67
67
  teamRequired: { type: 'boolean' },
68
68
  } },
69
+ display: { type: 'object', properties: {
70
+ hideTeamBudgetForMembers: { type: 'boolean' },
71
+ hideTeamBudgetForManagers: { type: 'boolean' },
72
+ } },
69
73
  } } } },
70
74
  },
71
75
  },
@@ -169,6 +173,9 @@ exports.openApiSpec = {
169
173
  get: {
170
174
  tags: ['Teams'],
171
175
  summary: 'List teams the current user belongs to',
176
+ description: 'When litellm.display.hideTeamBudgetForMembers is set, max_budget/spend ' +
177
+ 'are stripped and budget_pct/budget_status/budget_hidden carry the ' +
178
+ 'consumption level instead.',
172
179
  responses: { '200': { description: 'Array of teams' } },
173
180
  },
174
181
  },
@@ -176,6 +183,8 @@ exports.openApiSpec = {
176
183
  get: {
177
184
  tags: ['Teams'],
178
185
  summary: 'Usage metrics for a team',
186
+ description: 'Spend fields are zeroed when the caller is subject to team budget ' +
187
+ 'hiding (member or manager flag, whichever applies to them).',
179
188
  parameters: [
180
189
  { name: 'teamId', in: 'path', required: true, schema: { type: 'string' } },
181
190
  { name: 'start_date', in: 'query', required: true, schema: { type: 'string' } },
package/dist/router.js CHANGED
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "ProvisioningError", { enumerable: true, get: fun
45
45
  const bridge_1 = require("./bridge");
46
46
  const permissions_1 = require("./permissions");
47
47
  const teamAdmin_1 = require("./teamAdmin");
48
+ const teamBudgetVisibility_1 = require("./teamBudgetVisibility");
48
49
  const teamCreateInFlight = new Map();
49
50
  /** Backoff schedule for `withTeamFetchRetry` — 3 retries over ~1.3s total. */
50
51
  const TEAM_FETCH_RETRY_DELAYS_MS = [100, 300, 900];
@@ -102,6 +103,7 @@ async function createRouter(options) {
102
103
  const teamMgmtEnabled = (0, teamAdmin_1.isTeamManagementEnabled)(config);
103
104
  const objectPermsEnabled = (0, teamAdmin_1.isObjectPermissionsEnabled)(config);
104
105
  const teamAdminCfg = (0, teamAdmin_1.readTeamAdminConfig)(config);
106
+ const teamBudgetVisibility = (0, teamBudgetVisibility_1.readTeamBudgetVisibility)(config);
105
107
  const catalogClient = options.catalogClient ?? new catalog_client_1.CatalogClient({ discoveryApi: discovery });
106
108
  if (provisioningEnabled) {
107
109
  logger.info(`LiteLLM auto-provisioning enabled — defaults: budget=$${provisioningDefaults.maxBudget}/${provisioningDefaults.budgetDuration}, models=${provisioningDefaults.models.length
@@ -111,6 +113,10 @@ async function createRouter(options) {
111
113
  if (config.getOptional('litellm.teamAdmin') && !teamMgmtEnabled) {
112
114
  logger.warn('litellm.teamAdmin is configured but team management is disabled — set permission.enabled: true and litellm.teamAdmin.group to enable it.');
113
115
  }
116
+ if (teamBudgetVisibility.hideTeamBudgetForMembers ||
117
+ teamBudgetVisibility.hideTeamBudgetForManagers) {
118
+ logger.info(`LiteLLM team budget hiding enabled — members: ${teamBudgetVisibility.hideTeamBudgetForMembers}, managers: ${teamBudgetVisibility.hideTeamBudgetForManagers}`);
119
+ }
114
120
  const router = (0, express_1.Router)();
115
121
  // JSON body parser. Without this, every POST/PUT endpoint sees an empty
116
122
  // req.body. Backstage's httpRouter does not apply a body parser at the
@@ -131,6 +137,10 @@ async function createRouter(options) {
131
137
  allowUnlimitedBudget: teamAdminCfg.allowUnlimitedBudget,
132
138
  objectPermissionsEnabled: objectPermsEnabled,
133
139
  },
140
+ display: {
141
+ hideTeamBudgetForMembers: teamBudgetVisibility.hideTeamBudgetForMembers,
142
+ hideTeamBudgetForManagers: teamBudgetVisibility.hideTeamBudgetForManagers,
143
+ },
134
144
  });
135
145
  });
136
146
  // Self-hosted OpenAPI 3.1 contract — lets integrators read a spec instead
@@ -552,7 +562,12 @@ async function createRouter(options) {
552
562
  logger.warn(`Failed to fetch team ${teamId} after retries: ${err.message}`);
553
563
  return null;
554
564
  })));
555
- res.json(teams.filter(Boolean));
565
+ const list = teams.filter(Boolean);
566
+ // Member surface: strip dollar amounts when the operator hides them
567
+ // from members. Managers who need the numbers use /teams/managed.
568
+ res.json(teamBudgetVisibility.hideTeamBudgetForMembers
569
+ ? list.map(teamBudgetVisibility_1.redactTeamBudget)
570
+ : list);
556
571
  }
557
572
  catch (error) {
558
573
  if (error instanceof provisioning_1.ProvisioningError) {
@@ -656,7 +671,10 @@ async function createRouter(options) {
656
671
  teamCreateInFlight.set(key, createPromise);
657
672
  try {
658
673
  const result = await createPromise;
659
- res.json(result);
674
+ // Manager surface: redact dollars when hidden even from managers.
675
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
676
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(result)
677
+ : result);
660
678
  }
661
679
  catch (err) {
662
680
  sendTeamError(err, res);
@@ -704,7 +722,9 @@ async function createRouter(options) {
704
722
  teamId,
705
723
  owningGroup,
706
724
  });
707
- res.json(r);
725
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
726
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(r)
727
+ : r);
708
728
  }
709
729
  catch (err) {
710
730
  sendTeamError(err, res);
@@ -772,7 +792,10 @@ async function createRouter(options) {
772
792
  // the caller's group set and filter with includes().
773
793
  const owned = all.filter(t => typeof t.metadata?.owning_group === 'string' &&
774
794
  t.metadata.owning_group === teamAdminCfg.group);
775
- res.json(owned);
795
+ // Manager surface: strip dollar amounts when hidden even from managers.
796
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
797
+ ? owned.map(teamBudgetVisibility_1.redactTeamBudget)
798
+ : owned);
776
799
  }
777
800
  catch (err) {
778
801
  sendTeamError(err, res);
@@ -924,7 +947,9 @@ async function createRouter(options) {
924
947
  owningGroup,
925
948
  });
926
949
  const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
927
- res.json(updated);
950
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
951
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(updated)
952
+ : updated);
928
953
  }
929
954
  catch (err) {
930
955
  sendTeamError(err, res);
@@ -959,7 +984,9 @@ async function createRouter(options) {
959
984
  owningGroup,
960
985
  });
961
986
  const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
962
- res.json(updated);
987
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
988
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(updated)
989
+ : updated);
963
990
  }
964
991
  catch (err) {
965
992
  sendTeamError(err, res);
@@ -1047,7 +1074,9 @@ async function createRouter(options) {
1047
1074
  owningGroup,
1048
1075
  vector_stores: requested,
1049
1076
  });
1050
- res.json(updated);
1077
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
1078
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(updated)
1079
+ : updated);
1051
1080
  }
1052
1081
  catch (err) {
1053
1082
  sendTeamError(err, res);
@@ -1136,7 +1165,9 @@ async function createRouter(options) {
1136
1165
  before,
1137
1166
  after: requested,
1138
1167
  });
1139
- res.json(updated);
1168
+ res.json(teamBudgetVisibility.hideTeamBudgetForManagers
1169
+ ? (0, teamBudgetVisibility_1.redactTeamBudget)(updated)
1170
+ : updated);
1140
1171
  }
1141
1172
  catch (err) {
1142
1173
  sendTeamError(err, res);
@@ -1151,7 +1182,26 @@ async function createRouter(options) {
1151
1182
  return;
1152
1183
  }
1153
1184
  const usage = await client.getTeamUsage(teamId, start_date, end_date);
1154
- res.json(usage);
1185
+ // Spend figures would let a client derive a hidden budget
1186
+ // (budget = spend / pct), so they follow the same hiding flags.
1187
+ // Which flag applies depends on whether the caller is a team manager;
1188
+ // catalog failures fail closed to the member rule.
1189
+ let hide = teamBudgetVisibility.hideTeamBudgetForMembers;
1190
+ if (teamAdminCfg.group) {
1191
+ try {
1192
+ const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
1193
+ const isManager = !!tokenEntityRef &&
1194
+ (await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, teamAdminCfg.group, catalogClient, auth, logger));
1195
+ hide = isManager
1196
+ ? teamBudgetVisibility.hideTeamBudgetForManagers
1197
+ : teamBudgetVisibility.hideTeamBudgetForMembers;
1198
+ }
1199
+ catch (err) {
1200
+ logger.warn(`Team-manager check failed, hiding team spend: ${err.message}`);
1201
+ hide = teamBudgetVisibility.hideTeamBudgetForMembers;
1202
+ }
1203
+ }
1204
+ res.json(hide ? (0, teamBudgetVisibility_1.redactTeamUsage)(usage) : usage);
1155
1205
  }
1156
1206
  catch (error) {
1157
1207
  logger.error('Failed to fetch team usage', error);
@@ -0,0 +1,48 @@
1
+ import { Config } from '@backstage/config';
2
+ import { TeamInfo, UsageMetrics } from './types';
3
+ /**
4
+ * Independent "hide team budget" switches for the two team audiences.
5
+ *
6
+ * - `hideTeamBudgetForMembers` redacts dollar amounts on the member surface:
7
+ * `GET /teams`, the Teams cards, and the `TEAM` section of the budget widget.
8
+ * Members still see the consumption level (percent of cap + ok/near/over
9
+ * status + reset window).
10
+ * - `hideTeamBudgetForManagers` does the same on the manager surface:
11
+ * `GET /teams/managed`, team write responses, and the ManageTeamDialog
12
+ * budget field (which becomes write-only).
13
+ *
14
+ * Both default to false (current behavior: dollars visible). Splitting them
15
+ * lets an operator hide real budgets from regular members while team managers
16
+ * keep the numbers they need to govern — or hide from both.
17
+ */
18
+ export interface TeamBudgetVisibility {
19
+ hideTeamBudgetForMembers: boolean;
20
+ hideTeamBudgetForManagers: boolean;
21
+ }
22
+ /**
23
+ * Consumption status derived from spend vs. cap. Mirrors the frontend
24
+ * `budgetTone` thresholds (80% near, 100% over) so both sides agree.
25
+ */
26
+ export type TeamBudgetStatus = 'ok' | 'near' | 'over';
27
+ export declare function teamBudgetStatusFor(spend: number, budget: number): TeamBudgetStatus;
28
+ export declare function readTeamBudgetVisibility(config: Config): TeamBudgetVisibility;
29
+ /**
30
+ * Strips dollar amounts from a team record while preserving the consumption
31
+ * signal. `max_budget` is dropped, `spend` is zeroed (the field is required
32
+ * downstream, so it cannot be omitted — `budget_hidden: true` marks the
33
+ * record as redacted), and `budget_pct` + `budget_status` carry the level of
34
+ * consumption. `budget_duration`, models, members, and rate limits are kept:
35
+ * none of them reveal dollars.
36
+ *
37
+ * Teams without a positive budget (unlimited) carry no dollars to hide and
38
+ * are returned untouched.
39
+ */
40
+ export declare function redactTeamBudget(team: TeamInfo): TeamInfo;
41
+ /**
42
+ * Strips dollar amounts from team usage metrics while keeping the
43
+ * non-monetary signal (tokens, requests). Returning zeroed spend (rather
44
+ * than omitting the fields) keeps the response shape stable for existing
45
+ * clients; the frontend hides spend charts whenever the owning team record
46
+ * is redacted.
47
+ */
48
+ export declare function redactTeamUsage(usage: UsageMetrics): UsageMetrics;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.teamBudgetStatusFor = teamBudgetStatusFor;
4
+ exports.readTeamBudgetVisibility = readTeamBudgetVisibility;
5
+ exports.redactTeamBudget = redactTeamBudget;
6
+ exports.redactTeamUsage = redactTeamUsage;
7
+ function teamBudgetStatusFor(spend, budget) {
8
+ if (budget > 0 && spend >= budget)
9
+ return 'over';
10
+ if (budget > 0 && spend >= budget * 0.8)
11
+ return 'near';
12
+ return 'ok';
13
+ }
14
+ function readTeamBudgetVisibility(config) {
15
+ return {
16
+ hideTeamBudgetForMembers: config.getOptionalBoolean('litellm.display.hideTeamBudgetForMembers') ?? false,
17
+ hideTeamBudgetForManagers: config.getOptionalBoolean('litellm.display.hideTeamBudgetForManagers') ?? false,
18
+ };
19
+ }
20
+ /**
21
+ * Strips dollar amounts from a team record while preserving the consumption
22
+ * signal. `max_budget` is dropped, `spend` is zeroed (the field is required
23
+ * downstream, so it cannot be omitted — `budget_hidden: true` marks the
24
+ * record as redacted), and `budget_pct` + `budget_status` carry the level of
25
+ * consumption. `budget_duration`, models, members, and rate limits are kept:
26
+ * none of them reveal dollars.
27
+ *
28
+ * Teams without a positive budget (unlimited) carry no dollars to hide and
29
+ * are returned untouched.
30
+ */
31
+ function redactTeamBudget(team) {
32
+ const budget = team.max_budget ?? 0;
33
+ if (budget <= 0)
34
+ return team;
35
+ const spend = team.spend ?? 0;
36
+ const pct = Math.min(100, Math.max(0, (spend / budget) * 100));
37
+ const { max_budget: _dropped, ...rest } = team;
38
+ return {
39
+ ...rest,
40
+ spend: 0,
41
+ budget_pct: pct,
42
+ budget_status: teamBudgetStatusFor(spend, budget),
43
+ budget_hidden: true,
44
+ };
45
+ }
46
+ /**
47
+ * Strips dollar amounts from team usage metrics while keeping the
48
+ * non-monetary signal (tokens, requests). Returning zeroed spend (rather
49
+ * than omitting the fields) keeps the response shape stable for existing
50
+ * clients; the frontend hides spend charts whenever the owning team record
51
+ * is redacted.
52
+ */
53
+ function redactTeamUsage(usage) {
54
+ const zeroSpend = (p) => ({
55
+ ...p,
56
+ spend: 0,
57
+ });
58
+ const zeroTotalSpend = (p) => ({
59
+ ...p,
60
+ total_spend: 0,
61
+ });
62
+ return {
63
+ ...usage,
64
+ total_spend: 0,
65
+ usage_by_model: Object.fromEntries(Object.entries(usage.usage_by_model ?? {}).map(([k, v]) => [
66
+ k,
67
+ zeroTotalSpend(v),
68
+ ])),
69
+ usage_by_key: Object.fromEntries(Object.entries(usage.usage_by_key ?? {}).map(([k, v]) => [
70
+ k,
71
+ zeroTotalSpend(v),
72
+ ])),
73
+ daily_usage: (usage.daily_usage ?? []).map(zeroSpend),
74
+ daily_by_model: (usage.daily_by_model ?? []).map(zeroSpend),
75
+ };
76
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/types.ts"],
4
- "sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n /** Spend-reset period for max_budget, e.g. \"30d\". */\n budget_duration?: string;\n /** Backstage-computed: true when the user is a member of litellm.audit.group */\n can_view_audit?: boolean;\n}\n\nexport interface TeamMember {\n user_id: string;\n user_email?: string;\n role: 'admin' | 'user';\n}\n\nexport interface TeamObjectPermission {\n /** Vector store ids/names attached to the team. */\n vector_stores?: string[];\n /** MCP server ids/names attached to the team. */\n mcp_servers?: string[];\n /** MCP access group names attached to the team. */\n mcp_access_groups?: string[];\n}\n\nexport interface TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n /** Spend-reset period for max_budget, e.g. \"30d\". */\n budget_duration?: string;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n /** Arbitrary metadata stored on the team record. */\n metadata?: Record<string, unknown>;\n /** Knowledge bases (vector stores) and MCP servers attached to the team. */\n object_permission?: TeamObjectPermission;\n /** Whether the team is blocked/deactivated. */\n blocked?: boolean;\n /** Max budget available to individual team members (if set per-member). */\n team_member_budget?: number;\n}\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: TeamObjectPermission;\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_id: string;\n blocked?: boolean;\n}\n\nexport interface CreateTeamResponse {\n team_id: string;\n team_alias?: string;\n}\n\nexport interface VirtualKey {\n key: string;\n token: string;\n key_alias?: string;\n created_at: string;\n expires_at?: string;\n spend: number;\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n user_id?: string;\n blocked?: boolean;\n}\n\n/**\n * Shape of a single entry inside LiteLLM's `/user/info` `keys` array.\n * Differs from VirtualKey: uses `expires` (not `expires_at`), exposes\n * both a hashed `token` and a masked `key_name`, and fields are nullable\n * rather than optional.\n */\nexport interface LiteLLMUserKey {\n token: string;\n key_name?: string;\n key_alias?: string | null;\n spend?: number;\n expires?: string | null;\n models?: string[];\n tpm_limit?: number | null;\n rpm_limit?: number | null;\n max_budget?: number | null;\n budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n}\n\nexport interface ModelInfo {\n model_name: string;\n mode: string;\n supports_function_calling?: boolean;\n supports_vision?: boolean;\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n max_input_tokens?: number;\n max_output_tokens?: number;\n /** Access group names this model belongs to (litellm.model_info.access_groups). A team's `models` list can reference a group name instead of a literal model_name. */\n access_groups?: string[];\n}\n\nexport interface UsageModelBreakdown {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageKeyBreakdown {\n key_alias?: string;\n team_id?: string | null;\n models: string[];\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyPoint {\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyModelPoint {\n date: string;\n model: string;\n spend: number;\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n usage_by_model: Record<string, UsageModelBreakdown>;\n usage_by_key: Record<string, UsageKeyBreakdown>;\n daily_usage: UsageDailyPoint[];\n daily_by_model: UsageDailyModelPoint[];\n}\n\nexport interface GenerateKeyRequest {\n alias?: string;\n models?: string[];\n duration?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n user_id?: string;\n team_id?: string;\n key_type?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface UpdateKeyRequest {\n key: string;\n key_alias?: string;\n models?: string[];\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n team_id?: string;\n duration?: string;\n}\n\nexport interface GenerateKeyResponse {\n key: string;\n key_alias?: string;\n expires_at?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n}\n\nexport interface DeleteKeyRequest {\n keys: string[];\n}\n\nexport interface LiteLLMConfig {\n baseUrl: string;\n masterKey: string;\n}\n\nexport interface ProvisioningDefaults {\n maxBudget: number;\n budgetDuration: string;\n models: string[];\n teams: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n /**\n * LiteLLM user role applied on /user/new. Defaults to \"internal_user\"\n * which grants self-service Create/Delete/View on the user's own keys.\n * Valid values: proxy_admin, proxy_admin_viewer, internal_user,\n * internal_user_viewer, team.\n */\n userRole?: string;\n metadata: Record<string, string>;\n}\n\nexport interface RoleConfig {\n group: string;\n maxBudget?: number;\n budgetDuration?: string;\n models?: string[];\n teams?: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n userRole?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: string;\n user_alias?: string;\n user_role?: string;\n max_budget?: number;\n budget_duration?: string;\n models?: string[];\n teams?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, string>;\n auto_create_key?: boolean;\n}\n\nexport interface CreateUserResponse {\n user_id: string;\n user_email?: string;\n max_budget?: number;\n models?: string[];\n teams?: string[];\n}\n\nexport interface AuditLogEntry {\n id: string;\n updated_at: string;\n changed_by?: string;\n changed_by_api_key?: string;\n action?: string;\n table_name?: string;\n object_id?: string;\n before_value?: Record<string, unknown> | null;\n updated_values?: Record<string, unknown> | null;\n}\n\nexport interface PaginatedAuditLogs {\n audit_logs: AuditLogEntry[];\n total: number;\n page: number;\n page_size: number;\n total_pages: number;\n}\n\nexport interface AuditLogsParams {\n page?: number;\n page_size?: number;\n start_date?: string;\n end_date?: string;\n action?: string;\n table_name?: string;\n changed_by?: string;\n sort_by?: string;\n sort_order?: 'asc' | 'desc';\n}\n"],
4
+ "sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n /** Spend-reset period for max_budget, e.g. \"30d\". */\n budget_duration?: string;\n /** Backstage-computed: true when the user is a member of litellm.audit.group */\n can_view_audit?: boolean;\n}\n\nexport interface TeamMember {\n user_id: string;\n user_email?: string;\n role: 'admin' | 'user';\n}\n\nexport interface TeamObjectPermission {\n /** Vector store ids/names attached to the team. */\n vector_stores?: string[];\n /** MCP server ids/names attached to the team. */\n mcp_servers?: string[];\n /** MCP access group names attached to the team. */\n mcp_access_groups?: string[];\n}\n\nexport interface TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n /** Spend-reset period for max_budget, e.g. \"30d\". */\n budget_duration?: string;\n spend: number;\n /**\n * Share of max_budget consumed (0-100), present only on redacted records.\n * Set by the backend when litellm.display.hideTeamBudgetFor* is enabled so\n * clients can render the consumption level without dollar amounts.\n */\n budget_pct?: number;\n /** ok | near (>=80%) | over (>=100%), present only on redacted records. */\n budget_status?: 'ok' | 'near' | 'over';\n /** True when max_budget/spend were stripped by budget hiding. */\n budget_hidden?: boolean;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n /** Arbitrary metadata stored on the team record. */\n metadata?: Record<string, unknown>;\n /** Knowledge bases (vector stores) and MCP servers attached to the team. */\n object_permission?: TeamObjectPermission;\n /** Whether the team is blocked/deactivated. */\n blocked?: boolean;\n /** Max budget available to individual team members (if set per-member). */\n team_member_budget?: number;\n}\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: TeamObjectPermission;\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_id: string;\n blocked?: boolean;\n}\n\nexport interface CreateTeamResponse {\n team_id: string;\n team_alias?: string;\n}\n\nexport interface VirtualKey {\n key: string;\n token: string;\n key_alias?: string;\n created_at: string;\n expires_at?: string;\n spend: number;\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n user_id?: string;\n blocked?: boolean;\n}\n\n/**\n * Shape of a single entry inside LiteLLM's `/user/info` `keys` array.\n * Differs from VirtualKey: uses `expires` (not `expires_at`), exposes\n * both a hashed `token` and a masked `key_name`, and fields are nullable\n * rather than optional.\n */\nexport interface LiteLLMUserKey {\n token: string;\n key_name?: string;\n key_alias?: string | null;\n spend?: number;\n expires?: string | null;\n models?: string[];\n tpm_limit?: number | null;\n rpm_limit?: number | null;\n max_budget?: number | null;\n budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n}\n\nexport interface ModelInfo {\n model_name: string;\n mode: string;\n supports_function_calling?: boolean;\n supports_vision?: boolean;\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n max_input_tokens?: number;\n max_output_tokens?: number;\n /** Access group names this model belongs to (litellm.model_info.access_groups). A team's `models` list can reference a group name instead of a literal model_name. */\n access_groups?: string[];\n}\n\nexport interface UsageModelBreakdown {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageKeyBreakdown {\n key_alias?: string;\n team_id?: string | null;\n models: string[];\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyPoint {\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyModelPoint {\n date: string;\n model: string;\n spend: number;\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n usage_by_model: Record<string, UsageModelBreakdown>;\n usage_by_key: Record<string, UsageKeyBreakdown>;\n daily_usage: UsageDailyPoint[];\n daily_by_model: UsageDailyModelPoint[];\n}\n\nexport interface GenerateKeyRequest {\n alias?: string;\n models?: string[];\n duration?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n user_id?: string;\n team_id?: string;\n key_type?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface UpdateKeyRequest {\n key: string;\n key_alias?: string;\n models?: string[];\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n team_id?: string;\n duration?: string;\n}\n\nexport interface GenerateKeyResponse {\n key: string;\n key_alias?: string;\n expires_at?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n}\n\nexport interface DeleteKeyRequest {\n keys: string[];\n}\n\nexport interface LiteLLMConfig {\n baseUrl: string;\n masterKey: string;\n}\n\nexport interface ProvisioningDefaults {\n maxBudget: number;\n budgetDuration: string;\n models: string[];\n teams: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n /**\n * LiteLLM user role applied on /user/new. Defaults to \"internal_user\"\n * which grants self-service Create/Delete/View on the user's own keys.\n * Valid values: proxy_admin, proxy_admin_viewer, internal_user,\n * internal_user_viewer, team.\n */\n userRole?: string;\n metadata: Record<string, string>;\n}\n\nexport interface RoleConfig {\n group: string;\n maxBudget?: number;\n budgetDuration?: string;\n models?: string[];\n teams?: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n userRole?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: string;\n user_alias?: string;\n user_role?: string;\n max_budget?: number;\n budget_duration?: string;\n models?: string[];\n teams?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, string>;\n auto_create_key?: boolean;\n}\n\nexport interface CreateUserResponse {\n user_id: string;\n user_email?: string;\n max_budget?: number;\n models?: string[];\n teams?: string[];\n}\n\nexport interface AuditLogEntry {\n id: string;\n updated_at: string;\n changed_by?: string;\n changed_by_api_key?: string;\n action?: string;\n table_name?: string;\n object_id?: string;\n before_value?: Record<string, unknown> | null;\n updated_values?: Record<string, unknown> | null;\n}\n\nexport interface PaginatedAuditLogs {\n audit_logs: AuditLogEntry[];\n total: number;\n page: number;\n page_size: number;\n total_pages: number;\n}\n\nexport interface AuditLogsParams {\n page?: number;\n page_size?: number;\n start_date?: string;\n end_date?: string;\n action?: string;\n table_name?: string;\n changed_by?: string;\n sort_by?: string;\n sort_order?: 'asc' | 'desc';\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
package/dist/types.d.ts CHANGED
@@ -34,6 +34,16 @@ export interface TeamInfo {
34
34
  /** Spend-reset period for max_budget, e.g. "30d". */
35
35
  budget_duration?: string;
36
36
  spend: number;
37
+ /**
38
+ * Share of max_budget consumed (0-100), present only on redacted records.
39
+ * Set by the backend when litellm.display.hideTeamBudgetFor* is enabled so
40
+ * clients can render the consumption level without dollar amounts.
41
+ */
42
+ budget_pct?: number;
43
+ /** ok | near (>=80%) | over (>=100%), present only on redacted records. */
44
+ budget_status?: 'ok' | 'near' | 'over';
45
+ /** True when max_budget/spend were stripped by budget hiding. */
46
+ budget_hidden?: boolean;
37
47
  members_with_roles?: TeamMember[];
38
48
  models?: string[];
39
49
  tpm_limit?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",