@acarmisc/backstage-plugin-litellm-backend 0.27.0 → 0.29.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 +20 -0
- package/dist/index.cjs.js +27 -16
- package/dist/index.cjs.js.map +2 -2
- package/dist/openapi.js +1 -0
- package/dist/router.js +5 -0
- package/dist/routes/config.js +1 -0
- package/dist/routes/context.d.ts +2 -0
- package/dist/routes/teamUsage.js +21 -11
- package/dist/teamMemberUsage.d.ts +2 -0
- package/dist/teamMemberUsage.js +1 -0
- package/package.json +1 -1
package/dist/openapi.js
CHANGED
|
@@ -69,6 +69,7 @@ exports.openApiSpec = {
|
|
|
69
69
|
display: { type: 'object', properties: {
|
|
70
70
|
hideTeamBudgetForMembers: { type: 'boolean' },
|
|
71
71
|
hideTeamBudgetForManagers: { type: 'boolean' },
|
|
72
|
+
serviceKeyPrefixes: { type: 'array', items: { type: 'string' } },
|
|
72
73
|
} },
|
|
73
74
|
} } } },
|
|
74
75
|
},
|
package/dist/router.js
CHANGED
|
@@ -71,6 +71,10 @@ async function createRouter(options) {
|
|
|
71
71
|
const publicBaseUrl = config.getOptionalString('litellm.publicBaseUrl') ?? null;
|
|
72
72
|
// Support contact for unprovisioned users (optional).
|
|
73
73
|
const supportContact = config.getOptionalString('litellm.supportContact');
|
|
74
|
+
// Alias prefixes of system/service keys the frontend hides by default.
|
|
75
|
+
const serviceKeyPrefixes = (config.getOptionalStringArray('litellm.display.serviceKeyPrefixes') ?? [])
|
|
76
|
+
.map(p => p.trim())
|
|
77
|
+
.filter(p => p.length > 0);
|
|
74
78
|
const baseClient = options.client ?? new client_1.LiteLLMClient({ baseUrl, masterKey });
|
|
75
79
|
// Wrap client with userInfoCache. TTL is configurable (default 10s, 0 disables).
|
|
76
80
|
const userInfoCacheTtlSeconds = config.getOptionalNumber('litellm.cache.userInfoTtlSeconds') ?? 10;
|
|
@@ -134,6 +138,7 @@ async function createRouter(options) {
|
|
|
134
138
|
baseUrl,
|
|
135
139
|
publicBaseUrl,
|
|
136
140
|
supportContact,
|
|
141
|
+
serviceKeyPrefixes,
|
|
137
142
|
userIdDomain,
|
|
138
143
|
provisioningEnabled,
|
|
139
144
|
provisioningDefaults,
|
package/dist/routes/config.js
CHANGED
|
@@ -35,6 +35,7 @@ function registerConfigRoutes(router, ctx) {
|
|
|
35
35
|
display: {
|
|
36
36
|
hideTeamBudgetForMembers: ctx.teamBudgetVisibility.hideTeamBudgetForMembers,
|
|
37
37
|
hideTeamBudgetForManagers: ctx.teamBudgetVisibility.hideTeamBudgetForManagers,
|
|
38
|
+
serviceKeyPrefixes: ctx.serviceKeyPrefixes,
|
|
38
39
|
},
|
|
39
40
|
});
|
|
40
41
|
});
|
package/dist/routes/context.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface RouterContext {
|
|
|
23
23
|
baseUrl: string;
|
|
24
24
|
publicBaseUrl: string | null;
|
|
25
25
|
supportContact: string | undefined;
|
|
26
|
+
/** Key-alias prefixes treated as service keys by the frontend (display-only). */
|
|
27
|
+
serviceKeyPrefixes: string[];
|
|
26
28
|
userIdDomain: string | undefined;
|
|
27
29
|
provisioningEnabled: boolean;
|
|
28
30
|
provisioningDefaults: ProvisioningDefaults;
|
package/dist/routes/teamUsage.js
CHANGED
|
@@ -12,6 +12,23 @@ const withUser_1 = require("./middleware/withUser");
|
|
|
12
12
|
function registerTeamUsageRoutes(router, ctx) {
|
|
13
13
|
const { client, catalogClient, auth, logger, teamBudgetVisibility, } = ctx;
|
|
14
14
|
const requireUser = (0, withUser_1.createRequireUser)(ctx);
|
|
15
|
+
// A team manager: a member of the team's owning group (team management
|
|
16
|
+
// enabled), or a member of the catalog lead group `<alias><leadGroupSuffix>`
|
|
17
|
+
// for teams synced without an owning_group.
|
|
18
|
+
async function isTeamManager(team, tokenEntityRef) {
|
|
19
|
+
const owningGroup = typeof team.metadata?.owning_group === 'string' ? team.metadata.owning_group : undefined;
|
|
20
|
+
if (ctx.teamMgmtEnabled &&
|
|
21
|
+
owningGroup &&
|
|
22
|
+
(await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger))) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const suffix = ctx.memberBreakdownCfg.leadGroupSuffix;
|
|
26
|
+
if (!suffix || !team.team_alias)
|
|
27
|
+
return false;
|
|
28
|
+
// Same slug rule as the catalog group names: lower-case, other chars become '-'.
|
|
29
|
+
const slug = team.team_alias.toLowerCase().replace(/[^a-z0-9\-_.]/g, '-');
|
|
30
|
+
return (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, `group:default/${slug}${suffix}`, catalogClient, auth, logger);
|
|
31
|
+
}
|
|
15
32
|
router.get('/teams/:teamId/usage', requireUser, async (req, res) => {
|
|
16
33
|
try {
|
|
17
34
|
const { teamId } = req.params;
|
|
@@ -25,24 +42,20 @@ function registerTeamUsageRoutes(router, ctx) {
|
|
|
25
42
|
const userInfo = await (0, withUser_1.getProvisionedUser)(ctx, res);
|
|
26
43
|
// Check membership: user must be in the team OR be a team manager
|
|
27
44
|
const isMember = userInfo?.teams?.includes(teamId) ?? false;
|
|
28
|
-
// A "manager" administers this specific team
|
|
29
|
-
// and the caller belongs to the team's owning group (metadata.owning_group).
|
|
45
|
+
// A "manager" administers this specific team (see isTeamManager).
|
|
30
46
|
// Membership of the global team-admin group alone is not enough.
|
|
31
47
|
let isManager = false;
|
|
32
48
|
// Skip the extra lookup when it can't change the outcome: members always
|
|
33
49
|
// get through, and manager-ness only matters for members when the budget
|
|
34
50
|
// hiding flags differ between the two roles.
|
|
35
|
-
const managerCheckNeeded = ctx.teamMgmtEnabled &&
|
|
51
|
+
const managerCheckNeeded = (ctx.teamMgmtEnabled || !!ctx.memberBreakdownCfg.leadGroupSuffix) &&
|
|
36
52
|
(!isMember ||
|
|
37
53
|
teamBudgetVisibility.hideTeamBudgetForMembers !==
|
|
38
54
|
teamBudgetVisibility.hideTeamBudgetForManagers);
|
|
39
55
|
if (managerCheckNeeded) {
|
|
40
56
|
try {
|
|
41
57
|
const team = await (0, teamFetch_1.withTeamFetchRetry)(() => client.getTeamInfo(teamId));
|
|
42
|
-
|
|
43
|
-
if (owningGroup) {
|
|
44
|
-
isManager = await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger);
|
|
45
|
-
}
|
|
58
|
+
isManager = await isTeamManager(team, tokenEntityRef);
|
|
46
59
|
}
|
|
47
60
|
catch (err) {
|
|
48
61
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -104,10 +117,7 @@ function registerTeamUsageRoutes(router, ctx) {
|
|
|
104
117
|
}
|
|
105
118
|
throw err;
|
|
106
119
|
}
|
|
107
|
-
const
|
|
108
|
-
const isManager = ctx.teamMgmtEnabled &&
|
|
109
|
-
!!owningGroup &&
|
|
110
|
-
(await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger));
|
|
120
|
+
const isManager = await isTeamManager(team, tokenEntityRef);
|
|
111
121
|
if (!isMember && !isManager) {
|
|
112
122
|
res.status(404).json({ error: 'Team not found' });
|
|
113
123
|
return null;
|
|
@@ -8,6 +8,8 @@ export interface MemberBreakdownConfig {
|
|
|
8
8
|
viewerRoles: string[];
|
|
9
9
|
/** Whether `litellm.team.usage.read` may grant access (needs `permission.enabled`). */
|
|
10
10
|
permissionEnabled: boolean;
|
|
11
|
+
/** Suffix of a catalog group named `<team_alias><suffix>` whose members are team leads. */
|
|
12
|
+
leadGroupSuffix?: string;
|
|
11
13
|
}
|
|
12
14
|
export declare function readMemberBreakdownConfig(config: Config): MemberBreakdownConfig;
|
|
13
15
|
/** Catalog profile of a member, keyed by lower-cased email. */
|
package/dist/teamMemberUsage.js
CHANGED
|
@@ -11,6 +11,7 @@ function readMemberBreakdownConfig(config) {
|
|
|
11
11
|
enabled: config.getOptionalBoolean('litellm.teamUsage.memberBreakdown.enabled') ?? false,
|
|
12
12
|
viewerRoles: config.getOptionalStringArray('litellm.teamUsage.memberBreakdown.viewerRoles') ?? [],
|
|
13
13
|
permissionEnabled: config.getOptionalBoolean('permission.enabled') ?? false,
|
|
14
|
+
leadGroupSuffix: config.getOptionalString('litellm.teamUsage.memberBreakdown.leadGroupSuffix') || undefined,
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
/**
|