@acarmisc/backstage-plugin-litellm-backend 0.8.1 → 0.9.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 +1 -1
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +1 -1
- package/dist/client.js +1 -0
- package/dist/index.cjs.js +8063 -17
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.js +26 -0
- package/dist/plugin.js +12 -2
- package/dist/provisioning.d.ts +8 -3
- package/dist/provisioning.js +18 -2
- package/dist/router.d.ts +2 -1
- package/dist/router.js +52 -1
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +6 -3
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,4 @@ export { resolveUserId, resolveUserProfile, toLiteLLMUserId, getOrProvisionUser,
|
|
|
7
7
|
export type { BackstageUserProfile } from './provisioning';
|
|
8
8
|
export { KeycloakJWTVerifier, newDefaultVerifier, readBridgeConfig, resolveBridgeUserId, getOrProvisionUserFromClaims, bridgeListKeys, bridgeGenerateKey, } from './bridge';
|
|
9
9
|
export type { BridgeClaims, TokenVerifier, BridgeConfig, KeycloakJWTVerifierOptions, } from './bridge';
|
|
10
|
+
export { litellmPermissions, litellmKeyCreatePermission, litellmKeyRevokePermission, litellmKeyManagePermission, litellmAuditReadPermission, } 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.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = 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.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = exports.litellmPermissions = exports.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = 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");
|
|
@@ -43,3 +43,9 @@ Object.defineProperty(exports, "resolveBridgeUserId", { enumerable: true, get: f
|
|
|
43
43
|
Object.defineProperty(exports, "getOrProvisionUserFromClaims", { enumerable: true, get: function () { return bridge_1.getOrProvisionUserFromClaims; } });
|
|
44
44
|
Object.defineProperty(exports, "bridgeListKeys", { enumerable: true, get: function () { return bridge_1.bridgeListKeys; } });
|
|
45
45
|
Object.defineProperty(exports, "bridgeGenerateKey", { enumerable: true, get: function () { return bridge_1.bridgeGenerateKey; } });
|
|
46
|
+
var permissions_1 = require("./permissions");
|
|
47
|
+
Object.defineProperty(exports, "litellmPermissions", { enumerable: true, get: function () { return permissions_1.litellmPermissions; } });
|
|
48
|
+
Object.defineProperty(exports, "litellmKeyCreatePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyCreatePermission; } });
|
|
49
|
+
Object.defineProperty(exports, "litellmKeyRevokePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyRevokePermission; } });
|
|
50
|
+
Object.defineProperty(exports, "litellmKeyManagePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyManagePermission; } });
|
|
51
|
+
Object.defineProperty(exports, "litellmAuditReadPermission", { enumerable: true, get: function () { return permissions_1.litellmAuditReadPermission; } });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const litellmKeyCreatePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
2
|
+
export declare const litellmKeyRevokePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
3
|
+
export declare const litellmKeyManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
4
|
+
export declare const litellmAuditReadPermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
5
|
+
export declare const litellmPermissions: import("@backstage/plugin-permission-common").BasicPermission[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.litellmPermissions = exports.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = void 0;
|
|
4
|
+
const plugin_permission_common_1 = require("@backstage/plugin-permission-common");
|
|
5
|
+
exports.litellmKeyCreatePermission = (0, plugin_permission_common_1.createPermission)({
|
|
6
|
+
name: 'litellm.key.create',
|
|
7
|
+
attributes: { action: 'create' },
|
|
8
|
+
});
|
|
9
|
+
exports.litellmKeyRevokePermission = (0, plugin_permission_common_1.createPermission)({
|
|
10
|
+
name: 'litellm.key.revoke',
|
|
11
|
+
attributes: { action: 'delete' },
|
|
12
|
+
});
|
|
13
|
+
exports.litellmKeyManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
14
|
+
name: 'litellm.key.manage',
|
|
15
|
+
attributes: { action: 'update' },
|
|
16
|
+
});
|
|
17
|
+
exports.litellmAuditReadPermission = (0, plugin_permission_common_1.createPermission)({
|
|
18
|
+
name: 'litellm.audit.read',
|
|
19
|
+
attributes: { action: 'read' },
|
|
20
|
+
});
|
|
21
|
+
exports.litellmPermissions = [
|
|
22
|
+
exports.litellmKeyCreatePermission,
|
|
23
|
+
exports.litellmKeyRevokePermission,
|
|
24
|
+
exports.litellmKeyManagePermission,
|
|
25
|
+
exports.litellmAuditReadPermission,
|
|
26
|
+
];
|
package/dist/plugin.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.litellmPlugin = void 0;
|
|
|
4
4
|
const backend_plugin_api_1 = require("@backstage/backend-plugin-api");
|
|
5
5
|
const router_1 = require("./router");
|
|
6
6
|
const bridge_1 = require("./bridge");
|
|
7
|
+
const permissions_1 = require("./permissions");
|
|
7
8
|
exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
|
|
8
9
|
pluginId: 'litellm',
|
|
9
10
|
register(reg) {
|
|
@@ -14,9 +15,18 @@ exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
|
|
|
14
15
|
logger: backend_plugin_api_1.coreServices.logger,
|
|
15
16
|
auth: backend_plugin_api_1.coreServices.auth,
|
|
16
17
|
discovery: backend_plugin_api_1.coreServices.discovery,
|
|
18
|
+
permissions: backend_plugin_api_1.coreServices.permissions,
|
|
19
|
+
permissionsRegistry: backend_plugin_api_1.coreServices.permissionsRegistry,
|
|
17
20
|
},
|
|
18
|
-
async init({ httpRouter, config, logger, auth, discovery }) {
|
|
19
|
-
|
|
21
|
+
async init({ httpRouter, config, logger, auth, discovery, permissions, permissionsRegistry, }) {
|
|
22
|
+
permissionsRegistry.addPermissions(permissions_1.litellmPermissions);
|
|
23
|
+
const router = await (0, router_1.createRouter)({
|
|
24
|
+
config,
|
|
25
|
+
logger,
|
|
26
|
+
auth,
|
|
27
|
+
discovery,
|
|
28
|
+
permissions,
|
|
29
|
+
});
|
|
20
30
|
httpRouter.use(router);
|
|
21
31
|
// The CLI bridge verifies raw Keycloak JWTs itself (it does NOT use a
|
|
22
32
|
// Backstage-issued token). Exempt its routes from the default auth
|
package/dist/provisioning.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Config } from '@backstage/config';
|
|
2
|
-
import { AuthService } from '@backstage/backend-plugin-api';
|
|
2
|
+
import { AuthService, BackstageCredentials } from '@backstage/backend-plugin-api';
|
|
3
3
|
import { CatalogClient } from '@backstage/catalog-client';
|
|
4
4
|
import { Request } from 'express';
|
|
5
5
|
import { LiteLLMClient } from './client';
|
|
@@ -9,8 +9,8 @@ import { UserInfo, ProvisioningDefaults, RoleConfig } from './types';
|
|
|
9
9
|
*
|
|
10
10
|
* When userIdDomain is configured, the entity name is suffixed with the domain
|
|
11
11
|
* so that LiteLLM user_ids match the organisation's email addresses:
|
|
12
|
-
* "user:default/andrea.carmisciano" + "
|
|
13
|
-
* → "andrea.carmisciano@
|
|
12
|
+
* "user:default/andrea.carmisciano" + "example.com"
|
|
13
|
+
* → "andrea.carmisciano@example.com"
|
|
14
14
|
*
|
|
15
15
|
* Without a domain the bare entity name is returned unchanged, which works for
|
|
16
16
|
* deployments where LiteLLM users were created with plain usernames.
|
|
@@ -45,6 +45,11 @@ export declare function readProvisioningDefaults(config: Config): {
|
|
|
45
45
|
* the request carries no user credential (service-to-service calls).
|
|
46
46
|
*/
|
|
47
47
|
export declare function resolveUserId(req: Request, auth: AuthService): Promise<string | undefined>;
|
|
48
|
+
/**
|
|
49
|
+
* Like resolveUserId, but returns the raw BackstageCredentials object
|
|
50
|
+
* (rather than just the entity ref) for passing into PermissionsService.authorize().
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveCredentials(req: Request, auth: AuthService): Promise<BackstageCredentials | undefined>;
|
|
48
53
|
/**
|
|
49
54
|
* Profile data extracted from a Backstage Catalog User entity, used to
|
|
50
55
|
* populate user_email / user_alias on the LiteLLM record.
|
package/dist/provisioning.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.readRoleConfigs = readRoleConfigs;
|
|
|
6
6
|
exports.applyRoleOverrides = applyRoleOverrides;
|
|
7
7
|
exports.readProvisioningDefaults = readProvisioningDefaults;
|
|
8
8
|
exports.resolveUserId = resolveUserId;
|
|
9
|
+
exports.resolveCredentials = resolveCredentials;
|
|
9
10
|
exports.resolveUserProfile = resolveUserProfile;
|
|
10
11
|
exports.provisionUser = provisionUser;
|
|
11
12
|
exports.getOrProvisionUser = getOrProvisionUser;
|
|
@@ -16,8 +17,8 @@ exports.resolveUserRole = resolveUserRole;
|
|
|
16
17
|
*
|
|
17
18
|
* When userIdDomain is configured, the entity name is suffixed with the domain
|
|
18
19
|
* so that LiteLLM user_ids match the organisation's email addresses:
|
|
19
|
-
* "user:default/andrea.carmisciano" + "
|
|
20
|
-
* → "andrea.carmisciano@
|
|
20
|
+
* "user:default/andrea.carmisciano" + "example.com"
|
|
21
|
+
* → "andrea.carmisciano@example.com"
|
|
21
22
|
*
|
|
22
23
|
* Without a domain the bare entity name is returned unchanged, which works for
|
|
23
24
|
* deployments where LiteLLM users were created with plain usernames.
|
|
@@ -116,6 +117,21 @@ async function resolveUserId(req, auth) {
|
|
|
116
117
|
}
|
|
117
118
|
return undefined;
|
|
118
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Like resolveUserId, but returns the raw BackstageCredentials object
|
|
122
|
+
* (rather than just the entity ref) for passing into PermissionsService.authorize().
|
|
123
|
+
*/
|
|
124
|
+
async function resolveCredentials(req, auth) {
|
|
125
|
+
const rawToken = req.headers.authorization?.slice(7);
|
|
126
|
+
if (!rawToken)
|
|
127
|
+
return undefined;
|
|
128
|
+
try {
|
|
129
|
+
return await auth.authenticate(rawToken);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
119
135
|
/**
|
|
120
136
|
* Looks up the catalog User entity for the authenticated user and returns
|
|
121
137
|
* the profile block. Returns an empty object when the user has no catalog
|
package/dist/router.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { Config } from '@backstage/config';
|
|
3
|
-
import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { AuthService, DiscoveryService, PermissionsService } from '@backstage/backend-plugin-api';
|
|
4
4
|
import { LiteLLMClient } from './client';
|
|
5
5
|
import { ProvisioningError } from './provisioning';
|
|
6
6
|
import { TokenVerifier } from './bridge';
|
|
@@ -10,6 +10,7 @@ export interface RouterOptions {
|
|
|
10
10
|
logger: any;
|
|
11
11
|
auth: AuthService;
|
|
12
12
|
discovery: DiscoveryService;
|
|
13
|
+
permissions: PermissionsService;
|
|
13
14
|
/** Override the LiteLLM client (tests). Defaults to one built from config. */
|
|
14
15
|
client?: LiteLLMClient;
|
|
15
16
|
/** Override the bridge token verifier (tests). Defaults to a Keycloak JWKS verifier. */
|
package/dist/router.js
CHANGED
|
@@ -36,14 +36,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.ProvisioningError = void 0;
|
|
37
37
|
exports.createRouter = createRouter;
|
|
38
38
|
const express_1 = __importStar(require("express"));
|
|
39
|
+
const plugin_permission_common_1 = require("@backstage/plugin-permission-common");
|
|
39
40
|
const catalog_client_1 = require("@backstage/catalog-client");
|
|
40
41
|
const client_1 = require("./client");
|
|
41
42
|
const openapi_1 = require("./openapi");
|
|
42
43
|
const provisioning_1 = require("./provisioning");
|
|
43
44
|
Object.defineProperty(exports, "ProvisioningError", { enumerable: true, get: function () { return provisioning_1.ProvisioningError; } });
|
|
44
45
|
const bridge_1 = require("./bridge");
|
|
46
|
+
const permissions_1 = require("./permissions");
|
|
45
47
|
async function createRouter(options) {
|
|
46
|
-
const { config, logger, auth, discovery } = options;
|
|
48
|
+
const { config, logger, auth, discovery, permissions } = options;
|
|
47
49
|
const baseUrl = config.getString('litellm.baseUrl');
|
|
48
50
|
const masterKey = config.getString('litellm.masterKey');
|
|
49
51
|
const userIdDomain = config.getOptionalString('litellm.userIdDomain');
|
|
@@ -212,6 +214,27 @@ async function createRouter(options) {
|
|
|
212
214
|
}
|
|
213
215
|
return false;
|
|
214
216
|
}
|
|
217
|
+
// ── Permission-framework guard ───────────────────────────────────────────
|
|
218
|
+
//
|
|
219
|
+
// Coarse-grained "is this identity allowed to do this at all" check, layered
|
|
220
|
+
// on top of (not replacing) the ownership guard above. With no permission
|
|
221
|
+
// policy installed (the default for a fresh Backstage instance) this always
|
|
222
|
+
// resolves ALLOW, so behavior is unchanged until an operator wires up
|
|
223
|
+
// @backstage-community/plugin-rbac or a custom PermissionPolicy.
|
|
224
|
+
async function assertPermission(req, permission) {
|
|
225
|
+
const credentials = await (0, provisioning_1.resolveCredentials)(req, auth);
|
|
226
|
+
if (!credentials)
|
|
227
|
+
return false;
|
|
228
|
+
const [decision] = await permissions.authorize([{ permission }], {
|
|
229
|
+
credentials,
|
|
230
|
+
});
|
|
231
|
+
return decision.result === plugin_permission_common_1.AuthorizeResult.ALLOW;
|
|
232
|
+
}
|
|
233
|
+
function sendPermissionDenied(res, permission) {
|
|
234
|
+
res.status(403).json({
|
|
235
|
+
error: `Access denied: missing permission "${permission.name}"`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
215
238
|
router.post('/keys/generate', async (req, res) => {
|
|
216
239
|
try {
|
|
217
240
|
// Only alias is hard-required. max_budget is optional: a positive
|
|
@@ -236,6 +259,10 @@ async function createRouter(options) {
|
|
|
236
259
|
});
|
|
237
260
|
return;
|
|
238
261
|
}
|
|
262
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyCreatePermission))) {
|
|
263
|
+
sendPermissionDenied(res, permissions_1.litellmKeyCreatePermission);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
239
266
|
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
240
267
|
const resolvedUserId = tokenEntityRef
|
|
241
268
|
? (0, provisioning_1.toLiteLLMUserId)(tokenEntityRef, userIdDomain)
|
|
@@ -314,6 +341,10 @@ async function createRouter(options) {
|
|
|
314
341
|
res.status(400).json({ error: 'keyId is required' });
|
|
315
342
|
return;
|
|
316
343
|
}
|
|
344
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyManagePermission))) {
|
|
345
|
+
sendPermissionDenied(res, permissions_1.litellmKeyManagePermission);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
317
348
|
const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
|
|
318
349
|
const request = { ...req.body, key: keyId };
|
|
319
350
|
const result = await client.updateKey(request);
|
|
@@ -334,6 +365,10 @@ async function createRouter(options) {
|
|
|
334
365
|
res.status(400).json({ error: 'keyId is required' });
|
|
335
366
|
return;
|
|
336
367
|
}
|
|
368
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyRevokePermission))) {
|
|
369
|
+
sendPermissionDenied(res, permissions_1.litellmKeyRevokePermission);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
337
372
|
const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
|
|
338
373
|
await client.deleteKeys({ keys: [keyId] });
|
|
339
374
|
logger.info({ action: 'key.delete', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
@@ -349,6 +384,10 @@ async function createRouter(options) {
|
|
|
349
384
|
router.post('/keys/:keyId/block', async (req, res) => {
|
|
350
385
|
try {
|
|
351
386
|
const { keyId } = req.params;
|
|
387
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyManagePermission))) {
|
|
388
|
+
sendPermissionDenied(res, permissions_1.litellmKeyManagePermission);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
352
391
|
const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
|
|
353
392
|
await client.blockKey(keyId);
|
|
354
393
|
logger.info({ action: 'key.block', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
@@ -364,6 +403,10 @@ async function createRouter(options) {
|
|
|
364
403
|
router.post('/keys/:keyId/unblock', async (req, res) => {
|
|
365
404
|
try {
|
|
366
405
|
const { keyId } = req.params;
|
|
406
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyManagePermission))) {
|
|
407
|
+
sendPermissionDenied(res, permissions_1.litellmKeyManagePermission);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
367
410
|
const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
|
|
368
411
|
await client.unblockKey(keyId);
|
|
369
412
|
logger.info({ action: 'key.unblock', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
@@ -379,6 +422,10 @@ async function createRouter(options) {
|
|
|
379
422
|
router.post('/keys/:keyId/reset_spend', async (req, res) => {
|
|
380
423
|
try {
|
|
381
424
|
const { keyId } = req.params;
|
|
425
|
+
if (!(await assertPermission(req, permissions_1.litellmKeyManagePermission))) {
|
|
426
|
+
sendPermissionDenied(res, permissions_1.litellmKeyManagePermission);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
382
429
|
const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
|
|
383
430
|
await client.resetKeySpend(keyId);
|
|
384
431
|
logger.info({ action: 'key.reset_spend', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
@@ -406,6 +453,10 @@ async function createRouter(options) {
|
|
|
406
453
|
res.status(403).json({ error: 'Access denied: not a member of the audit group' });
|
|
407
454
|
return;
|
|
408
455
|
}
|
|
456
|
+
if (!(await assertPermission(req, permissions_1.litellmAuditReadPermission))) {
|
|
457
|
+
sendPermissionDenied(res, permissions_1.litellmAuditReadPermission);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
409
460
|
try {
|
|
410
461
|
const { page, page_size, start_date, end_date, action, table_name, changed_by } = req.query;
|
|
411
462
|
const result = await client.getAuditLogs({
|
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 TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\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}\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 /** 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 TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\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"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export interface ModelInfo {
|
|
|
71
71
|
output_cost_per_token?: number;
|
|
72
72
|
max_input_tokens?: number;
|
|
73
73
|
max_output_tokens?: number;
|
|
74
|
+
/** 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. */
|
|
75
|
+
access_groups?: string[];
|
|
74
76
|
}
|
|
75
77
|
export interface UsageModelBreakdown {
|
|
76
78
|
total_spend: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"build": "node build.js && tsc -p tsconfig.json",
|
|
32
32
|
"lint": "eslint src --ext .ts",
|
|
33
33
|
"test": "tsc -p tsconfig.test.json && node --test dist-test/**/*.test.js dist-test/*.test.js",
|
|
34
|
+
"api-report": "npm run build && api-extractor run --local --verbose",
|
|
34
35
|
"prepack": "npm run build",
|
|
35
36
|
"postpack": ""
|
|
36
37
|
},
|
|
@@ -39,16 +40,18 @@
|
|
|
39
40
|
"@backstage/catalog-client": "^1.9.0",
|
|
40
41
|
"@backstage/catalog-model": "^1.7.0",
|
|
41
42
|
"@backstage/config": "^1.3.0",
|
|
43
|
+
"@backstage/plugin-permission-common": "^0.9.10",
|
|
42
44
|
"@backstage/types": "^1.2.0",
|
|
43
45
|
"express": "^4.18.2",
|
|
44
46
|
"jose": "^5.9.6"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@backstage/cli": "^0.36.2",
|
|
50
|
+
"@microsoft/api-extractor": "^7.59.0",
|
|
48
51
|
"@types/express": "^4.17.25",
|
|
52
|
+
"@types/node": "^20.0.0",
|
|
49
53
|
"esbuild": "^0.28.0",
|
|
50
|
-
"typescript": "^5.9.3"
|
|
51
|
-
"@types/node": "^20.0.0"
|
|
54
|
+
"typescript": "^5.9.3"
|
|
52
55
|
},
|
|
53
56
|
"configSchema": "config.d.ts"
|
|
54
57
|
}
|