@acarmisc/backstage-plugin-litellm-backend 0.1.14 → 0.1.16

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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,6 +32,7 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  LiteLLMClient: () => LiteLLMClient,
24
34
  createRouter: () => createRouter,
35
+ default: () => litellmPlugin,
25
36
  litellmPlugin: () => litellmPlugin
26
37
  });
27
38
  module.exports = __toCommonJS(index_exports);
@@ -30,7 +41,7 @@ module.exports = __toCommonJS(index_exports);
30
41
  var import_backend_plugin_api = require("@backstage/backend-plugin-api");
31
42
 
32
43
  // src/router.ts
33
- var import_express = require("express");
44
+ var import_express = __toESM(require("express"));
34
45
  var import_catalog_client = require("@backstage/catalog-client");
35
46
 
36
47
  // src/client.ts
@@ -50,13 +61,15 @@ var LiteLLMClient = class {
50
61
  signal: controller.signal,
51
62
  headers: {
52
63
  "Content-Type": "application/json",
53
- "Authorization": `Bearer ${this.masterKey}`,
64
+ Authorization: `Bearer ${this.masterKey}`,
54
65
  ...options.headers
55
66
  }
56
67
  });
57
68
  if (!response.ok) {
58
69
  const errorBody = await response.text();
59
- const err = new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);
70
+ const err = new Error(
71
+ `LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`
72
+ );
60
73
  err.status = response.status;
61
74
  throw err;
62
75
  }
@@ -84,11 +97,36 @@ var LiteLLMClient = class {
84
97
  body: JSON.stringify(payload)
85
98
  });
86
99
  }
