@acarmisc/backstage-plugin-litellm-backend 0.1.0 → 0.1.2

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.
@@ -0,0 +1,18 @@
1
+ import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, DeleteKeyRequest } 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
+ getUserInfo(userId?: string): Promise<UserInfo>;
9
+ listKeys(userId?: string): Promise<VirtualKey[]>;
10
+ generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse>;
11
+ deleteKeys(request: DeleteKeyRequest): Promise<{
12
+ success: boolean;
13
+ }>;
14
+ listModels(): Promise<ModelInfo[]>;
15
+ getTeamInfo(teamId: string): Promise<TeamInfo>;
16
+ getUsage(startDate: string, endDate: string, userId?: string, groupBy?: string): Promise<UsageMetrics>;
17
+ getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
18
+ }
package/dist/client.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LiteLLMClient = void 0;
4
+ const DEFAULT_TIMEOUT = 30000;
5
+ class LiteLLMClient {
6
+ constructor(config, timeout = DEFAULT_TIMEOUT) {
7
+ this.baseUrl = config.baseUrl.replace(/\/$/, '');
8
+ this.masterKey = config.masterKey;
9
+ this.timeout = timeout;
10
+ }
11
+ async request(path, options = {}) {
12
+ const controller = new AbortController();
13
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
14
+ try {
15
+ const response = await fetch(`${this.baseUrl}${path}`, {
16
+ ...options,
17
+ signal: controller.signal,
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'Authorization': `Bearer ${this.masterKey}`,
21
+ ...options.headers,
22
+ },
23
+ });
24
+ if (!response.ok) {
25
+ const errorBody = await response.text();
26
+ throw new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);
27
+ }
28
+ return response.json();
29
+ }
30
+ finally {
31
+ clearTimeout(timeoutId);
32
+ }
33
+ }
34
+ async getUserInfo(userId) {
35
+ const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';
36
+ return this.request(`/user/info${query}`);
37
+ }
38
+ async listKeys(userId) {
39
+ const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';
40
+ const response = await this.request(`/key/info${query}`);
41
+ return Array.isArray(response) ? response : (response.info ?? []);
42
+ }
43
+ async generateKey(request) {
44
+ return this.request('/key/generate', {
45
+ method: 'POST',
46
+ body: JSON.stringify(request),
47
+ });
48
+ }
49
+ async deleteKeys(request) {
50
+ return this.request('/key/delete', {
51
+ method: 'POST',
52
+ body: JSON.stringify(request),
53
+ });
54
+ }
55
+ async listModels() {
56
+ const response = await this.request('/models');
57
+ return Array.isArray(response) ? response : (response.data ?? []);
58
+ }
59
+ async getTeamInfo(teamId) {
60
+ return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
61
+ }
62
+ async getUsage(startDate, endDate, userId, groupBy) {
63
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate });
64
+ if (userId)
65
+ params.append('user_id', userId);
66
+ if (groupBy)
67
+ params.append('group_by', groupBy);
68
+ return this.request(`/usage/keys?${params.toString()}`);
69
+ }
70
+ async getTeamUsage(teamId, startDate, endDate) {
71
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate, team_id: teamId });
72
+ return this.request(`/usage/keys?${params.toString()}`);
73
+ }
74
+ }
75
+ exports.LiteLLMClient = LiteLLMClient;
package/dist/index.cjs.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -67,7 +68,8 @@ var LiteLLMClient = class {
67
68
  }
68
69
  async listKeys(userId) {
69
70
  const query = userId ? `?user_id=${encodeURIComponent(userId)}` : "";
70
- return this.request(`/key/info${query}`);
71
+ const response = await this.request(`/key/info${query}`);
72
+ return Array.isArray(response) ? response : response.info ?? [];
71
73
  }
72
74
  async generateKey(request) {
73
75
  return this.request("/key/generate", {
@@ -82,22 +84,40 @@ var LiteLLMClient = class {
82
84
  });
83
85
  }
84
86
  async listModels() {
85
- return this.request("/models");
87
+ const response = await this.request("/models");
88
+ return Array.isArray(response) ? response : response.data ?? [];
89
+ }
90
+ async getTeamInfo(teamId) {
91
+ return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
86
92
  }
87
93
  async getUsage(startDate, endDate, userId, groupBy) {
88
- const params = new URLSearchParams({
89
- start_date: startDate,
90
- end_date: endDate
91
- });
94
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate });
92
95
  if (userId) params.append("user_id", userId);
93
96
  if (groupBy) params.append("group_by", groupBy);
94
97
  return this.request(`/usage/keys?${params.toString()}`);
95
98
  }
99
+ async getTeamUsage(teamId, startDate, endDate) {
100
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate, team_id: teamId });
101
+ return this.request(`/usage/keys?${params.toString()}`);
102
+ }
96
103
  };
97
104
 
98
105
  // src/router.ts
106
+ async function resolveUserId(req, auth) {
107
+ const rawToken = req.headers.authorization?.slice(7);
108
+ if (!rawToken) return void 0;
109
+ try {
110
+ const credentials = await auth.authenticate(rawToken);
111
+ const principal = credentials.principal;
112
+ if (principal?.type === "user") {
113
+ return principal.userEntityRef;
114
+ }
115
+ } catch {
116
+ }
117
+ return void 0;
118
+ }
99
119
  async function createRouter(options) {
100
- const { config, logger } = options;
120
+ const { config, logger, auth } = options;
101
121
  const baseUrl = config.getString("litellm.baseUrl");
102
122
  const masterKey = config.getString("litellm.masterKey");
103
123
  const client = new LiteLLMClient({ baseUrl, masterKey });
@@ -107,7 +127,8 @@ async function createRouter(options) {
107
127
  });
