@acarmisc/backstage-plugin-litellm-backend 0.11.1 → 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/client.js +2 -0
- package/dist/index.cjs.js +128 -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 +14 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/types.cjs.js.map
CHANGED
|
@@ -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 /** 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 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 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
|
@@ -9,6 +9,8 @@ export interface UserInfo {
|
|
|
9
9
|
current_spend?: number;
|
|
10
10
|
soft_limit?: number;
|
|
11
11
|
hard_limit?: number;
|
|
12
|
+
/** Spend-reset period for max_budget, e.g. "30d". */
|
|
13
|
+
budget_duration?: string;
|
|
12
14
|
/** Backstage-computed: true when the user is a member of litellm.audit.group */
|
|
13
15
|
can_view_audit?: boolean;
|
|
14
16
|
}
|
|
@@ -32,6 +34,16 @@ export interface TeamInfo {
|
|
|
32
34
|
/** Spend-reset period for max_budget, e.g. "30d". */
|
|
33
35
|
budget_duration?: string;
|
|
34
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;
|
|
35
47
|
members_with_roles?: TeamMember[];
|
|
36
48
|
models?: string[];
|
|
37
49
|
tpm_limit?: number;
|
|
@@ -71,6 +83,7 @@ export interface VirtualKey {
|
|
|
71
83
|
expires_at?: string;
|
|
72
84
|
spend: number;
|
|
73
85
|
max_budget?: number;
|
|
86
|
+
budget_duration?: string;
|
|
74
87
|
tpm_limit?: number;
|
|
75
88
|
rpm_limit?: number;
|
|
76
89
|
models?: string[];
|
|
@@ -93,6 +106,7 @@ export interface LiteLLMUserKey {
|
|
|
93
106
|
tpm_limit?: number | null;
|
|
94
107
|
rpm_limit?: number | null;
|
|
95
108
|
max_budget?: number | null;
|
|
109
|
+
budget_duration?: string | null;
|
|
96
110
|
user_id?: string | null;
|
|
97
111
|
team_id?: string | null;
|
|
98
112
|
created_at: string;
|