100
+ /**
101
+ * Updates an existing LiteLLM user record. Used as a defensive follow-up
102
+ * after /user/new because the upsert path of /user/new has been observed
103
+ * to silently drop fields like user_role under concurrent inserts.
104
+ */
105
+ async updateUser(payload) {
106
+ return this.request("/user/update", {
107
+ method: "POST",
108
+ body: JSON.stringify(payload)
109
+ });
110
+ }
111
+ /**
112
+ * Returns the keys belonging to a user.
113
+ *
114
+ * Implementation note: LiteLLM's `/key/info` endpoint requires a `key`
115
+ * hash and returns 404 when only `user_id` is passed. The correct way
116
+ * to enumerate a user's keys is `/user/info?user_id=X`, which embeds
117
+ * a `keys` array with per-key metadata. We unwrap that array and
118
+ * normalise field names to match the frontend VirtualKey shape
119
+ * (LiteLLM exposes `key_name` for the masked display value and
120
+ * `expires` instead of `expires_at`).
121
+ */
87
122
  async listKeys(userId) {
88
- const query = userId ? `?user_id=${encodeURIComponent(userId)}` : "";
123
+ if (!userId) return [];
89
124
  try {
90
- const response = await this.request(`/key/info${query}`);
91
- return Array.isArray(response) ? response : response.info ?? [];
125
+ const response = await this.request(
126
+ `/user/info?user_id=${encodeURIComponent(userId)}`
127
+ );
128
+ const rawKeys = response.keys ?? [];
129
+ return rawKeys.map(this.toVirtualKey);
92
130
  } catch (err) {
93
131
  if (err.status === 404 || err.message.includes("not found")) {
94
132
  return [];
@@ -96,10 +134,44 @@ var LiteLLMClient = class {
96
134
  throw err;
97
135
  }
98
136
  }
137
+ toVirtualKey(k) {
138
+ return {
139
+ // The hashed `token` never leaves LiteLLM in a usable form; the
140
+ // masked `key_name` ("sk-...XXXX") is what the UI displays. Fall
141
+ // back to `token` only when `key_name` is missing.
142
+ key: k.key_name ?? k.token,
143
+ token: k.token,
144
+ key_alias: k.key_alias ?? void 0,
145
+ created_at: k.created_at,
146
+ expires_at: k.expires ?? void 0,
147
+ spend: k.spend ?? 0,
148
+ max_budget: k.max_budget ?? void 0,
149
+ tpm_limit: k.tpm_limit ?? void 0,
150
+ rpm_limit: k.rpm_limit ?? void 0,
151
+ models: k.models ?? [],
152
+ user_id: k.user_id ?? void 0
153
+ };
154
+ }
155
+ /**
156
+ * Creates a new virtual key on the LiteLLM proxy.
157
+ *
158
+ * Implementation notes — both required to avoid silently-empty keys:
159
+ * 1. The body must be the plain payload. An earlier version wrapped
160
+ * it as `{ json: request }`; LiteLLM doesn't unwrap that envelope
161
+ * and treats the request as having no fields, returning a key
162
+ * with null alias / models / budget / limits.
163
+ * 2. LiteLLM expects `key_alias`, not `alias`. Without the rename,
164
+ * the alias the user typed is dropped on the floor.
165
+ */
99
166
  async generateKey(request) {
167
+ const { alias, ...rest } = request;
168
+ const payload = {
169
+ ...rest,
170
+ ...alias && { key_alias: alias }
171
+ };
100
172
  return this.request("/key/generate", {
101
173
  method: "POST",
102
- body: JSON.stringify({ json: request })
174
+ body: JSON.stringify(payload)
103
175
  });
104
176
  }
105
177
  async updateKey(request) {
@@ -115,11 +187,15 @@ var LiteLLMClient = class {
115
187
  });
116
188
  }
117
189
  async listModels() {
118
- const response = await this.request("/models");
190
+ const response = await this.request(
191
+ "/models"
192
+ );
119
193
  return Array.isArray(response) ? response : response.data ?? [];
120
194
  }
121
195
  async getTeamInfo(teamId) {
122
- return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
196
+ return this.request(
197
+ `/team/info?team_id=${encodeURIComponent(teamId)}`
198
+ );
123
199
  }
124
200
  emptyUsage() {
125
201
  return {
@@ -243,7 +319,9 @@ var LiteLLMClient = class {
243
319
  });
244
320
  if (userId) params.append("user_id", userId);
245
321
  try {
246
- const response = await this.request(`/user/daily/activity?${params.toString()}`);
322
+ const response = await this.request(
323
+ `/user/daily/activity?${params.toString()}`
324
+ );
247
325
  return this.transformDailyActivity(response);
248
326
  } catch (err) {
249
327
  if (err.status === 404 || err.message.includes("not found")) {
@@ -260,7 +338,9 @@ var LiteLLMClient = class {
260
338
  page_size: "100"
261
339
  });
262
340
  try {
263
- const response = await this.request(`/team/daily/activity?${params.toString()}`);
341
+ const response = await this.request(
342
+ `/team/daily/activity?${params.toString()}`
343
+ );
264
344
  return this.transformDailyActivity(response);
265
345
  } catch (err) {
266
346
  if (err.status === 404 || err.message.includes("not found")) {
@@ -370,6 +450,7 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
370
450
  ...defaults.tpmLimit !== void 0 && { tpm_limit: defaults.tpmLimit },
371
451
  ...defaults.rpmLimit !== void 0 && { rpm_limit: defaults.rpmLimit },
372
452
  ...defaults.userRole && { user_role: defaults.userRole },
453
+ auto_create_key: false,
373
454
  metadata: {
374
455
  ...defaults.metadata,
375
456
  provisioned_by: "backstage",
@@ -537,6 +618,7 @@ async function createRouter(options) {
537
618
  );
538
619
  }
539
620
  const router = (0, import_express.Router)();
621
+ router.use(import_express.default.json());
540
622
  router.get("/health", (_req, res) => {
541
623
  res.json({ status: "ok", provisioning: provisioningEnabled });
542
624
  });
@@ -593,6 +675,19 @@ async function createRouter(options) {
593
675
  });
594
676
  router.post("/keys/generate", async (req, res) => {
595
677
  try {
678
+ const body = req.body ?? {};
679
+ const missing = [];
680
+ if (!body.alias?.trim()) missing.push("alias");
681
+ if (typeof body.max_budget !== "number" || body.max_budget <= 0) {
682
+ missing.push("max_budget (positive number)");
683
+ }
684
+ if (missing.length) {
685
+ res.status(400).json({
686
+ error: "Missing required fields",
687
+ hint: `Required: ${missing.join(", ")}`
688
+ });
689
+ return;
690
+ }
596
691
  const tokenEntityRef = await resolveUserId(req, auth);
597
692
  const resolvedUserId = tokenEntityRef ? toLiteLLMUserId(tokenEntityRef, userIdDomain) : void 0;
598
693
  if (resolvedUserId) {
@@ -608,8 +703,20 @@ async function createRouter(options) {
608
703
  logger
609
704
  );
610
705
  }
706
+ const profile = tokenEntityRef ? await resolveUserProfile(tokenEntityRef, catalogClient, auth, logger) : {};
707
+ const enrichedMetadata = {
708
+ ...body.metadata ?? {},
709
+ created_by_backstage_user: tokenEntityRef ?? "unknown",
710
+ ...profile.email && { created_by_email: profile.email },
711
+ ...profile.displayName && {
712
+ created_by_display_name: profile.displayName
713
+ },
714
+ created_via: "backstage",
715
+ created_at_iso: (/* @__PURE__ */ new Date()).toISOString()
716
+ };
611
717
  const request = {
612
- ...req.body,
718
+ ...body,
719
+ metadata: enrichedMetadata,
613
720
  ...resolvedUserId && { user_id: resolvedUserId }
614
721
  };
615
722
  const result = await client.generateKey(request);
@@ -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 { 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 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
- "names": []
4
+ "sourcesContent": ["export { litellmPlugin } from './plugin';\nexport { litellmPlugin as default } from './plugin';\nexport { createRouter } from './router';\nexport * from './types';\nexport { LiteLLMClient } from './client';\n", "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 express, { 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 resolveUserProfile,\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 } =\n 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=$${\n provisioningDefaults.maxBudget\n }/${provisioningDefaults.budgetDuration}, models=${\n provisioningDefaults.models.length\n ? provisioningDefaults.models.join(',')\n : 'all'\n }, teams=[${provisioningDefaults.teams.join(',')}]`,\n );\n }\n\n const router = Router();\n // JSON body parser. Without this, every POST/PUT endpoint sees an empty\n // req.body. Backstage's httpRouter does not apply a body parser at the\n // plugin-router level, so each plugin must attach its own.\n router.use(express.json());\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 // Only alias + max_budget are required. An empty models array is\n // intentional \u2014 in LiteLLM `models: []` means \"all models the user\n // can access\" which is the desired default. Forcing a selection\n // up front is too restrictive for the common case.\n const body = (req.body ?? {}) as GenerateKeyRequest;\n const missing: string[] = [];\n if (!body.alias?.trim()) missing.push('alias');\n if (typeof body.max_budget !== 'number' || body.max_budget <= 0) {\n missing.push('max_budget (positive number)');\n }\n if (missing.length) {\n res.status(400).json({\n error: 'Missing required fields',\n hint: `Required: ${missing.join(', ')}`,\n });\n return;\n }\n\n const tokenEntityRef = await resolveUserId(req, auth);\n const resolvedUserId = tokenEntityRef\n ? toLiteLLMUserId(tokenEntityRef, userIdDomain)\n : 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 // Stamp ownership into LiteLLM key metadata. LiteLLM's native\n // `created_by` column is only populated when the caller authenticates\n // via JWT/SSO; we always call with the master key, so that column\n // stays null. Enriching `metadata` makes the owner identity visible\n // in LiteLLM's UI and queryable via API.\n const profile = tokenEntityRef\n ? await resolveUserProfile(tokenEntityRef, catalogClient, auth, logger)\n : {};\n const enrichedMetadata = {\n ...(body.metadata ?? {}),\n created_by_backstage_user: tokenEntityRef ?? 'unknown',\n ...(profile.email && { created_by_email: profile.email }),\n ...(profile.displayName && {\n created_by_display_name: profile.displayName,\n }),\n created_via: 'backstage',\n created_at_iso: new Date().toISOString(),\n };\n\n const request: GenerateKeyRequest = {\n ...body,\n metadata: enrichedMetadata,\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 LiteLLMUserKey,\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>(\n path: string,\n options: RequestInit = {},\n ): 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(\n `LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`,\n );\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 /**\n * Updates an existing LiteLLM user record. Used as a defensive follow-up\n * after /user/new because the upsert path of /user/new has been observed\n * to silently drop fields like user_role under concurrent inserts.\n */\n async updateUser(\n payload: Partial<CreateUserRequest> & { user_id: string },\n ): Promise<unknown> {\n return this.request<unknown>('/user/update', {\n method: 'POST',\n body: JSON.stringify(payload),\n });\n }\n\n /**\n * Returns the keys belonging to a user.\n *\n * Implementation note: LiteLLM's `/key/info` endpoint requires a `key`\n * hash and returns 404 when only `user_id` is passed. The correct way\n * to enumerate a user's keys is `/user/info?user_id=X`, which embeds\n * a `keys` array with per-key metadata. We unwrap that array and\n * normalise field names to match the frontend VirtualKey shape\n * (LiteLLM exposes `key_name` for the masked display value and\n * `expires` instead of `expires_at`).\n */\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n if (!userId) return [];\n try {\n const response = await this.request<{ keys?: LiteLLMUserKey[] }>(\n `/user/info?user_id=${encodeURIComponent(userId)}`,\n );\n const rawKeys = response.keys ?? [];\n return rawKeys.map(this.toVirtualKey);\n } catch (err: any) {\n if (err.status === 404 || err.message.includes('not found')) {\n return [];\n }\n throw err;\n }\n }\n\n private toVirtualKey(k: LiteLLMUserKey): VirtualKey {\n return {\n // The hashed `token` never leaves LiteLLM in a usable form; the\n // masked `key_name` (\"sk-...XXXX\") is what the UI displays. Fall\n // back to `token` only when `key_name` is missing.\n key: k.key_name ?? k.token,\n token: k.token,\n key_alias: k.key_alias ?? undefined,\n created_at: k.created_at,\n expires_at: k.expires ?? undefined,\n spend: k.spend ?? 0,\n max_budget: k.max_budget ?? undefined,\n tpm_limit: k.tpm_limit ?? undefined,\n rpm_limit: k.rpm_limit ?? undefined,\n models: k.models ?? [],\n user_id: k.user_id ?? undefined,\n };\n }\n\n /**\n * Creates a new virtual key on the LiteLLM proxy.\n *\n * Implementation notes \u2014 both required to avoid silently-empty keys:\n * 1. The body must be the plain payload. An earlier version wrapped\n * it as `{ json: request }`; LiteLLM doesn't unwrap that envelope\n * and treats the request as having no fields, returning a key\n * with null alias / models / budget / limits.\n * 2. LiteLLM expects `key_alias`, not `alias`. Without the rename,\n * the alias the user typed is dropped on the floor.\n */\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n const { alias, ...rest } = request;\n const payload = {\n ...rest,\n ...(alias && { key_alias: alias }),\n };\n return this.request<GenerateKeyResponse>('/key/generate', {\n method: 'POST',\n body: JSON.stringify(payload),\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[]>(\n '/models',\n );\n return Array.isArray(response) ? response : response.data ?? [];\n }\n\n async getTeamInfo(teamId: string): Promise<TeamInfo> {\n return this.request<TeamInfo>(\n `/team/info?team_id=${encodeURIComponent(teamId)}`,\n );\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)\n ? response.results\n : [];\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(\n startDate: string,\n endDate: string,\n userId?: string,\n _groupBy?: string,\n ): 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>(\n `/user/daily/activity?${params.toString()}`,\n );\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(\n teamId: string,\n startDate: string,\n endDate: string,\n ): 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>(\n `/team/daily/activity?${params.toString()}`,\n );\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
+ "names": ["express"]
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 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}\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}\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 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"],
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.14",
3
+ "version": "0.1.16",
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
- }