@acarmisc/backstage-plugin-litellm-backend 0.1.13 → 0.1.15

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/dist/index.cjs.js CHANGED
@@ -274,6 +274,7 @@ var LiteLLMClient = class {
274
274
  // src/provisioning.ts
275
275
  function toLiteLLMUserId(userEntityRef, userIdDomain) {
276
276
  const name = userEntityRef.split("/").pop() ?? userEntityRef;
277
+ if (name.includes("@")) return name;
277
278
  return userIdDomain ? `${name}@${userIdDomain}` : name;
278
279
  }
279
280
  function readRoleConfigs(config) {
@@ -287,6 +288,7 @@ function readRoleConfigs(config) {
287
288
  teams: r.teams,
288
289
  tpmLimit: r.tpmLimit,
289
290
  rpmLimit: r.rpmLimit,
291
+ userRole: r.userRole,
290
292
  metadata: r.metadata
291
293
  }));
292
294
  }
@@ -298,6 +300,7 @@ function applyRoleOverrides(defaults, role) {
298
300
  teams: role.teams ?? defaults.teams,
299
301
  tpmLimit: role.tpmLimit ?? defaults.tpmLimit,
300
302
  rpmLimit: role.rpmLimit ?? defaults.rpmLimit,
303
+ userRole: role.userRole ?? defaults.userRole,
301
304
  metadata: { ...defaults.metadata, ...role.metadata ?? {} }
302
305
  };
303
306
  }
@@ -305,12 +308,21 @@ function readProvisioningDefaults(config) {
305
308
  const enabled = config.getOptionalBoolean("litellm.provisioning.enabled") ?? false;
306
309
  const defaults = {
307
310
  maxBudget: config.getOptionalNumber("litellm.provisioning.defaults.maxBudget") ?? 10,
308
- budgetDuration: config.getOptionalString("litellm.provisioning.defaults.budgetDuration") ?? "30d",
311
+ budgetDuration: config.getOptionalString(
312
+ "litellm.provisioning.defaults.budgetDuration"
313
+ ) ?? "30d",
309
314
  models: config.getOptionalStringArray("litellm.provisioning.defaults.models") ?? [],
310
315
  teams: config.getOptionalStringArray("litellm.provisioning.defaults.teams") ?? [],
311
- tpmLimit: config.getOptionalNumber("litellm.provisioning.defaults.tpmLimit"),
312
- rpmLimit: config.getOptionalNumber("litellm.provisioning.defaults.rpmLimit"),
313
- metadata: config.getOptional("litellm.provisioning.defaults.metadata") ?? {}
316
+ tpmLimit: config.getOptionalNumber(
317
+ "litellm.provisioning.defaults.tpmLimit"
318
+ ),
319
+ rpmLimit: config.getOptionalNumber(
320
+ "litellm.provisioning.defaults.rpmLimit"
321
+ ),
322
+ userRole: config.getOptionalString("litellm.provisioning.defaults.userRole") ?? "internal_user",
323
+ metadata: config.getOptional(
324
+ "litellm.provisioning.defaults.metadata"
325
+ ) ?? {}
314
326
  };
315
327
  return { enabled, defaults };
316
328
  }
@@ -327,35 +339,83 @@ async function resolveUserId(req, auth) {
327
339
  }
328
340
  return void 0;
329
341
  }
