@acarmisc/backstage-plugin-litellm-backend 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.d.ts +15 -0
- package/dist/client.d.ts +5 -1
- package/dist/client.js +41 -3
- package/dist/index.cjs.js +151 -5
- package/dist/index.cjs.js.map +2 -2
- package/dist/provisioning.d.ts +5 -0
- package/dist/provisioning.js +22 -0
- package/dist/router.js +99 -1
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/package.json +2 -2
package/dist/provisioning.d.ts
CHANGED
|
@@ -80,6 +80,11 @@ export declare class ProvisioningError extends Error {
|
|
|
80
80
|
* When provisioning is disabled, throws a ProvisioningError with a clear message.
|
|
81
81
|
*/
|
|
82
82
|
export declare function getOrProvisionUser(client: LiteLLMClient, tokenEntityRef: string | undefined, userId: string | undefined, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<UserInfo>;
|
|
83
|
+
/**
|
|
84
|
+
* Returns true when the user is a member of the given Backstage group entity ref.
|
|
85
|
+
* Used for RBAC gates (e.g. audit log visibility) without the role-config machinery.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isUserMemberOfGroup(userEntityRef: string, group: string, catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<boolean>;
|
|
83
88
|
/**
|
|
84
89
|
* Fetches the user's Backstage group memberships and returns the first matching
|
|
85
90
|
* role config (priority order), or undefined when no role matches.
|
package/dist/provisioning.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.resolveUserId = resolveUserId;
|
|
|
9
9
|
exports.resolveUserProfile = resolveUserProfile;
|
|
10
10
|
exports.provisionUser = provisionUser;
|
|
11
11
|
exports.getOrProvisionUser = getOrProvisionUser;
|
|
12
|
+
exports.isUserMemberOfGroup = isUserMemberOfGroup;
|
|
12
13
|
exports.resolveUserRole = resolveUserRole;
|
|
13
14
|
/**
|
|
14
15
|
* Converts a Backstage user entity ref to a LiteLLM user_id.
|
|
@@ -314,6 +315,27 @@ async function getOrProvisionUser(client, tokenEntityRef, userId, provisioningEn
|
|
|
314
315
|
provisioningInFlight.delete(userId);
|
|
315
316
|
}
|
|
316
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Returns true when the user is a member of the given Backstage group entity ref.
|
|
320
|
+
* Used for RBAC gates (e.g. audit log visibility) without the role-config machinery.
|
|
321
|
+
*/
|
|
322
|
+
async function isUserMemberOfGroup(userEntityRef, group, catalogClient, auth, logger) {
|
|
323
|
+
try {
|
|
324
|
+
const { token } = await auth.getPluginRequestToken({
|
|
325
|
+
onBehalfOf: await auth.getOwnServiceCredentials(),
|
|
326
|
+
targetPluginId: 'catalog',
|
|
327
|
+
});
|
|
328
|
+
const entity = await catalogClient.getEntityByRef(userEntityRef, { token });
|
|
329
|
+
const groups = (entity?.relations ?? [])
|
|
330
|
+
.filter(r => r.type === 'memberOf')
|
|
331
|
+
.map(r => r.targetRef);
|
|
332
|
+
return groups.includes(group);
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
logger.warn(`Could not check group membership for ${userEntityRef}: ${err.message}`);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
317
339
|
/**
|
|
318
340
|
* Fetches the user's Backstage group memberships and returns the first matching
|
|
319
341
|
* role config (priority order), or undefined when no role matches.
|
package/dist/router.js
CHANGED
|
@@ -49,6 +49,7 @@ async function createRouter(options) {
|
|
|
49
49
|
const client = options.client ?? new client_1.LiteLLMClient({ baseUrl, masterKey });
|
|
50
50
|
const { enabled: provisioningEnabled, defaults: provisioningDefaults } = (0, provisioning_1.readProvisioningDefaults)(config);
|
|
51
51
|
const roleConfigs = (0, provisioning_1.readRoleConfigs)(config);
|
|
52
|
+
const auditGroup = config.getOptionalString('litellm.audit.group');
|
|
52
53
|
const catalogClient = new catalog_client_1.CatalogClient({ discoveryApi: discovery });
|
|
53
54
|
if (provisioningEnabled) {
|
|
54
55
|
logger.info(`LiteLLM auto-provisioning enabled — defaults: budget=$${provisioningDefaults.maxBudget}/${provisioningDefaults.budgetDuration}, models=${provisioningDefaults.models.length
|
|
@@ -70,7 +71,10 @@ async function createRouter(options) {
|
|
|
70
71
|
? (0, provisioning_1.toLiteLLMUserId)(tokenEntityRef, userIdDomain)
|
|
71
72
|
: req.query.user_id;
|
|
72
73
|
const userInfo = await (0, provisioning_1.getOrProvisionUser)(client, tokenEntityRef, userId, provisioningEnabled, provisioningDefaults, roleConfigs, catalogClient, auth, logger);
|
|
73
|
-
|
|
74
|
+
const canViewAudit = auditGroup && tokenEntityRef
|
|
75
|
+
? await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, auditGroup, catalogClient, auth, logger)
|
|
76
|
+
: false;
|
|
77
|
+
res.json({ ...userInfo, can_view_audit: canViewAudit });
|
|
74
78
|
}
|
|
75
79
|
catch (error) {
|
|
76
80
|
if (error instanceof provisioning_1.ProvisioningError) {
|
|
@@ -100,6 +104,23 @@ async function createRouter(options) {
|
|
|
100
104
|
res.status(500).json({ error: error.message });
|
|
101
105
|
}
|
|
102
106
|
});
|
|
107
|
+
router.post('/keys/:keyId/regenerate', async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const { keyId } = req.params;
|
|
110
|
+
if (!keyId) {
|
|
111
|
+
res.status(400).json({ error: 'keyId is required' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
115
|
+
const result = await client.regenerateKey(keyId);
|
|
116
|
+
logger.info({ action: 'key.rotate', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
117
|
+
res.json(result);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger.error('Failed to rotate key', error);
|
|
121
|
+
res.status(500).json({ error: error.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
103
124
|
router.post('/keys/generate', async (req, res) => {
|
|
104
125
|
try {
|
|
105
126
|
// Only alias + max_budget are required. An empty models array is
|
|
@@ -151,6 +172,7 @@ async function createRouter(options) {
|
|
|
151
172
|
...(resolvedUserId && { user_id: resolvedUserId }),
|
|
152
173
|
};
|
|
153
174
|
const result = await client.generateKey(request);
|
|
175
|
+
logger.info({ action: 'key.generate', userId: resolvedUserId ?? 'unknown', keyAlias: body.alias });
|
|
154
176
|
res.json(result);
|
|
155
177
|
}
|
|
156
178
|
catch (error) {
|
|
@@ -169,8 +191,10 @@ async function createRouter(options) {
|
|
|
169
191
|
res.status(400).json({ error: 'keyId is required' });
|
|
170
192
|
return;
|
|
171
193
|
}
|
|
194
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
172
195
|
const request = { ...req.body, key: keyId };
|
|
173
196
|
const result = await client.updateKey(request);
|
|
197
|
+
logger.info({ action: 'key.update', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
174
198
|
res.json(result);
|
|
175
199
|
}
|
|
176
200
|
catch (error) {
|
|
@@ -185,7 +209,9 @@ async function createRouter(options) {
|
|
|
185
209
|
res.status(400).json({ error: 'keyId is required' });
|
|
186
210
|
return;
|
|
187
211
|
}
|
|
212
|
+
const deleteEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
188
213
|
await client.deleteKeys({ keys: [keyId] });
|
|
214
|
+
logger.info({ action: 'key.delete', userId: deleteEntityRef ?? 'unknown', keyId });
|
|
189
215
|
res.json({ success: true });
|
|
190
216
|
}
|
|
191
217
|
catch (error) {
|
|
@@ -193,6 +219,78 @@ async function createRouter(options) {
|
|
|
193
219
|
res.status(500).json({ error: error.message });
|
|
194
220
|
}
|
|
195
221
|
});
|
|
222
|
+
router.post('/keys/:keyId/block', async (req, res) => {
|
|
223
|
+
try {
|
|
224
|
+
const { keyId } = req.params;
|
|
225
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
226
|
+
await client.blockKey(keyId);
|
|
227
|
+
logger.info({ action: 'key.block', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
228
|
+
res.json({ success: true });
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
logger.error('Failed to block key', error);
|
|
232
|
+
res.status(500).json({ error: error.message });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
router.post('/keys/:keyId/unblock', async (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
const { keyId } = req.params;
|
|
238
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
239
|
+
await client.unblockKey(keyId);
|
|
240
|
+
logger.info({ action: 'key.unblock', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
241
|
+
res.json({ success: true });
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
logger.error('Failed to unblock key', error);
|
|
245
|
+
res.status(500).json({ error: error.message });
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
router.post('/keys/:keyId/reset_spend', async (req, res) => {
|
|
249
|
+
try {
|
|
250
|
+
const { keyId } = req.params;
|
|
251
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
252
|
+
await client.resetKeySpend(keyId);
|
|
253
|
+
logger.info({ action: 'key.reset_spend', userId: tokenEntityRef ?? 'unknown', keyId });
|
|
254
|
+
res.json({ success: true });
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
logger.error('Failed to reset key spend', error);
|
|
258
|
+
res.status(500).json({ error: error.message });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
router.get('/audit', async (req, res) => {
|
|
262
|
+
if (!auditGroup) {
|
|
263
|
+
res.status(403).json({ error: 'Audit log is not configured (litellm.audit.group not set)' });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const tokenEntityRef = await (0, provisioning_1.resolveUserId)(req, auth);
|
|
267
|
+
if (!tokenEntityRef) {
|
|
268
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const allowed = await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, auditGroup, catalogClient, auth, logger);
|
|
272
|
+
if (!allowed) {
|
|
273
|
+
res.status(403).json({ error: 'Access denied: not a member of the audit group' });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const { page, page_size, start_date, end_date, action, table_name, changed_by } = req.query;
|
|
278
|
+
const result = await client.getAuditLogs({
|
|
279
|
+
page: page ? Number(page) : undefined,
|
|
280
|
+
page_size: page_size ? Number(page_size) : 25,
|
|
281
|
+
start_date,
|
|
282
|
+
end_date,
|
|
283
|
+
action,
|
|
284
|
+
table_name,
|
|
285
|
+
changed_by,
|
|
286
|
+
});
|
|
287
|
+
res.json(result);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
logger.error('Failed to fetch audit logs', error);
|
|
291
|
+
res.status(500).json({ error: error.message });
|
|
292
|
+
}
|
|
293
|
+
});
|
|
196
294
|
router.get('/models', async (_req, res) => {
|
|
197
295
|
try {
|
|
198
296
|
const models = await client.listModels();
|
package/dist/types.cjs.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/types.ts"],
|
|
4
|
-
"sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n}\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"],
|
|
4
|
+
"sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n /** Backstage-computed: true when the user is a member of litellm.audit.group */\n can_view_audit?: boolean;\n}\n\nexport interface TeamMember {\n user_id: string;\n role: 'admin' | 'user';\n}\n\nexport interface TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n}\n\nexport interface VirtualKey {\n key: string;\n token: string;\n key_alias?: string;\n created_at: string;\n expires_at?: string;\n spend: number;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n user_id?: string;\n blocked?: boolean;\n}\n\n/**\n * Shape of a single entry inside LiteLLM's `/user/info` `keys` array.\n * Differs from VirtualKey: uses `expires` (not `expires_at`), exposes\n * both a hashed `token` and a masked `key_name`, and fields are nullable\n * rather than optional.\n */\nexport interface LiteLLMUserKey {\n token: string;\n key_name?: string;\n key_alias?: string | null;\n spend?: number;\n expires?: string | null;\n models?: string[];\n tpm_limit?: number | null;\n rpm_limit?: number | null;\n max_budget?: number | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n}\n\nexport interface ModelInfo {\n model_name: string;\n mode: string;\n supports_function_calling?: boolean;\n supports_vision?: boolean;\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n}\n\nexport interface UsageModelBreakdown {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageKeyBreakdown {\n key_alias?: string;\n team_id?: string | null;\n models: string[];\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyPoint {\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyModelPoint {\n date: string;\n model: string;\n spend: number;\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n usage_by_model: Record<string, UsageModelBreakdown>;\n usage_by_key: Record<string, UsageKeyBreakdown>;\n daily_usage: UsageDailyPoint[];\n daily_by_model: UsageDailyModelPoint[];\n}\n\nexport interface GenerateKeyRequest {\n alias?: string;\n models?: string[];\n duration?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n user_id?: string;\n team_id?: string;\n key_type?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface UpdateKeyRequest {\n key: string;\n key_alias?: string;\n models?: string[];\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n team_id?: string;\n duration?: string;\n}\n\nexport interface GenerateKeyResponse {\n key: string;\n key_alias?: string;\n expires_at?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n}\n\nexport interface DeleteKeyRequest {\n keys: string[];\n}\n\nexport interface LiteLLMConfig {\n baseUrl: string;\n masterKey: string;\n}\n\nexport interface ProvisioningDefaults {\n maxBudget: number;\n budgetDuration: string;\n models: string[];\n teams: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n /**\n * LiteLLM user role applied on /user/new. Defaults to \"internal_user\"\n * which grants self-service Create/Delete/View on the user's own keys.\n * Valid values: proxy_admin, proxy_admin_viewer, internal_user,\n * internal_user_viewer, team.\n */\n userRole?: string;\n metadata: Record<string, string>;\n}\n\nexport interface RoleConfig {\n group: string;\n maxBudget?: number;\n budgetDuration?: string;\n models?: string[];\n teams?: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n userRole?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: string;\n user_alias?: string;\n user_role?: string;\n max_budget?: number;\n budget_duration?: string;\n models?: string[];\n teams?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, string>;\n auto_create_key?: boolean;\n}\n\nexport interface CreateUserResponse {\n user_id: string;\n user_email?: string;\n max_budget?: number;\n models?: string[];\n teams?: string[];\n}\n\nexport interface AuditLogEntry {\n id: string;\n updated_at: string;\n changed_by?: string;\n changed_by_api_key?: string;\n action?: string;\n table_name?: string;\n object_id?: string;\n before_value?: Record<string, unknown> | null;\n updated_values?: Record<string, unknown> | null;\n}\n\nexport interface PaginatedAuditLogs {\n audit_logs: AuditLogEntry[];\n total: number;\n page: number;\n page_size: number;\n total_pages: number;\n}\n\nexport interface AuditLogsParams {\n page?: number;\n page_size?: number;\n start_date?: string;\n end_date?: string;\n action?: string;\n table_name?: string;\n changed_by?: string;\n sort_by?: string;\n sort_order?: 'asc' | 'desc';\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface UserInfo {
|
|
|
9
9
|
current_spend?: number;
|
|
10
10
|
soft_limit?: number;
|
|
11
11
|
hard_limit?: number;
|
|
12
|
+
/** Backstage-computed: true when the user is a member of litellm.audit.group */
|
|
13
|
+
can_view_audit?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface TeamMember {
|
|
14
16
|
user_id: string;
|
|
@@ -36,6 +38,7 @@ export interface VirtualKey {
|
|
|
36
38
|
rpm_limit?: number;
|
|
37
39
|
models?: string[];
|
|
38
40
|
user_id?: string;
|
|
41
|
+
blocked?: boolean;
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
41
44
|
* Shape of a single entry inside LiteLLM's `/user/info` `keys` array.
|
|
@@ -56,6 +59,7 @@ export interface LiteLLMUserKey {
|
|
|
56
59
|
user_id?: string | null;
|
|
57
60
|
team_id?: string | null;
|
|
58
61
|
created_at: string;
|
|
62
|
+
blocked?: boolean | null;
|
|
59
63
|
}
|
|
60
64
|
export interface ModelInfo {
|
|
61
65
|
model_name: string;
|
|
@@ -206,3 +210,32 @@ export interface CreateUserResponse {
|
|
|
206
210
|
models?: string[];
|
|
207
211
|
teams?: string[];
|
|
208
212
|
}
|
|
213
|
+
export interface AuditLogEntry {
|
|
214
|
+
id: string;
|
|
215
|
+
updated_at: string;
|
|
216
|
+
changed_by?: string;
|
|
217
|
+
changed_by_api_key?: string;
|
|
218
|
+
action?: string;
|
|
219
|
+
table_name?: string;
|
|
220
|
+
object_id?: string;
|
|
221
|
+
before_value?: Record<string, unknown> | null;
|
|
222
|
+
updated_values?: Record<string, unknown> | null;
|
|
223
|
+
}
|
|
224
|
+
export interface PaginatedAuditLogs {
|
|
225
|
+
audit_logs: AuditLogEntry[];
|
|
226
|
+
total: number;
|
|
227
|
+
page: number;
|
|
228
|
+
page_size: number;
|
|
229
|
+
total_pages: number;
|
|
230
|
+
}
|
|
231
|
+
export interface AuditLogsParams {
|
|
232
|
+
page?: number;
|
|
233
|
+
page_size?: number;
|
|
234
|
+
start_date?: string;
|
|
235
|
+
end_date?: string;
|
|
236
|
+
action?: string;
|
|
237
|
+
table_name?: string;
|
|
238
|
+
changed_by?: string;
|
|
239
|
+
sort_by?: string;
|
|
240
|
+
sort_order?: 'asc' | 'desc';
|
|
241
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -44,4 +44,4 @@
|
|
|
44
44
|
"@types/node": "^20.0.0"
|
|
45
45
|
},
|
|
46
46
|
"configSchema": "config.d.ts"
|
|
47
|
-
}
|
|
47
|
+
}
|