@acarmisc/backstage-plugin-litellm-backend 0.27.0 → 0.28.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/config.d.ts CHANGED
@@ -463,6 +463,8 @@ export interface Config {
463
463
  * reaches:
464
464
  * - managers of the team (members of its metadata.owning_group, with
465
465
  * team management enabled);
466
+ * - team leads (members of the catalog group `<team_alias><leadGroupSuffix>`,
467
+ * when leadGroupSuffix is set);
466
468
  * - members of the team holding one of `viewerRoles` in it;
467
469
  * - members of the team granted `litellm.team.usage.read` (only
468
470
  * consulted when permission.enabled is true).
@@ -480,6 +482,13 @@ export interface Config {
480
482
  * @default []
481
483
  */
482
484
  viewerRoles?: string[];
485
+
486
+ /**
487
+ * Suffix of a catalog group named `<team_alias><suffix>` whose members
488
+ * are team leads (e.g. "-leads" -> group:default/abstrid-leads). For
489
+ * teams synced without metadata.owning_group. Unset: no lead access.
490
+ */
491
+ leadGroupSuffix?: string;
483
492
  };
484
493
  };
485
494
 
package/dist/index.cjs.js CHANGED
@@ -9449,7 +9449,8 @@ function readMemberBreakdownConfig(config) {
9449
9449
  return {
9450
9450
  enabled: config.getOptionalBoolean("litellm.teamUsage.memberBreakdown.enabled") ?? false,
9451
9451
  viewerRoles: config.getOptionalStringArray("litellm.teamUsage.memberBreakdown.viewerRoles") ?? [],
9452
- permissionEnabled: config.getOptionalBoolean("permission.enabled") ?? false
9452
+ permissionEnabled: config.getOptionalBoolean("permission.enabled") ?? false,
9453
+ leadGroupSuffix: config.getOptionalString("litellm.teamUsage.memberBreakdown.leadGroupSuffix") || void 0
9453
9454
  };
9454
9455
  }
9455
9456
  function memberEmails(team, keys) {
@@ -11521,6 +11522,22 @@ function registerTeamUsageRoutes(router, ctx) {
11521
11522
  teamBudgetVisibility
11522
11523
  } = ctx;
11523
11524
  const requireUser = createRequireUser(ctx);
11525
+ async function isTeamManager(team, tokenEntityRef) {
11526
+ const owningGroup = typeof team.metadata?.owning_group === "string" ? team.metadata.owning_group : void 0;
11527
+ if (ctx.teamMgmtEnabled && owningGroup && await isUserMemberOfGroup(tokenEntityRef, owningGroup, catalogClient, auth, logger)) {
11528
+ return true;
11529
+ }
11530
+ const suffix = ctx.memberBreakdownCfg.leadGroupSuffix;
11531
+ if (!suffix || !team.team_alias) return false;
11532
+ const slug = team.team_alias.toLowerCase().replace(/[^a-z0-9\-_.]/g, "-");
11533
+ return isUserMemberOfGroup(
11534
+ tokenEntityRef,
11535
+ `group:default/${slug}${suffix}`,
11536
+ catalogClient,
11537
+ auth,
11538
+ logger
11539
+ );
11540
+ }
11524
11541
  router.get("/teams/:teamId/usage", requireUser, async (req, res) => {
11525
11542
  try {
11526
11543
  const { teamId } = req.params;
@@ -11533,20 +11550,11 @@ function registerTeamUsageRoutes(router, ctx) {
11533
11550
  const userInfo = await getProvisionedUser(ctx, res);
11534
11551
  const isMember = userInfo?.teams?.includes(teamId) ?? false;
11535
11552
  let isManager = false;
11536
- const managerCheckNeeded = ctx.teamMgmtEnabled && (!isMember || teamBudgetVisibility.hideTeamBudgetForMembers !== teamBudgetVisibility.hideTeamBudgetForManagers);
11553
+ const managerCheckNeeded = (ctx.teamMgmtEnabled || !!ctx.memberBreakdownCfg.leadGroupSuffix) && (!isMember || teamBudgetVisibility.hideTeamBudgetForMembers !== teamBudgetVisibility.hideTeamBudgetForManagers);
11537
11554
  if (managerCheckNeeded) {
11538
11555
  try {
11539
11556
  const team = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
11540
- const owningGroup = typeof team.metadata?.owning_group === "string" ? team.metadata.owning_group : void 0;
11541
- if (owningGroup) {
11542
- isManager = await isUserMemberOfGroup(
11543
- tokenEntityRef,
11544
- owningGroup,
11545
- catalogClient,
11546
- auth,
11547
- logger
11548
- );
11549
- }
11557
+ isManager = await isTeamManager(team, tokenEntityRef);
11550
11558
  } catch (err) {
11551
11559
  const message2 = err instanceof Error ? err.message : String(err);
11552
11560
  logger.warn(`Team-manager check failed: ${message2}`);
@@ -11600,8 +11608,7 @@ function registerTeamUsageRoutes(router, ctx) {
11600
11608
  }
11601
11609
  throw err;
11602
11610
  }
11603
- const owningGroup = typeof team.metadata?.owning_group === "string" ? team.metadata.owning_group : void 0;
11604
- const isManager = ctx.teamMgmtEnabled && !!owningGroup && await isUserMemberOfGroup(tokenEntityRef, owningGroup, catalogClient, auth, logger);
11611
+ const isManager = await isTeamManager(team, tokenEntityRef);
11605
11612
  if (!isMember && !isManager) {
11606
11613
  res.status(404).json({ error: "Team not found" });
11607
11614
  return null;