@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/config.d.ts +28 -0
- package/dist/index.cjs.js +126 -9
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -1
- package/dist/openapi.js +9 -0
- package/dist/router.js +59 -9
- package/dist/teamBudgetVisibility.d.ts +48 -0
- package/dist/teamBudgetVisibility.js +76 -0
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/config.d.ts
CHANGED
|
@@ -248,6 +248,34 @@ export interface Config {
|
|
|
248
248
|
};
|
|
249
249
|
};
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Display controls for team budget amounts in the frontend.
|
|
253
|
+
* Each flag independently hides real dollar budgets while the backend
|
|
254
|
+
* still exposes the consumption level (percent of cap + ok/near/over
|
|
255
|
+
* status + reset window). Enforcement is server-side: the backend
|
|
256
|
+
* redacts max_budget/spend from the corresponding endpoints, so the
|
|
257
|
+
* dollars are not merely hidden in CSS.
|
|
258
|
+
*/
|
|
259
|
+
display?: {
|
|
260
|
+
/**
|
|
261
|
+
* Hide dollar budgets from regular team members (GET /teams, Teams
|
|
262
|
+
* cards, TEAM section of the budget widget). Team managers still see
|
|
263
|
+
* dollars unless hideTeamBudgetForManagers is also set.
|
|
264
|
+
* @default false
|
|
265
|
+
* @visibility frontend
|
|
266
|
+
*/
|
|
267
|
+
hideTeamBudgetForMembers?: boolean;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Hide dollar budgets even from team managers (GET /teams/managed,
|
|
271
|
+
* team write responses, ManageTeamDialog budget field which becomes
|
|
272
|
+
* write-only). Useful when budgets are finance-sensitive.
|
|
273
|
+
* @default false
|
|
274
|
+
* @visibility frontend
|
|
275
|
+
*/
|
|
276
|
+
hideTeamBudgetForManagers?: boolean;
|
|
277
|
+
};
|
|
278
|
+
|
|
251
279
|
bridge?: {
|
|
252
280
|
/**
|
|
253
281
|
* When true, mount the /bridge/keys, /bridge/keys (POST), /bridge/models
|
package/dist/index.cjs.js
CHANGED
|
@@ -8002,10 +8002,14 @@ __export(index_exports, {
|
|
|
8002
8002
|
readProvisioningDefaults: () => readProvisioningDefaults,
|
|
8003
8003
|
readRoleConfigs: () => readRoleConfigs,
|
|
8004
8004
|
readTeamAdminConfig: () => readTeamAdminConfig,
|
|
8005
|
+
readTeamBudgetVisibility: () => readTeamBudgetVisibility,
|
|
8006
|
+
redactTeamBudget: () => redactTeamBudget,
|
|
8007
|
+
redactTeamUsage: () => redactTeamUsage,
|
|
8005
8008
|
resolveBridgeUserId: () => resolveBridgeUserId,
|
|
8006
8009
|
resolveUserId: () => resolveUserId,
|
|
8007
8010
|
resolveUserProfile: () => resolveUserProfile,
|
|
8008
8011
|
resolveUserRole: () => resolveUserRole,
|
|
8012
|
+
teamBudgetStatusFor: () => teamBudgetStatusFor,
|
|
8009
8013
|
toLiteLLMUserId: () => toLiteLLMUserId,
|
|
8010
8014
|
validateTeamPatchInput: () => validateTeamPatchInput,
|
|
8011
8015
|
validateTeamWriteInput: () => validateTeamWriteInput
|
|
@@ -8629,6 +8633,10 @@ var openApiSpec = {
|
|
|
8629
8633
|
keyGeneration: { type: "object", properties: {
|
|
8630
8634
|
allowUnlimitedBudget: { type: "boolean" },
|
|
8631
8635
|
teamRequired: { type: "boolean" }
|
|
8636
|
+
} },
|
|
8637
|
+
display: { type: "object", properties: {
|
|
8638
|
+
hideTeamBudgetForMembers: { type: "boolean" },
|
|
8639
|
+
hideTeamBudgetForManagers: { type: "boolean" }
|
|
8632
8640
|
} }
|
|
8633
8641
|
} } } }
|
|
8634
8642
|
}
|
|
@@ -8733,6 +8741,7 @@ var openApiSpec = {
|
|
|
8733
8741
|
get: {
|
|
8734
8742
|
tags: ["Teams"],
|
|
8735
8743
|
summary: "List teams the current user belongs to",
|
|
8744
|
+
description: "When litellm.display.hideTeamBudgetForMembers is set, max_budget/spend are stripped and budget_pct/budget_status/budget_hidden carry the consumption level instead.",
|
|
8736
8745
|
responses: { "200": { description: "Array of teams" } }
|
|
8737
8746
|
}
|
|
8738
8747
|
},
|
|
@@ -8740,6 +8749,7 @@ var openApiSpec = {
|
|
|
8740
8749
|
get: {
|
|
8741
8750
|
tags: ["Teams"],
|
|
8742
8751
|
summary: "Usage metrics for a team",
|
|
8752
|
+
description: "Spend fields are zeroed when the caller is subject to team budget hiding (member or manager flag, whichever applies to them).",
|
|
8743
8753
|
parameters: [
|
|
8744
8754
|
{ name: "teamId", in: "path", required: true, schema: { type: "string" } },
|
|
8745
8755
|
{ name: "start_date", in: "query", required: true, schema: { type: "string" } },
|
|
@@ -10827,6 +10837,65 @@ function validateTeamPatchInput(input, cfg) {
|
|
|
10827
10837
|
return { ok: true, value: result };
|
|
10828
10838
|
}
|
|
10829
10839
|
|
|
10840
|
+
// src/teamBudgetVisibility.ts
|
|
10841
|
+
function teamBudgetStatusFor(spend, budget) {
|
|
10842
|
+
if (budget > 0 && spend >= budget) return "over";
|
|
10843
|
+
if (budget > 0 && spend >= budget * 0.8) return "near";
|
|
10844
|
+
return "ok";
|
|
10845
|
+
}
|
|
10846
|
+
function readTeamBudgetVisibility(config) {
|
|
10847
|
+
return {
|
|
10848
|
+
hideTeamBudgetForMembers: config.getOptionalBoolean(
|
|
10849
|
+
"litellm.display.hideTeamBudgetForMembers"
|
|
10850
|
+
) ?? false,
|
|
10851
|
+
hideTeamBudgetForManagers: config.getOptionalBoolean(
|
|
10852
|
+
"litellm.display.hideTeamBudgetForManagers"
|
|
10853
|
+
) ?? false
|
|
10854
|
+
};
|
|
10855
|
+
}
|
|
10856
|
+
function redactTeamBudget(team) {
|
|
10857
|
+
const budget = team.max_budget ?? 0;
|
|
10858
|
+
if (budget <= 0) return team;
|
|
10859
|
+
const spend = team.spend ?? 0;
|
|
10860
|
+
const pct = Math.min(100, Math.max(0, spend / budget * 100));
|
|
10861
|
+
const { max_budget: _dropped, ...rest } = team;
|
|
10862
|
+
return {
|
|
10863
|
+
...rest,
|
|
10864
|
+
spend: 0,
|
|
10865
|
+
budget_pct: pct,
|
|
10866
|
+
budget_status: teamBudgetStatusFor(spend, budget),
|
|
10867
|
+
budget_hidden: true
|
|
10868
|
+
};
|
|
10869
|
+
}
|
|
10870
|
+
function redactTeamUsage(usage) {
|
|
10871
|
+
const zeroSpend = (p) => ({
|
|
10872
|
+
...p,
|
|
10873
|
+
spend: 0
|
|
10874
|
+
});
|
|
10875
|
+
const zeroTotalSpend = (p) => ({
|
|
10876
|
+
...p,
|
|
10877
|
+
total_spend: 0
|
|
10878
|
+
});
|
|
10879
|
+
return {
|
|
10880
|
+
...usage,
|
|
10881
|
+
total_spend: 0,
|
|
10882
|
+
usage_by_model: Object.fromEntries(
|
|
10883
|
+
Object.entries(usage.usage_by_model ?? {}).map(([k, v]) => [
|
|
10884
|
+
k,
|
|
10885
|
+
zeroTotalSpend(v)
|
|
10886
|
+
])
|
|
10887
|
+
),
|
|
10888
|
+
usage_by_key: Object.fromEntries(
|
|
10889
|
+
Object.entries(usage.usage_by_key ?? {}).map(([k, v]) => [
|
|
10890
|
+
k,
|
|
10891
|
+
zeroTotalSpend(v)
|
|
10892
|
+
])
|
|
10893
|
+
),
|
|
10894
|
+
daily_usage: (usage.daily_usage ?? []).map(zeroSpend),
|
|
10895
|
+
daily_by_model: (usage.daily_by_model ?? []).map(zeroSpend)
|
|
10896
|
+
};
|
|
10897
|
+
}
|
|
10898
|
+
|
|
10830
10899
|
// src/router.ts
|
|
10831
10900
|
var teamCreateInFlight = /* @__PURE__ */ new Map();
|
|
10832
10901
|
var TEAM_FETCH_RETRY_DELAYS_MS = [100, 300, 900];
|
|
@@ -10863,6 +10932,7 @@ async function createRouter(options) {
|
|
|
10863
10932
|
const teamMgmtEnabled = isTeamManagementEnabled(config);
|
|
10864
10933
|
const objectPermsEnabled = isObjectPermissionsEnabled(config);
|
|
10865
10934
|
const teamAdminCfg = readTeamAdminConfig(config);
|
|
10935
|
+
const teamBudgetVisibility = readTeamBudgetVisibility(config);
|
|
10866
10936
|
const catalogClient = options.catalogClient ?? new import_catalog_client.CatalogClient({ discoveryApi: discovery });
|
|
10867
10937
|
if (provisioningEnabled) {
|
|
10868
10938
|
logger.info(
|
|
@@ -10874,6 +10944,11 @@ async function createRouter(options) {
|
|
|
10874
10944
|
"litellm.teamAdmin is configured but team management is disabled \u2014 set permission.enabled: true and litellm.teamAdmin.group to enable it."
|
|
10875
10945
|
);
|
|
10876
10946
|
}
|
|
10947
|
+
if (teamBudgetVisibility.hideTeamBudgetForMembers || teamBudgetVisibility.hideTeamBudgetForManagers) {
|
|
10948
|
+
logger.info(
|
|
10949
|
+
`LiteLLM team budget hiding enabled \u2014 members: ${teamBudgetVisibility.hideTeamBudgetForMembers}, managers: ${teamBudgetVisibility.hideTeamBudgetForManagers}`
|
|
10950
|
+
);
|
|
10951
|
+
}
|
|
10877
10952
|
const router = (0, import_express.Router)();
|
|
10878
10953
|
router.use(import_express.default.json());
|
|
10879
10954
|
router.get("/health", (_req, res) => {
|
|
@@ -10888,6 +10963,10 @@ async function createRouter(options) {
|
|
|
10888
10963
|
maxBudgetCeiling: teamAdminCfg.maxBudgetCeiling,
|
|
10889
10964
|
allowUnlimitedBudget: teamAdminCfg.allowUnlimitedBudget,
|
|
10890
10965
|
objectPermissionsEnabled: objectPermsEnabled
|
|
10966
|
+
},
|
|
10967
|
+
display: {
|
|
10968
|
+
hideTeamBudgetForMembers: teamBudgetVisibility.hideTeamBudgetForMembers,
|
|
10969
|
+
hideTeamBudgetForManagers: teamBudgetVisibility.hideTeamBudgetForManagers
|
|
10891
10970
|
}
|
|
10892
10971
|
});
|
|
10893
10972
|
});
|
|
@@ -11287,7 +11366,10 @@ async function createRouter(options) {
|
|
|
11287
11366
|
})
|
|
11288
11367
|
)
|
|
11289
11368
|
);
|
|
11290
|
-
|
|
11369
|
+
const list = teams.filter(Boolean);
|
|
11370
|
+
res.json(
|
|
11371
|
+
teamBudgetVisibility.hideTeamBudgetForMembers ? list.map(redactTeamBudget) : list
|
|
11372
|
+
);
|
|
11291
11373
|
} catch (error) {
|
|
11292
11374
|
if (error instanceof ProvisioningError) {
|
|
11293
11375
|
res.status(error.status).json(error.body);
|
|
@@ -11386,7 +11468,9 @@ async function createRouter(options) {
|
|
|
11386
11468
|
teamCreateInFlight.set(key, createPromise);
|
|
11387
11469
|
try {
|
|
11388
11470
|
const result = await createPromise;
|
|
11389
|
-
res.json(
|
|
11471
|
+
res.json(
|
|
11472
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(result) : result
|
|
11473
|
+
);
|
|
11390
11474
|
} catch (err) {
|
|
11391
11475
|
sendTeamError(err, res);
|
|
11392
11476
|
} finally {
|
|
@@ -11430,7 +11514,9 @@ async function createRouter(options) {
|
|
|
11430
11514
|
teamId,
|
|
11431
11515
|
owningGroup
|
|
11432
11516
|
});
|
|
11433
|
-
res.json(
|
|
11517
|
+
res.json(
|
|
11518
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(r) : r
|
|
11519
|
+
);
|
|
11434
11520
|
} catch (err) {
|
|
11435
11521
|
sendTeamError(err, res);
|
|
11436
11522
|
}
|
|
@@ -11488,7 +11574,9 @@ async function createRouter(options) {
|
|
|
11488
11574
|
const owned = all.filter(
|
|
11489
11575
|
(t) => typeof t.metadata?.owning_group === "string" && t.metadata.owning_group === teamAdminCfg.group
|
|
11490
11576
|
);
|
|
11491
|
-
res.json(
|
|
11577
|
+
res.json(
|
|
11578
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? owned.map(redactTeamBudget) : owned
|
|
11579
|
+
);
|
|
11492
11580
|
} catch (err) {
|
|
11493
11581
|
sendTeamError(err, res);
|
|
11494
11582
|
}
|
|
@@ -11630,7 +11718,9 @@ async function createRouter(options) {
|
|
|
11630
11718
|
owningGroup
|
|
11631
11719
|
});
|
|
11632
11720
|
const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
|
|
11633
|
-
res.json(
|
|
11721
|
+
res.json(
|
|
11722
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(updated) : updated
|
|
11723
|
+
);
|
|
11634
11724
|
} catch (err) {
|
|
11635
11725
|
sendTeamError(err, res);
|
|
11636
11726
|
}
|
|
@@ -11661,7 +11751,9 @@ async function createRouter(options) {
|
|
|
11661
11751
|
owningGroup
|
|
11662
11752
|
});
|
|
11663
11753
|
const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
|
|
11664
|
-
res.json(
|
|
11754
|
+
res.json(
|
|
11755
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(updated) : updated
|
|
11756
|
+
);
|
|
11665
11757
|
} catch (err) {
|
|
11666
11758
|
sendTeamError(err, res);
|
|
11667
11759
|
}
|
|
@@ -11740,7 +11832,9 @@ async function createRouter(options) {
|
|
|
11740
11832
|
owningGroup,
|
|
11741
11833
|
vector_stores: requested
|
|
11742
11834
|
});
|
|
11743
|
-
res.json(
|
|
11835
|
+
res.json(
|
|
11836
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(updated) : updated
|
|
11837
|
+
);
|
|
11744
11838
|
} catch (err) {
|
|
11745
11839
|
sendTeamError(err, res);
|
|
11746
11840
|
}
|
|
@@ -11821,7 +11915,9 @@ async function createRouter(options) {
|
|
|
11821
11915
|
before,
|
|
11822
11916
|
after: requested
|
|
11823
11917
|
});
|
|
11824
|
-
res.json(
|
|
11918
|
+
res.json(
|
|
11919
|
+
teamBudgetVisibility.hideTeamBudgetForManagers ? redactTeamBudget(updated) : updated
|
|
11920
|
+
);
|
|
11825
11921
|
} catch (err) {
|
|
11826
11922
|
sendTeamError(err, res);
|
|
11827
11923
|
}
|
|
@@ -11840,7 +11936,24 @@ async function createRouter(options) {
|
|
|
11840
11936
|
start_date,
|
|
11841
11937
|
end_date
|
|
11842
11938
|
);
|
|
11843
|
-
|
|
11939
|
+
let hide = teamBudgetVisibility.hideTeamBudgetForMembers;
|
|
11940
|
+
if (teamAdminCfg.group) {
|
|
11941
|
+
try {
|
|
11942
|
+
const tokenEntityRef = await resolveUserId(req, auth);
|
|
11943
|
+
const isManager = !!tokenEntityRef && await isUserMemberOfGroup(
|
|
11944
|
+
tokenEntityRef,
|
|
11945
|
+
teamAdminCfg.group,
|
|
11946
|
+
catalogClient,
|
|
11947
|
+
auth,
|
|
11948
|
+
logger
|
|
11949
|
+
);
|
|
11950
|
+
hide = isManager ? teamBudgetVisibility.hideTeamBudgetForManagers : teamBudgetVisibility.hideTeamBudgetForMembers;
|
|
11951
|
+
} catch (err) {
|
|
11952
|
+
logger.warn(`Team-manager check failed, hiding team spend: ${err.message}`);
|
|
11953
|
+
hide = teamBudgetVisibility.hideTeamBudgetForMembers;
|
|
11954
|
+
}
|
|
11955
|
+
}
|
|
11956
|
+
res.json(hide ? redactTeamUsage(usage) : usage);
|
|
11844
11957
|
} catch (error) {
|
|
11845
11958
|
logger.error("Failed to fetch team usage", error);
|
|
11846
11959
|
res.status(500).json({ error: error.message });
|
|
@@ -12042,10 +12155,14 @@ var litellmPlugin = (0, import_backend_plugin_api.createBackendPlugin)({
|
|
|
12042
12155
|
readProvisioningDefaults,
|
|
12043
12156
|
readRoleConfigs,
|
|
12044
12157
|
readTeamAdminConfig,
|
|
12158
|
+
readTeamBudgetVisibility,
|
|
12159
|
+
redactTeamBudget,
|
|
12160
|
+
redactTeamUsage,
|
|
12045
12161
|
resolveBridgeUserId,
|
|
12046
12162
|
resolveUserId,
|
|
12047
12163
|
resolveUserProfile,
|
|
12048
12164
|
resolveUserRole,
|
|
12165
|
+
teamBudgetStatusFor,
|
|
12049
12166
|
toLiteLLMUserId,
|
|
12050
12167
|
validateTeamPatchInput,
|
|
12051
12168
|
validateTeamWriteInput
|