330
- async function provisionUser(client, userId, defaults, logger) {
342
+ async function resolveUserProfile(userEntityRef, catalogClient, auth, logger) {
343
+ try {
344
+ const { token } = await auth.getPluginRequestToken({
345
+ onBehalfOf: await auth.getOwnServiceCredentials(),
346
+ targetPluginId: "catalog"
347
+ });
348
+ const entity = await catalogClient.getEntityByRef(userEntityRef, { token });
349
+ const profile = entity?.spec?.profile ?? {};
350
+ return {
351
+ email: profile.email,
352
+ displayName: profile.displayName
353
+ };
354
+ } catch (err) {
355
+ logger.warn(
356
+ `Could not fetch catalog profile for ${userEntityRef}: ${err.message}`
357
+ );
358
+ return {};
359
+ }
360
+ }
361
+ async function provisionUser(client, userId, defaults, profile, backstageEntity, logger) {
331
362
  const payload = {
332
363
  user_id: userId,
364
+ ...profile.email && { user_email: profile.email },
365
+ ...profile.displayName && { user_alias: profile.displayName },
333
366
  max_budget: defaults.maxBudget,
334
367
  budget_duration: defaults.budgetDuration,
335
368
  models: defaults.models,
336
369
  teams: defaults.teams,
337
370
  ...defaults.tpmLimit !== void 0 && { tpm_limit: defaults.tpmLimit },
338
371
  ...defaults.rpmLimit !== void 0 && { rpm_limit: defaults.rpmLimit },
372
+ ...defaults.userRole && { user_role: defaults.userRole },
373
+ auto_create_key: false,
339
374
  metadata: {
340
375
  ...defaults.metadata,
341
376
  provisioned_by: "backstage",
342
377
  provisioned_at: (/* @__PURE__ */ new Date()).toISOString(),
343
- backstage_entity: userId
378
+ backstage_entity: backstageEntity ?? userId,
379
+ ...profile.email && { backstage_email: profile.email },
380
+ ...profile.displayName && {
381
+ backstage_display_name: profile.displayName
382
+ }
344
383
  }
345
384
  };
346
- logger.info(`Provisioning new LiteLLM user for Backstage identity: ${userId}`);
385
+ logger.info(
386
+ `Provisioning new LiteLLM user for Backstage identity: ${userId}` + (profile.email ? ` (email=${profile.email})` : "")
387
+ );
347
388
  try {
348
389
  await client.createUser(payload);
390
+ if (defaults.userRole) {
391
+ try {
392
+ await client.updateUser({
393
+ user_id: userId,
394
+ user_role: defaults.userRole,
395
+ ...profile.email && { user_email: profile.email },
396
+ ...profile.displayName && { user_alias: profile.displayName }
397
+ });
398
+ } catch (updateErr) {
399
+ logger.warn(
400
+ `Defensive /user/update after provisioning ${userId} failed: ${updateErr.message}`
401
+ );
402
+ }
403
+ }
349
404
  return await client.getUserInfo(userId);
350
405
  } catch (err) {
351
406
  logger.error(`Failed to provision LiteLLM user ${userId}: ${err.message}`);
352
- return null;
407
+ throw err;
353
408
  }
354
409
  }
410
+ var provisioningInFlight = /* @__PURE__ */ new Map();
411
+ function sanitizeUpstreamMessage(message) {
412
+ if (!message) return "unknown error";
413
+ return message.replace(/Bearer\s+[A-Za-z0-9._\-+/=]+/g, "Bearer [redacted]").replace(/sk-[A-Za-z0-9_\-]{8,}/g, "sk-[redacted]").slice(0, 500);
414
+ }
355
415
  var ProvisioningError = class extends Error {
356
- constructor(message, hint, provisioning) {
416
+ constructor(message, hint, provisioning, status = 404) {
357
417
  super(message);
358
- this.status = 404;
418
+ this.status = status;
359
419
  this.body = { error: message, hint, provisioning };
360
420
  }
361
421
  };
@@ -367,33 +427,82 @@ async function getOrProvisionUser(client, tokenEntityRef, userId, provisioningEn
367
427
  provisioningEnabled
368
428
  );
369
429
  }
370
- let userInfo = await client.getUserInfo(userId);
371
- if (!userInfo) {
372
- if (provisioningEnabled) {
373
- const catalogRef = tokenEntityRef ?? userId;
374
- const matchedRole = await resolveUserRole(catalogRef, roleConfigs, catalogClient, auth, logger);
375
- const effectiveDefaults = matchedRole ? applyRoleOverrides(provisioningDefaults, matchedRole) : provisioningDefaults;
376
- if (matchedRole) {
377
- logger.info(`User ${userId} matched role group ${matchedRole.group} \u2014 using role-specific provisioning`);
378
- }
379
- userInfo = await provisionUser(client, userId, effectiveDefaults, logger);
430
+ const existing = await client.getUserInfo(userId);
431
+ if (existing) {
432
+ return existing;
433
+ }
434
+ if (!provisioningEnabled) {
435
+ throw new ProvisioningError(
436
+ "User not found in LiteLLM",
437
+ "Enable litellm.provisioning.enabled in app-config.yaml or create the user manually",
438
+ false
439
+ );
440
+ }
441
+ const pending = provisioningInFlight.get(userId);
442
+ if (pending) {
443
+ logger.info(
444
+ `LiteLLM provisioning already in flight for ${userId} \u2014 joining`
445
+ );
446
+ return pending;
447
+ }
448
+ const provisionPromise = (async () => {
449
+ const catalogRef = tokenEntityRef ?? userId;
450
+ const [matchedRole, profile] = await Promise.all([
451
+ resolveUserRole(catalogRef, roleConfigs, catalogClient, auth, logger),
452
+ tokenEntityRef ? resolveUserProfile(tokenEntityRef, catalogClient, auth, logger) : Promise.resolve({})
453
+ ]);
454
+ const effectiveDefaults = matchedRole ? applyRoleOverrides(provisioningDefaults, matchedRole) : provisioningDefaults;
455
+ if (matchedRole) {
456
+ logger.info(
457
+ `User ${userId} matched role group ${matchedRole.group} \u2014 using role-specific provisioning`
458
+ );
380
459
  }
381
- if (!userInfo) {
382
- if (provisioningEnabled) {
460
+ try {
461
+ const created = await provisionUser(
462
+ client,
463
+ userId,
464
+ effectiveDefaults,
465
+ profile,
466
+ tokenEntityRef,
467
+ logger
468
+ );
469
+ if (!created) {
383
470
  throw new ProvisioningError(
384
471
  "User not found in LiteLLM",
385
- "Provisioning attempted but failed \u2014 check LiteLLM logs",
472
+ "Provisioning attempted but returned no user \u2014 check LiteLLM logs",
386
473
  true
387
474
  );
388
475
  }
476
+ return created;
477
+ } catch (err) {
478
+ if (err.status === 409 || /already exists/i.test(err.message ?? "")) {
479
+ logger.info(
480
+ `LiteLLM user ${userId} already exists during provisioning \u2014 re-fetching`
481
+ );
482
+ const refetched = await client.getUserInfo(userId);
483
+ if (refetched) {
484
+ return refetched;
485
+ }
486
+ }
487
+ if (err instanceof ProvisioningError) {
488
+ throw err;
489
+ }
490
+ const upstreamStatus = err.status;
491
+ const passThrough = [400, 404, 409, 422].includes(upstreamStatus) ? upstreamStatus : 502;
389
492
  throw new ProvisioningError(
390
- "User not found in LiteLLM",
391
- "Enable litellm.provisioning.enabled in app-config.yaml or create the user manually",
392
- false
493
+ "LiteLLM auto-provisioning failed",
494
+ `LiteLLM upstream ${upstreamStatus ?? "error"}: ${sanitizeUpstreamMessage(err.message)}`,
495
+ true,
496
+ passThrough
393
497
  );
394
498
  }
499
+ })();
500
+ provisioningInFlight.set(userId, provisionPromise);
501
+ try {
502
+ return await provisionPromise;
503
+ } finally {
504
+ provisioningInFlight.delete(userId);
395
505
  }
396
- return userInfo;
397
506
  }
398
507
  async function resolveUserRole(userEntityRef, roleConfigs, catalogClient, auth, logger) {
399
508
  if (!roleConfigs.length) return void 0;
@@ -406,7 +515,9 @@ async function resolveUserRole(userEntityRef, roleConfigs, catalogClient, auth,
406
515
  const groups = (entity?.relations ?? []).filter((r) => r.type === "memberOf").map((r) => r.targetRef);
407
516
  return roleConfigs.find((rc) => groups.includes(rc.group));
408
517
  } catch (err) {
409
- logger.warn(`Could not resolve Backstage groups for ${userEntityRef}: ${err.message}`);
518
+ logger.warn(
519
+ `Could not resolve Backstage groups for ${userEntityRef}: ${err.message}`
520
+ );
410
521
  return void 0;
411
522
  }
412
523
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/plugin.ts", "../src/router.ts", "../src/client.ts", "../src/provisioning.ts"],
4
- "sourcesContent": ["export { litellmPlugin } from './plugin';\nexport { createRouter } from './router';\nexport * from './types';\nexport { LiteLLMClient } from './client';", "import { coreServices, createBackendPlugin } from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\n\nexport const litellmPlugin = createBackendPlugin({\n pluginId: 'litellm',\n register(reg) {\n reg.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n auth: coreServices.auth,\n discovery: coreServices.discovery,\n },\n async init({ httpRouter, config, logger, auth, discovery }) {\n const router = await createRouter({ config, logger, auth, discovery });\n httpRouter.use(router);\n },\n });\n },\n});\n", "import { Router, Request, Response } from 'express';\nimport { Config } from '@backstage/config';\nimport { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { LiteLLMClient } from './client';\nimport {\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n UpdateKeyRequest,\n} from './types';\nimport {\n toLiteLLMUserId,\n resolveUserId,\n getOrProvisionUser,\n readProvisioningDefaults,\n readRoleConfigs,\n ProvisioningError,\n} from './provisioning';\n\nexport { ProvisioningError };\n\nexport interface RouterOptions {\n config: Config;\n logger: any;\n auth: AuthService;\n discovery: DiscoveryService;\n}\n\nexport async function createRouter(options: RouterOptions): Promise<Router> {\n const { config, logger, auth, discovery } = options;\n\n const baseUrl = config.getString('litellm.baseUrl');\n const masterKey = config.getString('litellm.masterKey');\n const userIdDomain = config.getOptionalString('litellm.userIdDomain');\n const client = new LiteLLMClient({ baseUrl, masterKey });\n const { enabled: provisioningEnabled, defaults: provisioningDefaults } = readProvisioningDefaults(config);\n const roleConfigs = readRoleConfigs(config);\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n if (provisioningEnabled) {\n logger.info(\n `LiteLLM auto-provisioning enabled \u2014 defaults: budget=$${provisioningDefaults.maxBudget}/${provisioningDefaults.budgetDuration}, models=${provisioningDefaults.models.length ? provisioningDefaults.models.join(',') : 'all'}, teams=[${provisioningDefaults.teams.join(',')}]`,\n );\n }\n\n const router = Router();\n\n router.get('/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok', provisioning: provisioningEnabled });\n });\n\n router.get('/user/info', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n const userInfo = await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n res.json(userInfo);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch user info', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/keys', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n\n const keys: VirtualKey[] = await client.listKeys(userId);\n res.json(keys);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to list keys', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.post('/keys/generate', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const resolvedUserId = tokenEntityRef ? toLiteLLMUserId(tokenEntityRef, userIdDomain) : undefined;\n\n if (resolvedUserId) {\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n resolvedUserId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n }\n\n const request: GenerateKeyRequest = {\n ...req.body,\n ...(resolvedUserId && { user_id: resolvedUserId }),\n };\n const result: GenerateKeyResponse = await client.generateKey(request);\n res.json(result);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to generate key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.post('/keys/:keyId/update', async (req: Request, res: Response) => {\n try {\n const { keyId } = req.params;\n if (!keyId) {\n res.status(400).json({ error: 'keyId is required' });\n return;\n }\n const request: UpdateKeyRequest = { ...req.body, key: keyId };\n const result = await client.updateKey(request);\n res.json(result);\n } catch (error: any) {\n logger.error('Failed to update key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.delete('/keys/:keyId', async (req: Request, res: Response) => {\n try {\n const { keyId } = req.params;\n if (!keyId) {\n res.status(400).json({ error: 'keyId is required' });\n return;\n }\n await client.deleteKeys({ keys: [keyId] });\n res.json({ success: true });\n } catch (error: any) {\n logger.error('Failed to delete key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/models', async (_req: Request, res: Response) => {\n try {\n const models: ModelInfo[] = await client.listModels();\n res.json(models);\n } catch (error: any) {\n logger.error('Failed to list models', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/teams', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n const userInfo = await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n\n if (!userInfo?.teams?.length) {\n res.json([]);\n return;\n }\n\n const teams = await Promise.all(\n userInfo.teams.map(teamId =>\n client.getTeamInfo(teamId).catch(err => {\n logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);\n return null;\n }),\n ),\n );\n res.json(teams.filter(Boolean) as TeamInfo[]);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch teams', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/teams/:teamId/usage', async (req: Request, res: Response) => {\n try {\n const { teamId } = req.params;\n const { start_date, end_date } = req.query;\n if (!start_date || !end_date) {\n res.status(400).json({ error: 'start_date and end_date are required' });\n return;\n }\n const usage: UsageMetrics = await client.getTeamUsage(\n teamId,\n start_date as string,\n end_date as string,\n );\n res.json(usage);\n } catch (error: any) {\n logger.error('Failed to fetch team usage', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/usage', async (req: Request, res: Response) => {\n try {\n const { start_date, end_date, group_by } = req.query;\n if (!start_date || !end_date) {\n res.status(400).json({ error: 'start_date and end_date are required' });\n return;\n }\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n if (userId) {\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n }\n\n const usage: UsageMetrics = await client.getUsage(\n start_date as string,\n end_date as string,\n userId,\n group_by as string | undefined,\n );\n res.json(usage);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch usage', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n return router;\n}\n", "import {\n LiteLLMConfig,\n UserInfo,\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n UpdateKeyRequest,\n DeleteKeyRequest,\n CreateUserRequest,\n CreateUserResponse,\n} from './types';\n\nconst DEFAULT_TIMEOUT = 30000;\n\nexport class LiteLLMClient {\n private baseUrl: string;\n private masterKey: string;\n private timeout: number;\n\n constructor(config: LiteLLMConfig, timeout = DEFAULT_TIMEOUT) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.masterKey = config.masterKey;\n this.timeout = timeout;\n }\n\n private async request<T>(path: string, options: RequestInit = {}): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...options,\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.masterKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n const err = new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);\n (err as any).status = response.status;\n throw err;\n }\n\n return response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Returns null when the user is not found in LiteLLM (404).\n * Throws on all other errors so callers know something went wrong.\n */\n async getUserInfo(userId?: string): Promise<UserInfo | null> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n try {\n return await this.request<UserInfo>(`/user/info${query}`);\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n async createUser(payload: CreateUserRequest): Promise<CreateUserResponse> {\n return this.request<CreateUserResponse>('/user/new', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n }\n\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n try {\n const response = await this.request<{ info: VirtualKey[] } | VirtualKey[]>(`/key/info${query}`);\n return Array.isArray(response) ? response : (response.info ?? []);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return [];\n }\n throw err;\n }\n }\n\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n return this.request<GenerateKeyResponse>('/key/generate', {\n method: 'POST',\n body: JSON.stringify({ json: request }),\n });\n }\n\n async updateKey(request: UpdateKeyRequest): Promise<VirtualKey> {\n return this.request<VirtualKey>('/key/update', {\n method: 'POST',\n body: JSON.stringify(request),\n });\n }\n\n async deleteKeys(request: DeleteKeyRequest): Promise<{ success: boolean }> {\n return this.request<{ success: boolean }>('/key/delete', {\n method: 'POST',\n body: JSON.stringify(request),\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n const response = await this.request<{ data: ModelInfo[] } | ModelInfo[]>('/models');\n return Array.isArray(response) ? response : (response.data ?? []);\n }\n\n async getTeamInfo(teamId: string): Promise<TeamInfo> {\n return this.request<TeamInfo>(`/team/info?team_id=${encodeURIComponent(teamId)}`);\n }\n\n private emptyUsage(): UsageMetrics {\n return {\n total_spend: 0,\n total_tokens: 0,\n prompt_tokens: 0,\n completion_tokens: 0,\n api_requests: 0,\n successful_requests: 0,\n failed_requests: 0,\n usage_by_model: {},\n usage_by_key: {},\n daily_usage: [],\n daily_by_model: [],\n };\n }\n\n /**\n * Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter\n * UsageMetrics shape consumed by the frontend charts.\n *\n * Source shape (per result row):\n * { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }\n *\n * We fan that out into three views the UI consumes:\n * - daily_usage \u2192 spend + request trends over time\n * - usage_by_model \u2192 which models drove cost / traffic\n * - usage_by_key \u2192 which keys drove cost / traffic (with key_alias + team_id from metadata)\n */\n private transformDailyActivity(response: any): UsageMetrics {\n const results: any[] = Array.isArray(response?.results) ? response.results : [];\n const meta = response?.metadata ?? {};\n\n const daily_usage = results\n .map(r => ({\n date: r.date,\n spend: r.metrics?.spend ?? 0,\n total_tokens: r.metrics?.total_tokens ?? 0,\n prompt_tokens: r.metrics?.prompt_tokens ?? 0,\n completion_tokens: r.metrics?.completion_tokens ?? 0,\n api_requests: r.metrics?.api_requests ?? 0,\n successful_requests: r.metrics?.successful_requests ?? 0,\n failed_requests: r.metrics?.failed_requests ?? 0,\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n const usage_by_model: UsageMetrics['usage_by_model'] = {};\n const usage_by_key: UsageMetrics['usage_by_key'] = {};\n const daily_by_model: UsageMetrics['daily_by_model'] = [];\n\n const emptyModelBucket = () => ({\n total_spend: 0,\n total_tokens: 0,\n prompt_tokens: 0,\n completion_tokens: 0,\n api_requests: 0,\n successful_requests: 0,\n failed_requests: 0,\n });\n\n for (const r of results) {\n const models = r.breakdown?.models ?? {};\n for (const [name, entry] of Object.entries<any>(models)) {\n const m = entry?.metrics ?? {};\n const bucket = usage_by_model[name] ?? emptyModelBucket();\n bucket.total_spend += m.spend ?? 0;\n bucket.total_tokens += m.total_tokens ?? 0;\n bucket.prompt_tokens += m.prompt_tokens ?? 0;\n bucket.completion_tokens += m.completion_tokens ?? 0;\n bucket.api_requests += m.api_requests ?? 0;\n bucket.successful_requests += m.successful_requests ?? 0;\n bucket.failed_requests += m.failed_requests ?? 0;\n usage_by_model[name] = bucket;\n\n daily_by_model.push({\n date: r.date,\n model: name,\n spend: m.spend ?? 0,\n prompt_tokens: m.prompt_tokens ?? 0,\n completion_tokens: m.completion_tokens ?? 0,\n total_tokens: m.total_tokens ?? 0,\n api_requests: m.api_requests ?? 0,\n successful_requests: m.successful_requests ?? 0,\n failed_requests: m.failed_requests ?? 0,\n });\n\n const keyMap = entry?.api_key_breakdown ?? {};\n for (const [keyHash, keyEntry] of Object.entries<any>(keyMap)) {\n const km = keyEntry?.metrics ?? {};\n const kmeta = keyEntry?.metadata ?? {};\n const kb = usage_by_key[keyHash] ?? {\n key_alias: kmeta.key_alias,\n team_id: kmeta.team_id ?? null,\n models: [] as string[],\n ...emptyModelBucket(),\n };\n if (!kb.key_alias && kmeta.key_alias) kb.key_alias = kmeta.key_alias;\n if (kb.team_id == null && kmeta.team_id) kb.team_id = kmeta.team_id;\n if (!kb.models.includes(name)) kb.models.push(name);\n kb.total_spend += km.spend ?? 0;\n kb.total_tokens += km.total_tokens ?? 0;\n kb.prompt_tokens += km.prompt_tokens ?? 0;\n kb.completion_tokens += km.completion_tokens ?? 0;\n kb.api_requests += km.api_requests ?? 0;\n kb.successful_requests += km.successful_requests ?? 0;\n kb.failed_requests += km.failed_requests ?? 0;\n usage_by_key[keyHash] = kb;\n }\n }\n }\n\n return {\n total_spend: meta.total_spend ?? 0,\n total_tokens: meta.total_tokens ?? 0,\n prompt_tokens: meta.total_prompt_tokens ?? 0,\n completion_tokens: meta.total_completion_tokens ?? 0,\n api_requests: meta.total_api_requests ?? 0,\n successful_requests: meta.total_successful_requests ?? 0,\n failed_requests: meta.total_failed_requests ?? 0,\n usage_by_model,\n usage_by_key,\n daily_usage,\n daily_by_model,\n };\n }\n\n async getUsage(startDate: string, endDate: string, userId?: string, _groupBy?: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({\n start_date: startDate,\n end_date: endDate,\n page_size: '100',\n });\n if (userId) params.append('user_id', userId);\n try {\n const response = await this.request<any>(`/user/daily/activity?${params.toString()}`);\n return this.transformDailyActivity(response);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return this.emptyUsage();\n }\n throw err;\n }\n }\n\n async getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({\n start_date: startDate,\n end_date: endDate,\n team_ids: teamId,\n page_size: '100',\n });\n try {\n const response = await this.request<any>(`/team/daily/activity?${params.toString()}`);\n return this.transformDailyActivity(response);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return this.emptyUsage();\n }\n throw err;\n }\n }\n}\n", "import { Config } from '@backstage/config';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { Request } from 'express';\nimport { LiteLLMClient } from './client';\nimport {\n UserInfo,\n ProvisioningDefaults,\n RoleConfig,\n} from './types';\n\n/**\n * Converts a Backstage user entity ref to a LiteLLM user_id.\n *\n * When userIdDomain is configured, the entity name is suffixed with the domain\n * so that LiteLLM user_ids match the organisation's email addresses:\n * \"user:default/andrea.carmisciano\" + \"abstract.it\"\n * \u2192 \"andrea.carmisciano@abstract.it\"\n *\n * Without a domain the bare entity name is returned unchanged, which works for\n * deployments where LiteLLM users were created with plain usernames.\n */\nexport function toLiteLLMUserId(userEntityRef: string, userIdDomain?: string): string {\n const name = userEntityRef.split('/').pop() ?? userEntityRef;\n return userIdDomain ? `${name}@${userIdDomain}` : name;\n}\n\n/**\n * Reads the provisioning block from config, applying safe defaults for every\n * field so the feature works out-of-the-box without any YAML required.\n *\n * Safe defaults rationale:\n * maxBudget: $10 \u2014 prevents runaway spend on a forgotten test account\n * budgetDuration: 30d \u2014 monthly reset, aligns with typical billing cycles\n * models: [] \u2014 empty means all proxy models are allowed;\n * restrict here or at team level for tighter control\n * teams: [] \u2014 no automatic team assignment; add IDs to enrol users\n * tpmLimit: none \u2014 LiteLLM global / team limits still apply\n * rpmLimit: none \u2014 same\n * metadata: backstage source tag only\n */\nexport function readRoleConfigs(config: Config): RoleConfig[] {\n const raw = config.getOptional<any[]>('litellm.provisioning.roles');\n if (!raw?.length) return [];\n return raw.map((r: any) => ({\n group: r.group as string,\n maxBudget: r.maxBudget,\n budgetDuration: r.budgetDuration,\n models: r.models,\n teams: r.teams,\n tpmLimit: r.tpmLimit,\n rpmLimit: r.rpmLimit,\n metadata: r.metadata,\n }));\n}\n\n/**\n * Merges role config over defaults. Role fields override defaults only when explicitly set.\n */\nexport function applyRoleOverrides(\n defaults: ProvisioningDefaults,\n role: RoleConfig,\n): ProvisioningDefaults {\n return {\n maxBudget: role.maxBudget ?? defaults.maxBudget,\n budgetDuration: role.budgetDuration ?? defaults.budgetDuration,\n models: role.models ?? defaults.models,\n teams: role.teams ?? defaults.teams,\n tpmLimit: role.tpmLimit ?? defaults.tpmLimit,\n rpmLimit: role.rpmLimit ?? defaults.rpmLimit,\n metadata: { ...defaults.metadata, ...(role.metadata ?? {}) },\n };\n}\n\nexport function readProvisioningDefaults(config: Config): { enabled: boolean; defaults: ProvisioningDefaults } {\n const enabled = config.getOptionalBoolean('litellm.provisioning.enabled') ?? false;\n const defaults: ProvisioningDefaults = {\n maxBudget: config.getOptionalNumber('litellm.provisioning.defaults.maxBudget') ?? 10,\n budgetDuration: config.getOptionalString('litellm.provisioning.defaults.budgetDuration') ?? '30d',\n models: config.getOptionalStringArray('litellm.provisioning.defaults.models') ?? [],\n teams: config.getOptionalStringArray('litellm.provisioning.defaults.teams') ?? [],\n tpmLimit: config.getOptionalNumber('litellm.provisioning.defaults.tpmLimit'),\n rpmLimit: config.getOptionalNumber('litellm.provisioning.defaults.rpmLimit'),\n metadata: (config.getOptional<Record<string, string>>('litellm.provisioning.defaults.metadata') ?? {}),\n };\n return { enabled, defaults };\n}\n\n/**\n * Extracts the authenticated Backstage user identity from the request token.\n * Returns the userEntityRef (e.g. \"user:default/john.doe\") or undefined when\n * the request carries no user credential (service-to-service calls).\n */\nexport async function resolveUserId(req: Request, auth: AuthService): Promise<string | undefined> {\n const rawToken = req.headers.authorization?.slice(7);\n if (!rawToken) return undefined;\n try {\n const credentials = await auth.authenticate(rawToken);\n const principal = credentials.principal as any;\n if (principal?.type === 'user') {\n return principal.userEntityRef as string;\n }\n } catch {\n // invalid or service token \u2014 caller gets query-param fallback\n }\n return undefined;\n}\n\n/**\n * Creates a LiteLLM user for the given Backstage identity using the configured\n * defaults. Returns the UserInfo of the newly created account.\n */\nexport async function provisionUser(\n client: LiteLLMClient,\n userId: string,\n defaults: ProvisioningDefaults,\n logger: any,\n): Promise<UserInfo | null> {\n const payload = {\n user_id: userId,\n max_budget: defaults.maxBudget,\n budget_duration: defaults.budgetDuration,\n models: defaults.models,\n teams: defaults.teams,\n ...(defaults.tpmLimit !== undefined && { tpm_limit: defaults.tpmLimit }),\n ...(defaults.rpmLimit !== undefined && { rpm_limit: defaults.rpmLimit }),\n metadata: {\n ...defaults.metadata,\n provisioned_by: 'backstage',\n provisioned_at: new Date().toISOString(),\n backstage_entity: userId,\n },\n };\n\n logger.info(`Provisioning new LiteLLM user for Backstage identity: ${userId}`);\n try {\n await client.createUser(payload);\n // Fetch the freshly-created user record to return consistent UserInfo shape\n return await client.getUserInfo(userId);\n } catch (err: any) {\n logger.error(`Failed to provision LiteLLM user ${userId}: ${err.message}`);\n return null;\n }\n}\n\nexport class ProvisioningError extends Error {\n status: number;\n body: { error: string; hint: string; provisioning: boolean };\n\n constructor(message: string, hint: string, provisioning: boolean) {\n super(message);\n this.status = 404;\n this.body = { error: message, hint, provisioning };\n }\n}\n\n/**\n * Ensures the LiteLLM user exists, returning its UserInfo.\n * When the user is missing and provisioning is enabled, attempts to create it.\n * When provisioning is disabled, throws a ProvisioningError with a clear message.\n */\nexport async function getOrProvisionUser(\n client: LiteLLMClient,\n tokenEntityRef: string | undefined,\n userId: string | undefined,\n provisioningEnabled: boolean,\n provisioningDefaults: ProvisioningDefaults,\n roleConfigs: RoleConfig[],\n catalogClient: CatalogClient,\n auth: AuthService,\n logger: any,\n): Promise<UserInfo> {\n if (!userId) {\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'No user identity could be resolved from the request.',\n provisioningEnabled,\n );\n }\n\n let userInfo: UserInfo | null = await client.getUserInfo(userId);\n\n if (!userInfo) {\n if (provisioningEnabled) {\n const catalogRef = tokenEntityRef ?? userId;\n const matchedRole = await resolveUserRole(catalogRef, roleConfigs, catalogClient, auth, logger);\n const effectiveDefaults = matchedRole\n ? applyRoleOverrides(provisioningDefaults, matchedRole)\n : provisioningDefaults;\n if (matchedRole) {\n logger.info(`User ${userId} matched role group ${matchedRole.group} \u2014 using role-specific provisioning`);\n }\n userInfo = await provisionUser(client, userId, effectiveDefaults, logger);\n }\n\n if (!userInfo) {\n if (provisioningEnabled) {\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'Provisioning attempted but failed \u2014 check LiteLLM logs',\n true,\n );\n }\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'Enable litellm.provisioning.enabled in app-config.yaml or create the user manually',\n false,\n );\n }\n }\n\n return userInfo;\n}\n\n/**\n * Fetches the user's Backstage group memberships and returns the first matching\n * role config (priority order), or undefined when no role matches.\n */\nexport async function resolveUserRole(\n userEntityRef: string,\n roleConfigs: RoleConfig[],\n catalogClient: CatalogClient,\n auth: AuthService,\n logger: any,\n): Promise<RoleConfig | undefined> {\n if (!roleConfigs.length) return undefined;\n try {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(userEntityRef, { token });\n const groups = (entity?.relations ?? [])\n .filter(r => r.type === 'memberOf')\n .map(r => r.targetRef);\n return roleConfigs.find(rc => groups.includes(rc.group));\n } catch (err: any) {\n logger.warn(`Could not resolve Backstage groups for ${userEntityRef}: ${err.message}`);\n return undefined;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export { litellmPlugin } from './plugin';\nexport { createRouter } from './router';\nexport * from './types';\nexport { LiteLLMClient } from './client';", "import { coreServices, createBackendPlugin } from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\n\nexport const litellmPlugin = createBackendPlugin({\n pluginId: 'litellm',\n register(reg) {\n reg.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n auth: coreServices.auth,\n discovery: coreServices.discovery,\n },\n async init({ httpRouter, config, logger, auth, discovery }) {\n const router = await createRouter({ config, logger, auth, discovery });\n httpRouter.use(router);\n },\n });\n },\n});\n", "import { Router, Request, Response } from 'express';\nimport { Config } from '@backstage/config';\nimport { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { LiteLLMClient } from './client';\nimport {\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n UpdateKeyRequest,\n} from './types';\nimport {\n toLiteLLMUserId,\n resolveUserId,\n getOrProvisionUser,\n readProvisioningDefaults,\n readRoleConfigs,\n ProvisioningError,\n} from './provisioning';\n\nexport { ProvisioningError };\n\nexport interface RouterOptions {\n config: Config;\n logger: any;\n auth: AuthService;\n discovery: DiscoveryService;\n}\n\nexport async function createRouter(options: RouterOptions): Promise<Router> {\n const { config, logger, auth, discovery } = options;\n\n const baseUrl = config.getString('litellm.baseUrl');\n const masterKey = config.getString('litellm.masterKey');\n const userIdDomain = config.getOptionalString('litellm.userIdDomain');\n const client = new LiteLLMClient({ baseUrl, masterKey });\n const { enabled: provisioningEnabled, defaults: provisioningDefaults } = readProvisioningDefaults(config);\n const roleConfigs = readRoleConfigs(config);\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n if (provisioningEnabled) {\n logger.info(\n `LiteLLM auto-provisioning enabled \u2014 defaults: budget=$${provisioningDefaults.maxBudget}/${provisioningDefaults.budgetDuration}, models=${provisioningDefaults.models.length ? provisioningDefaults.models.join(',') : 'all'}, teams=[${provisioningDefaults.teams.join(',')}]`,\n );\n }\n\n const router = Router();\n\n router.get('/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok', provisioning: provisioningEnabled });\n });\n\n router.get('/user/info', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n const userInfo = await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n res.json(userInfo);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch user info', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/keys', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n\n const keys: VirtualKey[] = await client.listKeys(userId);\n res.json(keys);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to list keys', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.post('/keys/generate', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const resolvedUserId = tokenEntityRef ? toLiteLLMUserId(tokenEntityRef, userIdDomain) : undefined;\n\n if (resolvedUserId) {\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n resolvedUserId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n }\n\n const request: GenerateKeyRequest = {\n ...req.body,\n ...(resolvedUserId && { user_id: resolvedUserId }),\n };\n const result: GenerateKeyResponse = await client.generateKey(request);\n res.json(result);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to generate key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.post('/keys/:keyId/update', async (req: Request, res: Response) => {\n try {\n const { keyId } = req.params;\n if (!keyId) {\n res.status(400).json({ error: 'keyId is required' });\n return;\n }\n const request: UpdateKeyRequest = { ...req.body, key: keyId };\n const result = await client.updateKey(request);\n res.json(result);\n } catch (error: any) {\n logger.error('Failed to update key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.delete('/keys/:keyId', async (req: Request, res: Response) => {\n try {\n const { keyId } = req.params;\n if (!keyId) {\n res.status(400).json({ error: 'keyId is required' });\n return;\n }\n await client.deleteKeys({ keys: [keyId] });\n res.json({ success: true });\n } catch (error: any) {\n logger.error('Failed to delete key', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/models', async (_req: Request, res: Response) => {\n try {\n const models: ModelInfo[] = await client.listModels();\n res.json(models);\n } catch (error: any) {\n logger.error('Failed to list models', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/teams', async (req: Request, res: Response) => {\n try {\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n const userInfo = await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n\n if (!userInfo?.teams?.length) {\n res.json([]);\n return;\n }\n\n const teams = await Promise.all(\n userInfo.teams.map(teamId =>\n client.getTeamInfo(teamId).catch(err => {\n logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);\n return null;\n }),\n ),\n );\n res.json(teams.filter(Boolean) as TeamInfo[]);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch teams', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/teams/:teamId/usage', async (req: Request, res: Response) => {\n try {\n const { teamId } = req.params;\n const { start_date, end_date } = req.query;\n if (!start_date || !end_date) {\n res.status(400).json({ error: 'start_date and end_date are required' });\n return;\n }\n const usage: UsageMetrics = await client.getTeamUsage(\n teamId,\n start_date as string,\n end_date as string,\n );\n res.json(usage);\n } catch (error: any) {\n logger.error('Failed to fetch team usage', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n router.get('/usage', async (req: Request, res: Response) => {\n try {\n const { start_date, end_date, group_by } = req.query;\n if (!start_date || !end_date) {\n res.status(400).json({ error: 'start_date and end_date are required' });\n return;\n }\n const tokenEntityRef = await resolveUserId(req, auth);\n const userId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : (req.query.user_id as string | undefined);\n\n if (userId) {\n await getOrProvisionUser(\n client,\n tokenEntityRef,\n userId,\n provisioningEnabled,\n provisioningDefaults,\n roleConfigs,\n catalogClient,\n auth,\n logger,\n );\n }\n\n const usage: UsageMetrics = await client.getUsage(\n start_date as string,\n end_date as string,\n userId,\n group_by as string | undefined,\n );\n res.json(usage);\n } catch (error: any) {\n if (error instanceof ProvisioningError) {\n res.status(error.status).json(error.body);\n return;\n }\n logger.error('Failed to fetch usage', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n return router;\n}\n", "import {\n LiteLLMConfig,\n UserInfo,\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n UpdateKeyRequest,\n DeleteKeyRequest,\n CreateUserRequest,\n CreateUserResponse,\n} from './types';\n\nconst DEFAULT_TIMEOUT = 30000;\n\nexport class LiteLLMClient {\n private baseUrl: string;\n private masterKey: string;\n private timeout: number;\n\n constructor(config: LiteLLMConfig, timeout = DEFAULT_TIMEOUT) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.masterKey = config.masterKey;\n this.timeout = timeout;\n }\n\n private async request<T>(path: string, options: RequestInit = {}): Promise<T> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...options,\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.masterKey}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n const err = new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);\n (err as any).status = response.status;\n throw err;\n }\n\n return response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Returns null when the user is not found in LiteLLM (404).\n * Throws on all other errors so callers know something went wrong.\n */\n async getUserInfo(userId?: string): Promise<UserInfo | null> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n try {\n return await this.request<UserInfo>(`/user/info${query}`);\n } catch (err: any) {\n if (err.status === 404) return null;\n throw err;\n }\n }\n\n async createUser(payload: CreateUserRequest): Promise<CreateUserResponse> {\n return this.request<CreateUserResponse>('/user/new', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n }\n\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n try {\n const response = await this.request<{ info: VirtualKey[] } | VirtualKey[]>(`/key/info${query}`);\n return Array.isArray(response) ? response : (response.info ?? []);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return [];\n }\n throw err;\n }\n }\n\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n return this.request<GenerateKeyResponse>('/key/generate', {\n method: 'POST',\n body: JSON.stringify({ json: request }),\n });\n }\n\n async updateKey(request: UpdateKeyRequest): Promise<VirtualKey> {\n return this.request<VirtualKey>('/key/update', {\n method: 'POST',\n body: JSON.stringify(request),\n });\n }\n\n async deleteKeys(request: DeleteKeyRequest): Promise<{ success: boolean }> {\n return this.request<{ success: boolean }>('/key/delete', {\n method: 'POST',\n body: JSON.stringify(request),\n });\n }\n\n async listModels(): Promise<ModelInfo[]> {\n const response = await this.request<{ data: ModelInfo[] } | ModelInfo[]>('/models');\n return Array.isArray(response) ? response : (response.data ?? []);\n }\n\n async getTeamInfo(teamId: string): Promise<TeamInfo> {\n return this.request<TeamInfo>(`/team/info?team_id=${encodeURIComponent(teamId)}`);\n }\n\n private emptyUsage(): UsageMetrics {\n return {\n total_spend: 0,\n total_tokens: 0,\n prompt_tokens: 0,\n completion_tokens: 0,\n api_requests: 0,\n successful_requests: 0,\n failed_requests: 0,\n usage_by_model: {},\n usage_by_key: {},\n daily_usage: [],\n daily_by_model: [],\n };\n }\n\n /**\n * Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter\n * UsageMetrics shape consumed by the frontend charts.\n *\n * Source shape (per result row):\n * { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }\n *\n * We fan that out into three views the UI consumes:\n * - daily_usage \u2192 spend + request trends over time\n * - usage_by_model \u2192 which models drove cost / traffic\n * - usage_by_key \u2192 which keys drove cost / traffic (with key_alias + team_id from metadata)\n */\n private transformDailyActivity(response: any): UsageMetrics {\n const results: any[] = Array.isArray(response?.results) ? response.results : [];\n const meta = response?.metadata ?? {};\n\n const daily_usage = results\n .map(r => ({\n date: r.date,\n spend: r.metrics?.spend ?? 0,\n total_tokens: r.metrics?.total_tokens ?? 0,\n prompt_tokens: r.metrics?.prompt_tokens ?? 0,\n completion_tokens: r.metrics?.completion_tokens ?? 0,\n api_requests: r.metrics?.api_requests ?? 0,\n successful_requests: r.metrics?.successful_requests ?? 0,\n failed_requests: r.metrics?.failed_requests ?? 0,\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n const usage_by_model: UsageMetrics['usage_by_model'] = {};\n const usage_by_key: UsageMetrics['usage_by_key'] = {};\n const daily_by_model: UsageMetrics['daily_by_model'] = [];\n\n const emptyModelBucket = () => ({\n total_spend: 0,\n total_tokens: 0,\n prompt_tokens: 0,\n completion_tokens: 0,\n api_requests: 0,\n successful_requests: 0,\n failed_requests: 0,\n });\n\n for (const r of results) {\n const models = r.breakdown?.models ?? {};\n for (const [name, entry] of Object.entries<any>(models)) {\n const m = entry?.metrics ?? {};\n const bucket = usage_by_model[name] ?? emptyModelBucket();\n bucket.total_spend += m.spend ?? 0;\n bucket.total_tokens += m.total_tokens ?? 0;\n bucket.prompt_tokens += m.prompt_tokens ?? 0;\n bucket.completion_tokens += m.completion_tokens ?? 0;\n bucket.api_requests += m.api_requests ?? 0;\n bucket.successful_requests += m.successful_requests ?? 0;\n bucket.failed_requests += m.failed_requests ?? 0;\n usage_by_model[name] = bucket;\n\n daily_by_model.push({\n date: r.date,\n model: name,\n spend: m.spend ?? 0,\n prompt_tokens: m.prompt_tokens ?? 0,\n completion_tokens: m.completion_tokens ?? 0,\n total_tokens: m.total_tokens ?? 0,\n api_requests: m.api_requests ?? 0,\n successful_requests: m.successful_requests ?? 0,\n failed_requests: m.failed_requests ?? 0,\n });\n\n const keyMap = entry?.api_key_breakdown ?? {};\n for (const [keyHash, keyEntry] of Object.entries<any>(keyMap)) {\n const km = keyEntry?.metrics ?? {};\n const kmeta = keyEntry?.metadata ?? {};\n const kb = usage_by_key[keyHash] ?? {\n key_alias: kmeta.key_alias,\n team_id: kmeta.team_id ?? null,\n models: [] as string[],\n ...emptyModelBucket(),\n };\n if (!kb.key_alias && kmeta.key_alias) kb.key_alias = kmeta.key_alias;\n if (kb.team_id == null && kmeta.team_id) kb.team_id = kmeta.team_id;\n if (!kb.models.includes(name)) kb.models.push(name);\n kb.total_spend += km.spend ?? 0;\n kb.total_tokens += km.total_tokens ?? 0;\n kb.prompt_tokens += km.prompt_tokens ?? 0;\n kb.completion_tokens += km.completion_tokens ?? 0;\n kb.api_requests += km.api_requests ?? 0;\n kb.successful_requests += km.successful_requests ?? 0;\n kb.failed_requests += km.failed_requests ?? 0;\n usage_by_key[keyHash] = kb;\n }\n }\n }\n\n return {\n total_spend: meta.total_spend ?? 0,\n total_tokens: meta.total_tokens ?? 0,\n prompt_tokens: meta.total_prompt_tokens ?? 0,\n completion_tokens: meta.total_completion_tokens ?? 0,\n api_requests: meta.total_api_requests ?? 0,\n successful_requests: meta.total_successful_requests ?? 0,\n failed_requests: meta.total_failed_requests ?? 0,\n usage_by_model,\n usage_by_key,\n daily_usage,\n daily_by_model,\n };\n }\n\n async getUsage(startDate: string, endDate: string, userId?: string, _groupBy?: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({\n start_date: startDate,\n end_date: endDate,\n page_size: '100',\n });\n if (userId) params.append('user_id', userId);\n try {\n const response = await this.request<any>(`/user/daily/activity?${params.toString()}`);\n return this.transformDailyActivity(response);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return this.emptyUsage();\n }\n throw err;\n }\n }\n\n async getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({\n start_date: startDate,\n end_date: endDate,\n team_ids: teamId,\n page_size: '100',\n });\n try {\n const response = await this.request<any>(`/team/daily/activity?${params.toString()}`);\n return this.transformDailyActivity(response);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return this.emptyUsage();\n }\n throw err;\n }\n }\n}\n", "import { Config } from '@backstage/config';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { Request } from 'express';\nimport { LiteLLMClient } from './client';\nimport { UserInfo, ProvisioningDefaults, RoleConfig } from './types';\n\n/**\n * Converts a Backstage user entity ref to a LiteLLM user_id.\n *\n * When userIdDomain is configured, the entity name is suffixed with the domain\n * so that LiteLLM user_ids match the organisation's email addresses:\n * \"user:default/andrea.carmisciano\" + \"abstract.it\"\n * \u2192 \"andrea.carmisciano@abstract.it\"\n *\n * Without a domain the bare entity name is returned unchanged, which works for\n * deployments where LiteLLM users were created with plain usernames.\n */\nexport function toLiteLLMUserId(\n userEntityRef: string,\n userIdDomain?: string,\n): string {\n const name = userEntityRef.split('/').pop() ?? userEntityRef;\n // Defensive: if the entity name is already email-shaped (e.g. when the\n // Keycloak provider imports usernames as full emails without our\n // name-rewrite transformer running, or when a catalog change leaves\n // an entity ref like \"user:default/foo@bar.it\"), do NOT append the\n // userIdDomain \u2014 that produced \"foo@bar.it@bar.it\" in production.\n if (name.includes('@')) return name;\n return userIdDomain ? `${name}@${userIdDomain}` : name;\n}\n\n/**\n * Reads the provisioning block from config, applying safe defaults for every\n * field so the feature works out-of-the-box without any YAML required.\n *\n * Safe defaults rationale:\n * maxBudget: $10 \u2014 prevents runaway spend on a forgotten test account\n * budgetDuration: 30d \u2014 monthly reset, aligns with typical billing cycles\n * models: [] \u2014 empty means all proxy models are allowed;\n * restrict here or at team level for tighter control\n * teams: [] \u2014 no automatic team assignment; add IDs to enrol users\n * tpmLimit: none \u2014 LiteLLM global / team limits still apply\n * rpmLimit: none \u2014 same\n * metadata: backstage source tag only\n */\nexport function readRoleConfigs(config: Config): RoleConfig[] {\n const raw = config.getOptional<any[]>('litellm.provisioning.roles');\n if (!raw?.length) return [];\n return raw.map((r: any) => ({\n group: r.group as string,\n maxBudget: r.maxBudget,\n budgetDuration: r.budgetDuration,\n models: r.models,\n teams: r.teams,\n tpmLimit: r.tpmLimit,\n rpmLimit: r.rpmLimit,\n userRole: r.userRole,\n metadata: r.metadata,\n }));\n}\n\n/**\n * Merges role config over defaults. Role fields override defaults only when explicitly set.\n */\nexport function applyRoleOverrides(\n defaults: ProvisioningDefaults,\n role: RoleConfig,\n): ProvisioningDefaults {\n return {\n maxBudget: role.maxBudget ?? defaults.maxBudget,\n budgetDuration: role.budgetDuration ?? defaults.budgetDuration,\n models: role.models ?? defaults.models,\n teams: role.teams ?? defaults.teams,\n tpmLimit: role.tpmLimit ?? defaults.tpmLimit,\n rpmLimit: role.rpmLimit ?? defaults.rpmLimit,\n userRole: role.userRole ?? defaults.userRole,\n metadata: { ...defaults.metadata, ...(role.metadata ?? {}) },\n };\n}\n\nexport function readProvisioningDefaults(config: Config): {\n enabled: boolean;\n defaults: ProvisioningDefaults;\n} {\n const enabled =\n config.getOptionalBoolean('litellm.provisioning.enabled') ?? false;\n const defaults: ProvisioningDefaults = {\n maxBudget:\n config.getOptionalNumber('litellm.provisioning.defaults.maxBudget') ?? 10,\n budgetDuration:\n config.getOptionalString(\n 'litellm.provisioning.defaults.budgetDuration',\n ) ?? '30d',\n models:\n config.getOptionalStringArray('litellm.provisioning.defaults.models') ??\n [],\n teams:\n config.getOptionalStringArray('litellm.provisioning.defaults.teams') ??\n [],\n tpmLimit: config.getOptionalNumber(\n 'litellm.provisioning.defaults.tpmLimit',\n ),\n rpmLimit: config.getOptionalNumber(\n 'litellm.provisioning.defaults.rpmLimit',\n ),\n userRole:\n config.getOptionalString('litellm.provisioning.defaults.userRole') ??\n 'internal_user',\n metadata:\n config.getOptional<Record<string, string>>(\n 'litellm.provisioning.defaults.metadata',\n ) ?? {},\n };\n return { enabled, defaults };\n}\n\n/**\n * Extracts the authenticated Backstage user identity from the request token.\n * Returns the userEntityRef (e.g. \"user:default/john.doe\") or undefined when\n * the request carries no user credential (service-to-service calls).\n */\nexport async function resolveUserId(\n req: Request,\n auth: AuthService,\n): Promise<string | undefined> {\n const rawToken = req.headers.authorization?.slice(7);\n if (!rawToken) return undefined;\n try {\n const credentials = await auth.authenticate(rawToken);\n const principal = credentials.principal as any;\n if (principal?.type === 'user') {\n return principal.userEntityRef as string;\n }\n } catch {\n // invalid or service token \u2014 caller gets query-param fallback\n }\n return undefined;\n}\n\n/**\n * Profile data extracted from a Backstage Catalog User entity, used to\n * populate user_email / user_alias on the LiteLLM record.\n */\nexport interface BackstageUserProfile {\n email?: string;\n displayName?: string;\n}\n\n/**\n * Looks up the catalog User entity for the authenticated user and returns\n * the profile block. Returns an empty object when the user has no catalog\n * entity (e.g. dangerouslyAllowSignInWithoutUserInCatalog was used) \u2014 the\n * caller falls back to deriving identity from userIdDomain.\n */\nexport async function resolveUserProfile(\n userEntityRef: string,\n catalogClient: CatalogClient,\n auth: AuthService,\n logger: any,\n): Promise<BackstageUserProfile> {\n try {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(userEntityRef, { token });\n const profile = (entity?.spec as any)?.profile ?? {};\n return {\n email: profile.email,\n displayName: profile.displayName,\n };\n } catch (err: any) {\n logger.warn(\n `Could not fetch catalog profile for ${userEntityRef}: ${err.message}`,\n );\n return {};\n }\n}\n\n/**\n * Creates a LiteLLM user for the given Backstage identity using the configured\n * defaults. Returns the UserInfo of the newly created account.\n */\nexport async function provisionUser(\n client: LiteLLMClient,\n userId: string,\n defaults: ProvisioningDefaults,\n profile: BackstageUserProfile,\n backstageEntity: string | undefined,\n logger: any,\n): Promise<UserInfo | null> {\n const payload = {\n user_id: userId,\n ...(profile.email && { user_email: profile.email }),\n ...(profile.displayName && { user_alias: profile.displayName }),\n max_budget: defaults.maxBudget,\n budget_duration: defaults.budgetDuration,\n models: defaults.models,\n teams: defaults.teams,\n ...(defaults.tpmLimit !== undefined && { tpm_limit: defaults.tpmLimit }),\n ...(defaults.rpmLimit !== undefined && { rpm_limit: defaults.rpmLimit }),\n ...(defaults.userRole && { user_role: defaults.userRole }),\n auto_create_key: false,\n metadata: {\n ...defaults.metadata,\n provisioned_by: 'backstage',\n provisioned_at: new Date().toISOString(),\n backstage_entity: backstageEntity ?? userId,\n ...(profile.email && { backstage_email: profile.email }),\n ...(profile.displayName && {\n backstage_display_name: profile.displayName,\n }),\n },\n };\n\n logger.info(\n `Provisioning new LiteLLM user for Backstage identity: ${userId}` +\n (profile.email ? ` (email=${profile.email})` : ''),\n );\n try {\n await client.createUser(payload);\n // Defensive /user/update: LiteLLM's /user/new upsert path has been\n // observed to drop user_role under concurrent inserts (the first\n // call sets the field, a racing second call upserts and clears it).\n // Re-asserting the role-bearing fields immediately after creation\n // is cheap and makes the role guarantee robust.\n if (defaults.userRole) {\n try {\n await client.updateUser({\n user_id: userId,\n user_role: defaults.userRole,\n ...(profile.email && { user_email: profile.email }),\n ...(profile.displayName && { user_alias: profile.displayName }),\n });\n } catch (updateErr: any) {\n logger.warn(\n `Defensive /user/update after provisioning ${userId} failed: ${updateErr.message}`,\n );\n }\n }\n // Fetch the freshly-created user record to return consistent UserInfo shape\n return await client.getUserInfo(userId);\n } catch (err: any) {\n logger.error(`Failed to provision LiteLLM user ${userId}: ${err.message}`);\n throw err;\n }\n}\n\n/**\n * Module-scope single-flight cache keyed by LiteLLM user_id. Coalesces\n * concurrent provisioning attempts for the same user so /user/new fires\n * at most once per user across parallel requests. Without this, an\n * authenticated page load that fires /keys, /teams and /usage in\n * parallel triggers three concurrent /user/new calls; LiteLLM's\n * upsert path then creates one default key per call (so the user lands\n * with 3 unexpected keys) and may silently lose user_role.\n *\n * Cache entries are removed once the promise settles, so subsequent\n * requests for a re-deleted user can still trigger fresh provisioning.\n */\nconst provisioningInFlight = new Map<string, Promise<UserInfo>>();\n\n/**\n * Strips any echoed Authorization bearer token from upstream LiteLLM error\n * messages before they're shipped back to the browser. LiteLLM normally does\n * not echo the master key, but defense in depth: never let a `Bearer \u2026`\n * substring travel out in a response body.\n */\nfunction sanitizeUpstreamMessage(message: string): string {\n if (!message) return 'unknown error';\n return message\n .replace(/Bearer\\s+[A-Za-z0-9._\\-+/=]+/g, 'Bearer [redacted]')\n .replace(/sk-[A-Za-z0-9_\\-]{8,}/g, 'sk-[redacted]')\n .slice(0, 500);\n}\n\nexport class ProvisioningError extends Error {\n status: number;\n body: { error: string; hint: string; provisioning: boolean };\n\n constructor(\n message: string,\n hint: string,\n provisioning: boolean,\n status = 404,\n ) {\n super(message);\n this.status = status;\n this.body = { error: message, hint, provisioning };\n }\n}\n\n/**\n * Ensures the LiteLLM user exists, returning its UserInfo.\n * When the user is missing and provisioning is enabled, attempts to create it.\n * When provisioning is disabled, throws a ProvisioningError with a clear message.\n */\nexport async function getOrProvisionUser(\n client: LiteLLMClient,\n tokenEntityRef: string | undefined,\n userId: string | undefined,\n provisioningEnabled: boolean,\n provisioningDefaults: ProvisioningDefaults,\n roleConfigs: RoleConfig[],\n catalogClient: CatalogClient,\n auth: AuthService,\n logger: any,\n): Promise<UserInfo> {\n if (!userId) {\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'No user identity could be resolved from the request.',\n provisioningEnabled,\n );\n }\n\n const existing = await client.getUserInfo(userId);\n if (existing) {\n return existing;\n }\n\n if (!provisioningEnabled) {\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'Enable litellm.provisioning.enabled in app-config.yaml or create the user manually',\n false,\n );\n }\n\n // Single-flight: if another request for the same userId is already\n // provisioning, await its result instead of starting a new /user/new.\n // This collapses the /keys + /teams + /usage page-load thundering\n // herd into a single LiteLLM round-trip.\n const pending = provisioningInFlight.get(userId);\n if (pending) {\n logger.info(\n `LiteLLM provisioning already in flight for ${userId} \u2014 joining`,\n );\n return pending;\n }\n\n const provisionPromise = (async () => {\n const catalogRef = tokenEntityRef ?? userId;\n const [matchedRole, profile] = await Promise.all([\n resolveUserRole(catalogRef, roleConfigs, catalogClient, auth, logger),\n tokenEntityRef\n ? resolveUserProfile(tokenEntityRef, catalogClient, auth, logger)\n : Promise.resolve<BackstageUserProfile>({}),\n ]);\n const effectiveDefaults = matchedRole\n ? applyRoleOverrides(provisioningDefaults, matchedRole)\n : provisioningDefaults;\n if (matchedRole) {\n logger.info(\n `User ${userId} matched role group ${matchedRole.group} \u2014 using role-specific provisioning`,\n );\n }\n try {\n const created = await provisionUser(\n client,\n userId,\n effectiveDefaults,\n profile,\n tokenEntityRef,\n logger,\n );\n if (!created) {\n throw new ProvisioningError(\n 'User not found in LiteLLM',\n 'Provisioning attempted but returned no user \u2014 check LiteLLM logs',\n true,\n );\n }\n return created;\n } catch (err: any) {\n // The single-flight cache should prevent the parallel-409 race,\n // but keep the recovery path: if /user/new still 409s (e.g.\n // multi-replica deploys where the lock is per-process), treat\n // it as \"user exists\" and re-fetch.\n if (err.status === 409 || /already exists/i.test(err.message ?? '')) {\n logger.info(\n `LiteLLM user ${userId} already exists during provisioning \u2014 re-fetching`,\n );\n const refetched = await client.getUserInfo(userId);\n if (refetched) {\n return refetched;\n }\n }\n if (err instanceof ProvisioningError) {\n throw err;\n }\n // Map upstream LiteLLM status to a Backstage-safe gateway status.\n // 401/403/5xx from LiteLLM mean the gateway (this plugin) cannot\n // talk to LiteLLM \u2014 they MUST NOT propagate as 401/403 to the\n // browser, otherwise Backstage's fetch middleware treats the\n // user's Backstage session as expired and forces a re-login.\n // Only safe client-semantic codes pass through.\n const upstreamStatus = err.status;\n const passThrough = [400, 404, 409, 422].includes(upstreamStatus)\n ? upstreamStatus\n : 502;\n throw new ProvisioningError(\n 'LiteLLM auto-provisioning failed',\n `LiteLLM upstream ${\n upstreamStatus ?? 'error'\n }: ${sanitizeUpstreamMessage(err.message)}`,\n true,\n passThrough,\n );\n }\n })();\n\n provisioningInFlight.set(userId, provisionPromise);\n try {\n return await provisionPromise;\n } finally {\n provisioningInFlight.delete(userId);\n }\n}\n\n/**\n * Fetches the user's Backstage group memberships and returns the first matching\n * role config (priority order), or undefined when no role matches.\n */\nexport async function resolveUserRole(\n userEntityRef: string,\n roleConfigs: RoleConfig[],\n catalogClient: CatalogClient,\n auth: AuthService,\n logger: any,\n): Promise<RoleConfig | undefined> {\n if (!roleConfigs.length) return undefined;\n try {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(userEntityRef, { token });\n const groups = (entity?.relations ?? [])\n .filter(r => r.type === 'memberOf')\n .map(r => r.targetRef);\n return roleConfigs.find(rc => groups.includes(rc.group));\n } catch (err: any) {\n logger.warn(\n `Could not resolve Backstage groups for ${userEntityRef}: ${err.message}`,\n );\n return undefined;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -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}\n\nexport interface TeamMember {\n user_id: 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 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}\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}\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}\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 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 metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: 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}\n\nexport interface CreateUserResponse {\n user_id: string;\n user_email?: string;\n max_budget?: number;\n models?: string[];\n teams?: string[];\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}\n\nexport interface TeamMember {\n user_id: 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 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}\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}\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}\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 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 metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: 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"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
package/dist/client.d.ts DELETED
@@ -1,38 +0,0 @@
1
- import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, DeleteKeyRequest, CreateUserRequest, CreateUserResponse } from './types';
2
- export declare class LiteLLMClient {
3
- private baseUrl;
4
- private masterKey;
5
- private timeout;
6
- constructor(config: LiteLLMConfig, timeout?: number);
7
- private request;
8
- /**
9
- * Returns null when the user is not found in LiteLLM (404).
10
- * Throws on all other errors so callers know something went wrong.
11
- */
12
- getUserInfo(userId?: string): Promise<UserInfo | null>;
13
- createUser(payload: CreateUserRequest): Promise<CreateUserResponse>;
14
- listKeys(userId?: string): Promise<VirtualKey[]>;
15
- generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse>;
16
- updateKey(request: UpdateKeyRequest): Promise<VirtualKey>;
17
- deleteKeys(request: DeleteKeyRequest): Promise<{
18
- success: boolean;
19
- }>;
20
- listModels(): Promise<ModelInfo[]>;
21
- getTeamInfo(teamId: string): Promise<TeamInfo>;
22
- private emptyUsage;
23
- /**
24
- * Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter
25
- * UsageMetrics shape consumed by the frontend charts.
26
- *
27
- * Source shape (per result row):
28
- * { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }
29
- *
30
- * We fan that out into three views the UI consumes:
31
- * - daily_usage → spend + request trends over time
32
- * - usage_by_model → which models drove cost / traffic
33
- * - usage_by_key → which keys drove cost / traffic (with key_alias + team_id from metadata)
34
- */
35
- private transformDailyActivity;
36
- getUsage(startDate: string, endDate: string, userId?: string, _groupBy?: string): Promise<UsageMetrics>;
37
- getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
38
- }