108
128
  router.get("/user/info", async (req, res) => {
109
129
  try {
110
- const userId = req.query.user_id;
130
+ const tokenUserId = await resolveUserId(req, auth);
131
+ const userId = tokenUserId ?? req.query.user_id;
111
132
  const userInfo = await client.getUserInfo(userId);
112
133
  res.json(userInfo);
113
134
  } catch (error) {
@@ -117,7 +138,8 @@ async function createRouter(options) {
117
138
  });
118
139
  router.get("/keys", async (req, res) => {
119
140
  try {
120
- const userId = req.query.user_id;
141
+ const tokenUserId = await resolveUserId(req, auth);
142
+ const userId = tokenUserId ?? req.query.user_id;
121
143
  const keys = await client.listKeys(userId);
122
144
  res.json(keys);
123
145
  } catch (error) {
@@ -127,7 +149,12 @@ async function createRouter(options) {
127
149
  });
128
150
  router.post("/keys/generate", async (req, res) => {
129
151
  try {
130
- const request = req.body;
152
+ const tokenUserId = await resolveUserId(req, auth);
153
+ const request = {
154
+ ...req.body,
155
+ // Bind generated key to the authenticated user so LiteLLM enforces their limits.
156
+ ...tokenUserId && { user_id: tokenUserId }
157
+ };
131
158
  const result = await client.generateKey(request);
132
159
  res.json(result);
133
160
  } catch (error) {
@@ -158,6 +185,48 @@ async function createRouter(options) {
158
185
  res.status(500).json({ error: error.message });
159
186
  }
160
187
  });
188
+ router.get("/teams", async (req, res) => {
189
+ try {
190
+ const tokenUserId = await resolveUserId(req, auth);
191
+ const userId = tokenUserId ?? req.query.user_id;
192
+ const userInfo = await client.getUserInfo(userId);
193
+ if (!userInfo.teams?.length) {
194
+ res.json([]);
195
+ return;
196
+ }
197
+ const teams = await Promise.all(
198
+ userInfo.teams.map(
199
+ (teamId) => client.getTeamInfo(teamId).catch((err) => {
200
+ logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);
201
+ return null;
202
+ })
203
+ )
204
+ );
205
+ res.json(teams.filter(Boolean));
206
+ } catch (error) {
207
+ logger.error("Failed to fetch teams", error);
208
+ res.status(500).json({ error: error.message });
209
+ }
210
+ });
211
+ router.get("/teams/:teamId/usage", async (req, res) => {
212
+ try {
213
+ const { teamId } = req.params;
214
+ const { start_date, end_date } = req.query;
215
+ if (!start_date || !end_date) {
216
+ res.status(400).json({ error: "start_date and end_date are required" });
217
+ return;
218
+ }
219
+ const usage = await client.getTeamUsage(
220
+ teamId,
221
+ start_date,
222
+ end_date
223
+ );
224
+ res.json(usage);
225
+ } catch (error) {
226
+ logger.error("Failed to fetch team usage", error);
227
+ res.status(500).json({ error: error.message });
228
+ }
229
+ });
161
230
  router.get("/usage", async (req, res) => {
162
231
  try {
163
232
  const { start_date, end_date, user_id, group_by } = req.query;
@@ -165,10 +234,12 @@ async function createRouter(options) {
165
234
  res.status(400).json({ error: "start_date and end_date are required" });
166
235
  return;
167
236
  }
237
+ const tokenUserId = await resolveUserId(req, auth);
238
+ const userId = tokenUserId ?? user_id;
168
239
  const usage = await client.getUsage(
169
240
  start_date,
170
241
  end_date,
171
- user_id,
242
+ userId,
172
243
  group_by
173
244
  );
174
245
  res.json(usage);
@@ -186,15 +257,14 @@ var litellmPlugin = (0, import_backend_plugin_api.createBackendPlugin)({
186
257
  register(reg) {
187
258
  reg.registerInit({
188
259
  deps: {
189
- httpRouter: "coreServices.httpRouter",
190
- config: "coreServices.rootConfig",
191
- logger: "coreServices.logger"
260
+ httpRouter: import_backend_plugin_api.coreServices.httpRouter,
261
+ config: import_backend_plugin_api.coreServices.rootConfig,
262
+ logger: import_backend_plugin_api.coreServices.logger,
263
+ auth: import_backend_plugin_api.coreServices.auth,
264
+ discovery: import_backend_plugin_api.coreServices.discovery
192
265
  },
193
- async init({ httpRouter, config, logger }) {
194
- const router = await createRouter({
195
- config,
196
- logger
197
- });
266
+ async init({ httpRouter, config, logger, auth }) {
267
+ const router = await createRouter({ config, logger, auth });
198
268
  httpRouter.use(router);
199
269
  }
200
270
  });
@@ -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"],
4
- "sourcesContent": ["export { litellmPlugin } from './plugin';\nexport { createRouter } from './router';\nexport * from './types';\nexport { LiteLLMClient } from './client';", "import { createBackendPlugin } from '@backstage/backend-plugin-api';\nimport { createRouter } from './router';\n\nexport const litellmPlugin = createBackendPlugin({\n pluginId: 'litellm',\n register(reg: any) {\n reg.registerInit({\n deps: {\n httpRouter: 'coreServices.httpRouter',\n config: 'coreServices.rootConfig',\n logger: 'coreServices.logger',\n },\n async init({ httpRouter, config, logger }) {\n const router = await createRouter({\n config,\n logger,\n });\n httpRouter.use(router);\n },\n });\n },\n});", "import { Router, Request, Response } from 'express';\nimport { Config } from '@backstage/config';\nimport { LiteLLMClient } from './client';\nimport {\n UserInfo,\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n GenerateKeyRequest,\n GenerateKeyResponse,\n} from './types';\n\nexport interface RouterOptions {\n config: Config;\n logger: any;\n}\n\nexport async function createRouter(options: RouterOptions): Promise<Router> {\n const { config, logger } = options;\n\n const baseUrl = config.getString('litellm.baseUrl');\n const masterKey = config.getString('litellm.masterKey');\n const client = new LiteLLMClient({ baseUrl, masterKey });\n\n const router = Router();\n\n router.get('/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok' });\n });\n\n router.get('/user/info', async (req: Request, res: Response) => {\n try {\n const userId = req.query.user_id as string | undefined;\n const userInfo: UserInfo = await client.getUserInfo(userId);\n res.json(userInfo);\n } catch (error: any) {\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 userId = req.query.user_id as string | undefined;\n const keys: VirtualKey[] = await client.listKeys(userId);\n res.json(keys);\n } catch (error: any) {\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 request: GenerateKeyRequest = req.body;\n const result: GenerateKeyResponse = await client.generateKey(request);\n res.json(result);\n } catch (error: any) {\n logger.error('Failed to generate 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('/usage', async (req: Request, res: Response) => {\n try {\n const { start_date, end_date, user_id, 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 usage: UsageMetrics = await client.getUsage(\n start_date as string,\n end_date as string,\n user_id as string | undefined,\n group_by as string | undefined\n );\n res.json(usage);\n } catch (error: any) {\n logger.error('Failed to fetch usage', error);\n res.status(500).json({ error: error.message });\n }\n });\n\n return router;\n}", "import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, GenerateKeyRequest, GenerateKeyResponse, DeleteKeyRequest } 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 throw new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);\n }\n\n return response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async getUserInfo(userId?: string): Promise<UserInfo> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n return this.request<UserInfo>(`/user/info${query}`);\n }\n\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n return this.request<VirtualKey[]>(`/key/info${query}`);\n }\n\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n return this.request<GenerateKeyResponse>('/key/generate', {\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 return this.request<ModelInfo[]>('/models');\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 });\n if (userId) params.append('user_id', userId);\n if (groupBy) params.append('group_by', groupBy);\n\n return this.request<UsageMetrics>(`/usage/keys?${params.toString()}`);\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 }) {\n const router = await createRouter({ config, logger, auth });\n httpRouter.use(router);\n },\n });\n },\n});\n", "import { Router, Request, Response } from 'express';\nimport { Config } from '@backstage/config';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { LiteLLMClient } from './client';\nimport {\n UserInfo,\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n} from './types';\n\nexport interface RouterOptions {\n config: Config;\n logger: any;\n auth: AuthService;\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 if\n * the request carries no user credential (service-to-service calls).\n */\nasync function resolveUserId(req: Request, auth: AuthService): Promise<string | undefined> {\n const rawToken = req.headers.authorization?.slice(7); // strip \"Bearer \"\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 // token invalid or service token \u2014 fall through\n }\n return undefined;\n}\n\nexport async function createRouter(options: RouterOptions): Promise<Router> {\n const { config, logger, auth } = options;\n\n const baseUrl = config.getString('litellm.baseUrl');\n const masterKey = config.getString('litellm.masterKey');\n const client = new LiteLLMClient({ baseUrl, masterKey });\n\n const router = Router();\n\n router.get('/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok' });\n });\n\n // Resolve user: prefer the identity extracted from the Backstage token so the\n // caller cannot spoof another user_id. Falls back to the query param only when\n // no user token is present (e.g. admin tooling using a service token).\n router.get('/user/info', async (req: Request, res: Response) => {\n try {\n const tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const userInfo: UserInfo = await client.getUserInfo(userId);\n res.json(userInfo);\n } catch (error: any) {\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 tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const keys: VirtualKey[] = await client.listKeys(userId);\n res.json(keys);\n } catch (error: any) {\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 tokenUserId = await resolveUserId(req, auth);\n const request: GenerateKeyRequest = {\n ...req.body,\n // Bind generated key to the authenticated user so LiteLLM enforces their limits.\n ...(tokenUserId && { user_id: tokenUserId }),\n };\n const result: GenerateKeyResponse = await client.generateKey(request);\n res.json(result);\n } catch (error: any) {\n logger.error('Failed to generate 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 // Returns TeamInfo for every team the authenticated user belongs to.\n // Team membership is read from /user/info .teams[], then each team is\n // resolved in parallel via /team/info.\n router.get('/teams', async (req: Request, res: Response) => {\n try {\n const tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const userInfo: UserInfo = await client.getUserInfo(userId);\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 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, user_id, 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 tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (user_id as string | undefined);\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 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 DeleteKeyRequest,\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 throw new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);\n }\n\n return response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async getUserInfo(userId?: string): Promise<UserInfo> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n return this.request<UserInfo>(`/user/info${query}`);\n }\n\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n const response = await this.request<{ info: VirtualKey[] } | VirtualKey[]>(`/key/info${query}`);\n return Array.isArray(response) ? response : (response.info ?? []);\n }\n\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n return this.request<GenerateKeyResponse>('/key/generate', {\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 async getUsage(startDate: string, endDate: string, userId?: string, groupBy?: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({ start_date: startDate, end_date: endDate });\n if (userId) params.append('user_id', userId);\n if (groupBy) params.append('group_by', groupBy);\n return this.request<UsageMetrics>(`/usage/keys?${params.toString()}`);\n }\n\n async getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({ start_date: startDate, end_date: endDate, team_id: teamId });\n return this.request<UsageMetrics>(`/usage/keys?${params.toString()}`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,250 @@
1
+ // src/plugin.ts
2
+ import { coreServices, createBackendPlugin } from "@backstage/backend-plugin-api";
3
+
4
+ // src/router.ts
5
+ import { Router } from "express";
6
+
7
+ // src/client.ts
8
+ var DEFAULT_TIMEOUT = 3e4;
9
+ var LiteLLMClient = class {
10
+ constructor(config, timeout = DEFAULT_TIMEOUT) {
11
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
12
+ this.masterKey = config.masterKey;
13
+ this.timeout = timeout;
14
+ }
15
+ async request(path, options = {}) {
16
+ const controller = new AbortController();
17
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
18
+ try {
19
+ const response = await fetch(`${this.baseUrl}${path}`, {
20
+ ...options,
21
+ signal: controller.signal,
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ "Authorization": `Bearer ${this.masterKey}`,
25
+ ...options.headers
26
+ }
27
+ });
28
+ if (!response.ok) {
29
+ const errorBody = await response.text();
30
+ throw new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);
31
+ }
32
+ return response.json();
33
+ } finally {
34
+ clearTimeout(timeoutId);
35
+ }
36
+ }
37
+ async getUserInfo(userId) {
38
+ const query = userId ? `?user_id=${encodeURIComponent(userId)}` : "";
39
+ return this.request(`/user/info${query}`);
40
+ }
41
+ async listKeys(userId) {
42
+ const query = userId ? `?user_id=${encodeURIComponent(userId)}` : "";
43
+ const response = await this.request(`/key/info${query}`);
44
+ return Array.isArray(response) ? response : response.info ?? [];
45
+ }
46
+ async generateKey(request) {
47
+ return this.request("/key/generate", {
48
+ method: "POST",
49
+ body: JSON.stringify(request)
50
+ });
51
+ }
52
+ async deleteKeys(request) {
53
+ return this.request("/key/delete", {
54
+ method: "POST",
55
+ body: JSON.stringify(request)
56
+ });
57
+ }
58
+ async listModels() {
59
+ const response = await this.request("/models");
60
+ return Array.isArray(response) ? response : response.data ?? [];
61
+ }
62
+ async getTeamInfo(teamId) {
63
+ return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
64
+ }
65
+ async getUsage(startDate, endDate, userId, groupBy) {
66
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate });
67
+ if (userId) params.append("user_id", userId);
68
+ if (groupBy) params.append("group_by", groupBy);
69
+ return this.request(`/usage/keys?${params.toString()}`);
70
+ }
71
+ async getTeamUsage(teamId, startDate, endDate) {
72
+ const params = new URLSearchParams({ start_date: startDate, end_date: endDate, team_id: teamId });
73
+ return this.request(`/usage/keys?${params.toString()}`);
74
+ }
75
+ };
76
+
77
+ // src/router.ts
78
+ async function resolveUserId(req, auth) {
79
+ const rawToken = req.headers.authorization?.slice(7);
80
+ if (!rawToken) return void 0;
81
+ try {
82
+ const credentials = await auth.authenticate(rawToken);
83
+ const principal = credentials.principal;
84
+ if (principal?.type === "user") {
85
+ return principal.userEntityRef;
86
+ }
87
+ } catch {
88
+ }
89
+ return void 0;
90
+ }
91
+ async function createRouter(options) {
92
+ const { config, logger, auth } = options;
93
+ const baseUrl = config.getString("litellm.baseUrl");
94
+ const masterKey = config.getString("litellm.masterKey");
95
+ const client = new LiteLLMClient({ baseUrl, masterKey });
96
+ const router = Router();
97
+ router.get("/health", (_req, res) => {
98
+ res.json({ status: "ok" });
99
+ });
100
+ router.get("/user/info", async (req, res) => {
101
+ try {
102
+ const tokenUserId = await resolveUserId(req, auth);
103
+ const userId = tokenUserId ?? req.query.user_id;
104
+ const userInfo = await client.getUserInfo(userId);
105
+ res.json(userInfo);
106
+ } catch (error) {
107
+ logger.error("Failed to fetch user info", error);
108
+ res.status(500).json({ error: error.message });
109
+ }
110
+ });
111
+ router.get("/keys", async (req, res) => {
112
+ try {
113
+ const tokenUserId = await resolveUserId(req, auth);
114
+ const userId = tokenUserId ?? req.query.user_id;
115
+ const keys = await client.listKeys(userId);
116
+ res.json(keys);
117
+ } catch (error) {
118
+ logger.error("Failed to list keys", error);
119
+ res.status(500).json({ error: error.message });
120
+ }
121
+ });
122
+ router.post("/keys/generate", async (req, res) => {
123
+ try {
124
+ const tokenUserId = await resolveUserId(req, auth);
125
+ const request = {
126
+ ...req.body,
127
+ // Bind generated key to the authenticated user so LiteLLM enforces their limits.
128
+ ...tokenUserId && { user_id: tokenUserId }
129
+ };
130
+ const result = await client.generateKey(request);
131
+ res.json(result);
132
+ } catch (error) {
133
+ logger.error("Failed to generate key", error);
134
+ res.status(500).json({ error: error.message });
135
+ }
136
+ });
137
+ router.delete("/keys/:keyId", async (req, res) => {
138
+ try {
139
+ const { keyId } = req.params;
140
+ if (!keyId) {
141
+ res.status(400).json({ error: "keyId is required" });
142
+ return;
143
+ }
144
+ await client.deleteKeys({ keys: [keyId] });
145
+ res.json({ success: true });
146
+ } catch (error) {
147
+ logger.error("Failed to delete key", error);
148
+ res.status(500).json({ error: error.message });
149
+ }
150
+ });
151
+ router.get("/models", async (_req, res) => {
152
+ try {
153
+ const models = await client.listModels();
154
+ res.json(models);
155
+ } catch (error) {
156
+ logger.error("Failed to list models", error);
157
+ res.status(500).json({ error: error.message });
158
+ }
159
+ });
160
+ router.get("/teams", async (req, res) => {
161
+ try {
162
+ const tokenUserId = await resolveUserId(req, auth);
163
+ const userId = tokenUserId ?? req.query.user_id;
164
+ const userInfo = await client.getUserInfo(userId);
165
+ if (!userInfo.teams?.length) {
166
+ res.json([]);
167
+ return;
168
+ }
169
+ const teams = await Promise.all(
170
+ userInfo.teams.map(
171
+ (teamId) => client.getTeamInfo(teamId).catch((err) => {
172
+ logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);
173
+ return null;
174
+ })
175
+ )
176
+ );
177
+ res.json(teams.filter(Boolean));
178
+ } catch (error) {
179
+ logger.error("Failed to fetch teams", error);
180
+ res.status(500).json({ error: error.message });
181
+ }
182
+ });
183
+ router.get("/teams/:teamId/usage", async (req, res) => {
184
+ try {
185
+ const { teamId } = req.params;
186
+ const { start_date, end_date } = req.query;
187
+ if (!start_date || !end_date) {
188
+ res.status(400).json({ error: "start_date and end_date are required" });
189
+ return;
190
+ }
191
+ const usage = await client.getTeamUsage(
192
+ teamId,
193
+ start_date,
194
+ end_date
195
+ );
196
+ res.json(usage);
197
+ } catch (error) {
198
+ logger.error("Failed to fetch team usage", error);
199
+ res.status(500).json({ error: error.message });
200
+ }
201
+ });
202
+ router.get("/usage", async (req, res) => {
203
+ try {
204
+ const { start_date, end_date, user_id, group_by } = req.query;
205
+ if (!start_date || !end_date) {
206
+ res.status(400).json({ error: "start_date and end_date are required" });
207
+ return;
208
+ }
209
+ const tokenUserId = await resolveUserId(req, auth);
210
+ const userId = tokenUserId ?? user_id;
211
+ const usage = await client.getUsage(
212
+ start_date,
213
+ end_date,
214
+ userId,
215
+ group_by
216
+ );
217
+ res.json(usage);
218
+ } catch (error) {
219
+ logger.error("Failed to fetch usage", error);
220
+ res.status(500).json({ error: error.message });
221
+ }
222
+ });
223
+ return router;
224
+ }
225
+
226
+ // src/plugin.ts
227
+ var litellmPlugin = createBackendPlugin({
228
+ pluginId: "litellm",
229
+ register(reg) {
230
+ reg.registerInit({
231
+ deps: {
232
+ httpRouter: coreServices.httpRouter,
233
+ config: coreServices.rootConfig,
234
+ logger: coreServices.logger,
235
+ auth: coreServices.auth,
236
+ discovery: coreServices.discovery
237
+ },
238
+ async init({ httpRouter, config, logger, auth }) {
239
+ const router = await createRouter({ config, logger, auth });
240
+ httpRouter.use(router);
241
+ }
242
+ });
243
+ }
244
+ });
245
+ export {
246
+ LiteLLMClient,
247
+ createRouter,
248
+ litellmPlugin
249
+ };
250
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugin.ts", "../src/router.ts", "../src/client.ts"],
4
+ "sourcesContent": ["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 }) {\n const router = await createRouter({ config, logger, auth });\n httpRouter.use(router);\n },\n });\n },\n});\n", "import { Router, Request, Response } from 'express';\nimport { Config } from '@backstage/config';\nimport { AuthService } from '@backstage/backend-plugin-api';\nimport { LiteLLMClient } from './client';\nimport {\n UserInfo,\n VirtualKey,\n ModelInfo,\n UsageMetrics,\n TeamInfo,\n GenerateKeyRequest,\n GenerateKeyResponse,\n} from './types';\n\nexport interface RouterOptions {\n config: Config;\n logger: any;\n auth: AuthService;\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 if\n * the request carries no user credential (service-to-service calls).\n */\nasync function resolveUserId(req: Request, auth: AuthService): Promise<string | undefined> {\n const rawToken = req.headers.authorization?.slice(7); // strip \"Bearer \"\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 // token invalid or service token \u2014 fall through\n }\n return undefined;\n}\n\nexport async function createRouter(options: RouterOptions): Promise<Router> {\n const { config, logger, auth } = options;\n\n const baseUrl = config.getString('litellm.baseUrl');\n const masterKey = config.getString('litellm.masterKey');\n const client = new LiteLLMClient({ baseUrl, masterKey });\n\n const router = Router();\n\n router.get('/health', (_req: Request, res: Response) => {\n res.json({ status: 'ok' });\n });\n\n // Resolve user: prefer the identity extracted from the Backstage token so the\n // caller cannot spoof another user_id. Falls back to the query param only when\n // no user token is present (e.g. admin tooling using a service token).\n router.get('/user/info', async (req: Request, res: Response) => {\n try {\n const tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const userInfo: UserInfo = await client.getUserInfo(userId);\n res.json(userInfo);\n } catch (error: any) {\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 tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const keys: VirtualKey[] = await client.listKeys(userId);\n res.json(keys);\n } catch (error: any) {\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 tokenUserId = await resolveUserId(req, auth);\n const request: GenerateKeyRequest = {\n ...req.body,\n // Bind generated key to the authenticated user so LiteLLM enforces their limits.\n ...(tokenUserId && { user_id: tokenUserId }),\n };\n const result: GenerateKeyResponse = await client.generateKey(request);\n res.json(result);\n } catch (error: any) {\n logger.error('Failed to generate 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 // Returns TeamInfo for every team the authenticated user belongs to.\n // Team membership is read from /user/info .teams[], then each team is\n // resolved in parallel via /team/info.\n router.get('/teams', async (req: Request, res: Response) => {\n try {\n const tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (req.query.user_id as string | undefined);\n const userInfo: UserInfo = await client.getUserInfo(userId);\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 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, user_id, 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 tokenUserId = await resolveUserId(req, auth);\n const userId = tokenUserId ?? (user_id as string | undefined);\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 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 DeleteKeyRequest,\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 throw new Error(`LiteLLM API error: ${response.status} ${response.statusText} - ${errorBody}`);\n }\n\n return response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async getUserInfo(userId?: string): Promise<UserInfo> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n return this.request<UserInfo>(`/user/info${query}`);\n }\n\n async listKeys(userId?: string): Promise<VirtualKey[]> {\n const query = userId ? `?user_id=${encodeURIComponent(userId)}` : '';\n const response = await this.request<{ info: VirtualKey[] } | VirtualKey[]>(`/key/info${query}`);\n return Array.isArray(response) ? response : (response.info ?? []);\n }\n\n async generateKey(request: GenerateKeyRequest): Promise<GenerateKeyResponse> {\n return this.request<GenerateKeyResponse>('/key/generate', {\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 async getUsage(startDate: string, endDate: string, userId?: string, groupBy?: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({ start_date: startDate, end_date: endDate });\n if (userId) params.append('user_id', userId);\n if (groupBy) params.append('group_by', groupBy);\n return this.request<UsageMetrics>(`/usage/keys?${params.toString()}`);\n }\n\n async getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics> {\n const params = new URLSearchParams({ start_date: startDate, end_date: endDate, team_id: teamId });\n return this.request<UsageMetrics>(`/usage/keys?${params.toString()}`);\n }\n}\n"],
5
+ "mappings": ";AAAA,SAAS,cAAc,2BAA2B;;;ACAlD,SAAS,cAAiC;;;ACY1C,IAAM,kBAAkB;AAEjB,IAAM,gBAAN,MAAoB;AAAA,EAKzB,YAAY,QAAuB,UAAU,iBAAiB;AAC5D,SAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC/C,SAAK,YAAY,OAAO;AACxB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAc,QAAW,MAAc,UAAuB,CAAC,GAAe;AAC5E,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAEnE,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,QACrD,GAAG;AAAA,QACH,QAAQ,WAAW;AAAA,QACnB,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB,UAAU,KAAK,SAAS;AAAA,UACzC,GAAG,QAAQ;AAAA,QACb;AAAA,MACF,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,IAAI,SAAS,UAAU,MAAM,SAAS,EAAE;AAAA,MAC/F;AAEA,aAAO,SAAS,KAAK;AAAA,IACvB,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,QAAoC;AACpD,UAAM,QAAQ,SAAS,YAAY,mBAAmB,MAAM,CAAC,KAAK;AAClE,WAAO,KAAK,QAAkB,aAAa,KAAK,EAAE;AAAA,EACpD;AAAA,EAEA,MAAM,SAAS,QAAwC;AACrD,UAAM,QAAQ,SAAS,YAAY,mBAAmB,MAAM,CAAC,KAAK;AAClE,UAAM,WAAW,MAAM,KAAK,QAA+C,YAAY,KAAK,EAAE;AAC9F,WAAO,MAAM,QAAQ,QAAQ,IAAI,WAAY,SAAS,QAAQ,CAAC;AAAA,EACjE;AAAA,EAEA,MAAM,YAAY,SAA2D;AAC3E,WAAO,KAAK,QAA6B,iBAAiB;AAAA,MACxD,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,SAA0D;AACzE,WAAO,KAAK,QAA8B,eAAe;AAAA,MACvD,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAmC;AACvC,UAAM,WAAW,MAAM,KAAK,QAA6C,SAAS;AAClF,WAAO,MAAM,QAAQ,QAAQ,IAAI,WAAY,SAAS,QAAQ,CAAC;AAAA,EACjE;AAAA,EAEA,MAAM,YAAY,QAAmC;AACnD,WAAO,KAAK,QAAkB,sBAAsB,mBAAmB,MAAM,CAAC,EAAE;AAAA,EAClF;AAAA,EAEA,MAAM,SAAS,WAAmB,SAAiB,QAAiB,SAAyC;AAC3G,UAAM,SAAS,IAAI,gBAAgB,EAAE,YAAY,WAAW,UAAU,QAAQ,CAAC;AAC/E,QAAI,OAAQ,QAAO,OAAO,WAAW,MAAM;AAC3C,QAAI,QAAS,QAAO,OAAO,YAAY,OAAO;AAC9C,WAAO,KAAK,QAAsB,eAAe,OAAO,SAAS,CAAC,EAAE;AAAA,EACtE;AAAA,EAEA,MAAM,aAAa,QAAgB,WAAmB,SAAwC;AAC5F,UAAM,SAAS,IAAI,gBAAgB,EAAE,YAAY,WAAW,UAAU,SAAS,SAAS,OAAO,CAAC;AAChG,WAAO,KAAK,QAAsB,eAAe,OAAO,SAAS,CAAC,EAAE;AAAA,EACtE;AACF;;;ADvEA,eAAe,cAAc,KAAc,MAAgD;AACzF,QAAM,WAAW,IAAI,QAAQ,eAAe,MAAM,CAAC;AACnD,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,cAAc,MAAM,KAAK,aAAa,QAAQ;AACpD,UAAM,YAAY,YAAY;AAC9B,QAAI,WAAW,SAAS,QAAQ;AAC9B,aAAO,UAAU;AAAA,IACnB;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,eAAsB,aAAa,SAAyC;AAC1E,QAAM,EAAE,QAAQ,QAAQ,KAAK,IAAI;AAEjC,QAAM,UAAU,OAAO,UAAU,iBAAiB;AAClD,QAAM,YAAY,OAAO,UAAU,mBAAmB;AACtD,QAAM,SAAS,IAAI,cAAc,EAAE,SAAS,UAAU,CAAC;AAEvD,QAAM,SAAS,OAAO;AAEtB,SAAO,IAAI,WAAW,CAAC,MAAe,QAAkB;AACtD,QAAI,KAAK,EAAE,QAAQ,KAAK,CAAC;AAAA,EAC3B,CAAC;AAKD,SAAO,IAAI,cAAc,OAAO,KAAc,QAAkB;AAC9D,QAAI;AACF,YAAM,cAAc,MAAM,cAAc,KAAK,IAAI;AACjD,YAAM,SAAS,eAAgB,IAAI,MAAM;AACzC,YAAM,WAAqB,MAAM,OAAO,YAAY,MAAM;AAC1D,UAAI,KAAK,QAAQ;AAAA,IACnB,SAAS,OAAY;AACnB,aAAO,MAAM,6BAA6B,KAAK;AAC/C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,OAAO,KAAc,QAAkB;AACzD,QAAI;AACF,YAAM,cAAc,MAAM,cAAc,KAAK,IAAI;AACjD,YAAM,SAAS,eAAgB,IAAI,MAAM;AACzC,YAAM,OAAqB,MAAM,OAAO,SAAS,MAAM;AACvD,UAAI,KAAK,IAAI;AAAA,IACf,SAAS,OAAY;AACnB,aAAO,MAAM,uBAAuB,KAAK;AACzC,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,KAAK,kBAAkB,OAAO,KAAc,QAAkB;AACnE,QAAI;AACF,YAAM,cAAc,MAAM,cAAc,KAAK,IAAI;AACjD,YAAM,UAA8B;AAAA,QAClC,GAAG,IAAI;AAAA;AAAA,QAEP,GAAI,eAAe,EAAE,SAAS,YAAY;AAAA,MAC5C;AACA,YAAM,SAA8B,MAAM,OAAO,YAAY,OAAO;AACpE,UAAI,KAAK,MAAM;AAAA,IACjB,SAAS,OAAY;AACnB,aAAO,MAAM,0BAA0B,KAAK;AAC5C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,OAAO,gBAAgB,OAAO,KAAc,QAAkB;AACnE,QAAI;AACF,YAAM,EAAE,MAAM,IAAI,IAAI;AACtB,UAAI,CAAC,OAAO;AACV,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,oBAAoB,CAAC;AACnD;AAAA,MACF;AACA,YAAM,OAAO,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AACzC,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,OAAY;AACnB,aAAO,MAAM,wBAAwB,KAAK;AAC1C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,IAAI,WAAW,OAAO,MAAe,QAAkB;AAC5D,QAAI;AACF,YAAM,SAAsB,MAAM,OAAO,WAAW;AACpD,UAAI,KAAK,MAAM;AAAA,IACjB,SAAS,OAAY;AACnB,aAAO,MAAM,yBAAyB,KAAK;AAC3C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAKD,SAAO,IAAI,UAAU,OAAO,KAAc,QAAkB;AAC1D,QAAI;AACF,YAAM,cAAc,MAAM,cAAc,KAAK,IAAI;AACjD,YAAM,SAAS,eAAgB,IAAI,MAAM;AACzC,YAAM,WAAqB,MAAM,OAAO,YAAY,MAAM;AAE1D,UAAI,CAAC,SAAS,OAAO,QAAQ;AAC3B,YAAI,KAAK,CAAC,CAAC;AACX;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,QAAQ;AAAA,QAC1B,SAAS,MAAM;AAAA,UAAI,YACjB,OAAO,YAAY,MAAM,EAAE,MAAM,SAAO;AACtC,mBAAO,KAAK,wBAAwB,MAAM,KAAK,IAAI,OAAO,EAAE;AAC5D,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,KAAK,MAAM,OAAO,OAAO,CAAe;AAAA,IAC9C,SAAS,OAAY;AACnB,aAAO,MAAM,yBAAyB,KAAK;AAC3C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,IAAI,wBAAwB,OAAO,KAAc,QAAkB;AACxE,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,IAAI;AACvB,YAAM,EAAE,YAAY,SAAS,IAAI,IAAI;AACrC,UAAI,CAAC,cAAc,CAAC,UAAU;AAC5B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uCAAuC,CAAC;AACtE;AAAA,MACF;AACA,YAAM,QAAsB,MAAM,OAAO;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,KAAK,KAAK;AAAA,IAChB,SAAS,OAAY;AACnB,aAAO,MAAM,8BAA8B,KAAK;AAChD,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO,IAAI,UAAU,OAAO,KAAc,QAAkB;AAC1D,QAAI;AACF,YAAM,EAAE,YAAY,UAAU,SAAS,SAAS,IAAI,IAAI;AACxD,UAAI,CAAC,cAAc,CAAC,UAAU;AAC5B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uCAAuC,CAAC;AACtE;AAAA,MACF;AACA,YAAM,cAAc,MAAM,cAAc,KAAK,IAAI;AACjD,YAAM,SAAS,eAAgB;AAC/B,YAAM,QAAsB,MAAM,OAAO;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,KAAK,KAAK;AAAA,IAChB,SAAS,OAAY;AACnB,aAAO,MAAM,yBAAyB,KAAK;AAC3C,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AD9LO,IAAM,gBAAgB,oBAAoB;AAAA,EAC/C,UAAU;AAAA,EACV,SAAS,KAAK;AACZ,QAAI,aAAa;AAAA,MACf,MAAM;AAAA,QACJ,YAAY,aAAa;AAAA,QACzB,QAAQ,aAAa;AAAA,QACrB,QAAQ,aAAa;AAAA,QACrB,MAAM,aAAa;AAAA,QACnB,WAAW,aAAa;AAAA,MAC1B;AAAA,MACA,MAAM,KAAK,EAAE,YAAY,QAAQ,QAAQ,KAAK,GAAG;AAC/C,cAAM,SAAS,MAAM,aAAa,EAAE,QAAQ,QAAQ,KAAK,CAAC;AAC1D,mBAAW,IAAI,MAAM;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF,CAAC;",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.LiteLLMClient = exports.createRouter = exports.litellmPlugin = void 0;
18
+ var plugin_1 = require("./plugin");
19
+ Object.defineProperty(exports, "litellmPlugin", { enumerable: true, get: function () { return plugin_1.litellmPlugin; } });
20
+ var router_1 = require("./router");
21
+ Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return router_1.createRouter; } });
22
+ __exportStar(require("./types"), exports);
23
+ var client_1 = require("./client");
24
+ Object.defineProperty(exports, "LiteLLMClient", { enumerable: true, get: function () { return client_1.LiteLLMClient; } });
@@ -0,0 +1 @@
1
+ export declare const litellmPlugin: import("@backstage/backend-plugin-api").BackendFeature;
package/dist/plugin.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.litellmPlugin = void 0;
4
+ const backend_plugin_api_1 = require("@backstage/backend-plugin-api");
5
+ const router_1 = require("./router");
6
+ exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
7
+ pluginId: 'litellm',
8
+ register(reg) {
9
+ reg.registerInit({
10
+ deps: {
11
+ httpRouter: backend_plugin_api_1.coreServices.httpRouter,
12
+ config: backend_plugin_api_1.coreServices.rootConfig,
13
+ logger: backend_plugin_api_1.coreServices.logger,
14
+ auth: backend_plugin_api_1.coreServices.auth,
15
+ discovery: backend_plugin_api_1.coreServices.discovery,
16
+ },
17
+ async init({ httpRouter, config, logger, auth }) {
18
+ const router = await (0, router_1.createRouter)({ config, logger, auth });
19
+ httpRouter.use(router);
20
+ },
21
+ });
22
+ },
23
+ });
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import { Config } from '@backstage/config';
3
+ import { AuthService } from '@backstage/backend-plugin-api';
4
+ export interface RouterOptions {
5
+ config: Config;
6
+ logger: any;
7
+ auth: AuthService;
8
+ }
9
+ export declare function createRouter(options: RouterOptions): Promise<Router>;
package/dist/router.js ADDED
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRouter = createRouter;
4
+ const express_1 = require("express");
5
+ const client_1 = require("./client");
6
+ /**
7
+ * Extracts the authenticated Backstage user identity from the request token.
8
+ * Returns the userEntityRef (e.g. "user:default/john.doe") or undefined if
9
+ * the request carries no user credential (service-to-service calls).
10
+ */
11
+ async function resolveUserId(req, auth) {
12
+ const rawToken = req.headers.authorization?.slice(7); // strip "Bearer "
13
+ if (!rawToken)
14
+ return undefined;
15
+ try {
16
+ const credentials = await auth.authenticate(rawToken);
17
+ const principal = credentials.principal;
18
+ if (principal?.type === 'user') {
19
+ return principal.userEntityRef;
20
+ }
21
+ }
22
+ catch {
23
+ // token invalid or service token — fall through
24
+ }
25
+ return undefined;
26
+ }
27
+ async function createRouter(options) {
28
+ const { config, logger, auth } = options;
29
+ const baseUrl = config.getString('litellm.baseUrl');
30
+ const masterKey = config.getString('litellm.masterKey');
31
+ const client = new client_1.LiteLLMClient({ baseUrl, masterKey });
32
+ const router = (0, express_1.Router)();
33
+ router.get('/health', (_req, res) => {
34
+ res.json({ status: 'ok' });
35
+ });
36
+ // Resolve user: prefer the identity extracted from the Backstage token so the
37
+ // caller cannot spoof another user_id. Falls back to the query param only when
38
+ // no user token is present (e.g. admin tooling using a service token).
39
+ router.get('/user/info', async (req, res) => {
40
+ try {
41
+ const tokenUserId = await resolveUserId(req, auth);
42
+ const userId = tokenUserId ?? req.query.user_id;
43
+ const userInfo = await client.getUserInfo(userId);
44
+ res.json(userInfo);
45
+ }
46
+ catch (error) {
47
+ logger.error('Failed to fetch user info', error);
48
+ res.status(500).json({ error: error.message });
49
+ }
50
+ });
51
+ router.get('/keys', async (req, res) => {
52
+ try {
53
+ const tokenUserId = await resolveUserId(req, auth);
54
+ const userId = tokenUserId ?? req.query.user_id;
55
+ const keys = await client.listKeys(userId);
56
+ res.json(keys);
57
+ }
58
+ catch (error) {
59
+ logger.error('Failed to list keys', error);
60
+ res.status(500).json({ error: error.message });
61
+ }
62
+ });
63
+ router.post('/keys/generate', async (req, res) => {
64
+ try {
65
+ const tokenUserId = await resolveUserId(req, auth);
66
+ const request = {
67
+ ...req.body,
68
+ // Bind generated key to the authenticated user so LiteLLM enforces their limits.
69
+ ...(tokenUserId && { user_id: tokenUserId }),
70
+ };
71
+ const result = await client.generateKey(request);
72
+ res.json(result);
73
+ }
74
+ catch (error) {
75
+ logger.error('Failed to generate key', error);
76
+ res.status(500).json({ error: error.message });
77
+ }
78
+ });
79
+ router.delete('/keys/:keyId', async (req, res) => {
80
+ try {
81
+ const { keyId } = req.params;
82
+ if (!keyId) {
83
+ res.status(400).json({ error: 'keyId is required' });
84
+ return;
85
+ }
86
+ await client.deleteKeys({ keys: [keyId] });
87
+ res.json({ success: true });
88
+ }
89
+ catch (error) {
90
+ logger.error('Failed to delete key', error);
91
+ res.status(500).json({ error: error.message });
92
+ }
93
+ });
94
+ router.get('/models', async (_req, res) => {
95
+ try {
96
+ const models = await client.listModels();
97
+ res.json(models);
98
+ }
99
+ catch (error) {
100
+ logger.error('Failed to list models', error);
101
+ res.status(500).json({ error: error.message });
102
+ }
103
+ });
104
+ // Returns TeamInfo for every team the authenticated user belongs to.
105
+ // Team membership is read from /user/info .teams[], then each team is
106
+ // resolved in parallel via /team/info.
107
+ router.get('/teams', async (req, res) => {
108
+ try {
109
+ const tokenUserId = await resolveUserId(req, auth);
110
+ const userId = tokenUserId ?? req.query.user_id;
111
+ const userInfo = await client.getUserInfo(userId);
112
+ if (!userInfo.teams?.length) {
113
+ res.json([]);
114
+ return;
115
+ }
116
+ const teams = await Promise.all(userInfo.teams.map(teamId => client.getTeamInfo(teamId).catch(err => {
117
+ logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);
118
+ return null;
119
+ })));
120
+ res.json(teams.filter(Boolean));
121
+ }
122
+ catch (error) {
123
+ logger.error('Failed to fetch teams', error);
124
+ res.status(500).json({ error: error.message });
125
+ }
126
+ });
127
+ router.get('/teams/:teamId/usage', async (req, res) => {
128
+ try {
129
+ const { teamId } = req.params;
130
+ const { start_date, end_date } = req.query;
131
+ if (!start_date || !end_date) {
132
+ res.status(400).json({ error: 'start_date and end_date are required' });
133
+ return;
134
+ }
135
+ const usage = await client.getTeamUsage(teamId, start_date, end_date);
136
+ res.json(usage);
137
+ }
138
+ catch (error) {
139
+ logger.error('Failed to fetch team usage', error);
140
+ res.status(500).json({ error: error.message });
141
+ }
142
+ });
143
+ router.get('/usage', async (req, res) => {
144
+ try {
145
+ const { start_date, end_date, user_id, group_by } = req.query;
146
+ if (!start_date || !end_date) {
147
+ res.status(400).json({ error: 'start_date and end_date are required' });
148
+ return;
149
+ }
150
+ const tokenUserId = await resolveUserId(req, auth);
151
+ const userId = tokenUserId ?? user_id;
152
+ const usage = await client.getUsage(start_date, end_date, userId, group_by);
153
+ res.json(usage);
154
+ }
155
+ catch (error) {
156
+ logger.error('Failed to fetch usage', error);
157
+ res.status(500).json({ error: error.message });
158
+ }
159
+ });
160
+ return router;
161
+ }
package/dist/types.cjs.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -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 email: string;\n team_id?: string;\n team_alias?: string;\n max_budget?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_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 UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n usage_by_model: Record<string, {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n }>;\n daily_usage: Array<{\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n }>;\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}\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}"],
5
- "mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;",
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 UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n usage_by_model: Record<string, {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n }>;\n daily_usage: Array<{\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n }>;\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}\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"],
5
+ "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
package/dist/types.d.ts CHANGED
@@ -1,13 +1,29 @@
1
1
  export interface UserInfo {
2
2
  user_id: string;
3
- email: string;
4
- team_id?: string;
5
- team_alias?: string;
3
+ user_email?: string;
4
+ email?: string;
5
+ teams?: string[];
6
+ models?: string[];
6
7
  max_budget?: number;
8
+ spend?: number;
7
9
  current_spend?: number;
8
10
  soft_limit?: number;
9
11
  hard_limit?: number;
10
12
  }
13
+ export interface TeamMember {
14
+ user_id: string;
15
+ role: 'admin' | 'user';
16
+ }
17
+ export interface TeamInfo {
18
+ team_id: string;
19
+ team_alias?: string;
20
+ max_budget?: number;
21
+ spend: number;
22
+ members_with_roles?: TeamMember[];
23
+ models?: string[];
24
+ tpm_limit?: number;
25
+ rpm_limit?: number;
26
+ }
11
27
  export interface VirtualKey {
12
28
  key: string;
13
29
  key_alias?: string;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
7
7
  "pluginId": "litellm",
8
8
  "pluginPackages": [
9
- "@govai/backstage-plugin-litellm-backend"
9
+ "@acarmisc/backstage-plugin-litellm-backend"
10
10
  ]
11
11
  },
12
12
  "publishConfig": {