@acarmisc/backstage-plugin-litellm-backend 0.21.0 → 0.23.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/dist/client.d.ts +12 -1
- package/dist/client.js +91 -0
- package/dist/index.cjs.js +126 -5
- package/dist/index.cjs.js.map +3 -3
- package/dist/router.js +3 -1
- package/dist/routes/insights.d.ts +3 -0
- package/dist/routes/insights.js +43 -0
- package/dist/routes/opencode.js +12 -0
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/router.js
CHANGED
|
@@ -56,6 +56,7 @@ const opencode_2 = require("./routes/opencode");
|
|
|
56
56
|
const teams_1 = require("./routes/teams");
|
|
57
57
|
const objectPermissions_1 = require("./routes/objectPermissions");
|
|
58
58
|
const teamUsage_1 = require("./routes/teamUsage");
|
|
59
|
+
const insights_1 = require("./routes/insights");
|
|
59
60
|
const bridge_1 = require("./routes/bridge");
|
|
60
61
|
const teamFetch_1 = require("./http/teamFetch");
|
|
61
62
|
async function createRouter(options) {
|
|
@@ -311,7 +312,7 @@ async function createRouter(options) {
|
|
|
311
312
|
// string or body — so service/external principals (and anonymous callers)
|
|
312
313
|
// are rejected instead of being allowed to name an arbitrary user_id.
|
|
313
314
|
const requireUser = (0, withUser_1.createRequireUser)(ctx);
|
|
314
|
-
router.use(['/user/info', '/keys', '/usage'], requireUser);
|
|
315
|
+
router.use(['/user/info', '/keys', '/usage', '/insights'], requireUser);
|
|
315
316
|
// Register config routes (/health, /config, /openapi.json, /provisioning/preview)
|
|
316
317
|
(0, config_1.registerConfigRoutes)(router, ctx);
|
|
317
318
|
(0, keys_1.registerKeysRoutes)(router, ctx);
|
|
@@ -319,6 +320,7 @@ async function createRouter(options) {
|
|
|
319
320
|
(0, teams_1.registerTeamsRoutes)(router, ctx);
|
|
320
321
|
(0, objectPermissions_1.registerObjectPermissionsRoutes)(router, ctx);
|
|
321
322
|
(0, teamUsage_1.registerTeamUsageRoutes)(router, ctx);
|
|
323
|
+
(0, insights_1.registerInsightsRoutes)(router, ctx);
|
|
322
324
|
(0, bridge_1.registerBridgeRoutes)(router, ctx, { config, tokenVerifier: options.tokenVerifier });
|
|
323
325
|
return router;
|
|
324
326
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerInsightsRoutes = registerInsightsRoutes;
|
|
4
|
+
const provisioning_1 = require("../provisioning");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const withUser_1 = require("./middleware/withUser");
|
|
7
|
+
function registerInsightsRoutes(router, ctx) {
|
|
8
|
+
const { client, catalogClient, auth, logger, auditGroup } = ctx;
|
|
9
|
+
const requireUser = (0, withUser_1.createRequireUser)(ctx);
|
|
10
|
+
router.get('/insights/spend-logs', requireUser, async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { start_date, end_date, userId, teamId, page, page_size } = req.query;
|
|
13
|
+
if (!start_date || !end_date) {
|
|
14
|
+
res.status(400).json({ error: 'start_date and end_date are required' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const tokenEntityRef = res.locals.tokenEntityRef;
|
|
18
|
+
await (0, withUser_1.getProvisionedUser)(ctx, res);
|
|
19
|
+
const callerId = res.locals.userId;
|
|
20
|
+
const isAudit = auditGroup
|
|
21
|
+
? await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, auditGroup, catalogClient, auth, logger)
|
|
22
|
+
: false;
|
|
23
|
+
// Server-side scoping: only audit-group members may query other
|
|
24
|
+
// users/teams — everyone else is forced to their own user_id.
|
|
25
|
+
const result = await client.getSpendLogs({
|
|
26
|
+
startDate: start_date,
|
|
27
|
+
endDate: end_date,
|
|
28
|
+
userId: isAudit ? userId || callerId : callerId,
|
|
29
|
+
teamId: isAudit ? teamId : undefined,
|
|
30
|
+
page: page ? Number(page) : undefined,
|
|
31
|
+
pageSize: page_size ? Number(page_size) : undefined,
|
|
32
|
+
});
|
|
33
|
+
res.json(result);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof provisioning_1.ProvisioningError) {
|
|
37
|
+
res.status(error.status).json(error.body);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
(0, errors_1.sendError)(res, error, logger, 'fetch spend logs');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
package/dist/routes/opencode.js
CHANGED
|
@@ -149,6 +149,13 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
149
149
|
.catch(() => []);
|
|
150
150
|
// Find a healthy existing key (not blocked).
|
|
151
151
|
const reusable = existing.find(k => k.key_alias === alias && !k.blocked && k.user_id === userId);
|
|
152
|
+
// The gateway requires MCP access on the key itself (require_key_mcp_access_defined), and a
|
|
153
|
+
// key may only list what its team has: copy the team's MCP servers so local clients get
|
|
154
|
+
// the team's tools. A team lookup failure only costs the tools, never the connection.
|
|
155
|
+
const teamPerm = teamId ? (await client.getTeamInfo(teamId).catch(() => undefined))?.object_permission : undefined;
|
|
156
|
+
const mcp = teamPerm?.mcp_servers?.length || teamPerm?.mcp_access_groups?.length
|
|
157
|
+
? { mcp_servers: teamPerm.mcp_servers ?? [], mcp_access_groups: teamPerm.mcp_access_groups ?? [] }
|
|
158
|
+
: undefined;
|
|
152
159
|
// Mint a brand-new key bound to this user (and team) under the slot alias.
|
|
153
160
|
const mintNewKey = async () => {
|
|
154
161
|
const profile = await (0, provisioning_1.resolveUserProfile)(tokenEntityRef, catalogClient, auth, logger);
|
|
@@ -166,6 +173,7 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
166
173
|
...(teamId && { opencode_team: teamId }),
|
|
167
174
|
},
|
|
168
175
|
user_id: userId,
|
|
176
|
+
...(mcp && { object_permission: mcp }),
|
|
169
177
|
});
|
|
170
178
|
return result.key;
|
|
171
179
|
};
|
|
@@ -177,6 +185,10 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
177
185
|
// Rotate the existing key in place via LiteLLM's key regeneration.
|
|
178
186
|
key = (await client.regenerateKey(keyHashOrId)).key;
|
|
179
187
|
rotated = true;
|
|
188
|
+
// Rotation keeps the old permissions: refresh them so team MCP changes arrive too.
|
|
189
|
+
// object_permission is not in the shared UpdateKeyRequest type yet (common 0.2.0).
|
|
190
|
+
if (key && mcp)
|
|
191
|
+
await client.updateKey({ key, object_permission: mcp });
|
|
180
192
|
}
|
|
181
193
|
catch (err) {
|
|
182
194
|
// Key regeneration isn't available on every LiteLLM edition. Only
|
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": ["// Re-export common types for backward compatibility\nexport {\n type UserInfo,\n type TeamMember,\n type TeamObjectPermission,\n type TeamInfo,\n type CreateTeamResponse,\n type VirtualKey,\n type ModelInfo,\n type UsageModelBreakdown,\n type UsageKeyBreakdown,\n type UsageDailyPoint,\n type UsageDailyModelPoint,\n type UsageMetrics,\n type TeamMemberUsage,\n type TeamMemberUsageRow,\n type GenerateKeyRequest,\n type UpdateKeyRequest,\n type GenerateKeyResponse,\n type AuditLogEntry,\n type PaginatedAuditLogs,\n} from '@acarmisc/backstage-plugin-litellm-common';\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: {\n vector_stores?: string[];\n mcp_servers?: string[];\n mcp_access_groups?: string[];\n };\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_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 budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n /** Arbitrary metadata stored on the key record. */\n metadata?: Record<string, unknown> | null;\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\n\nexport interface AuditLogsParams {\n page?: number;\n page_size?: number;\n start_date?: string;\n end_date?: string;\n action?: string;\n table_name?: string;\n changed_by?: string;\n sort_by?: string;\n sort_order?: 'asc' | 'desc';\n}\n"],
|
|
4
|
+
"sourcesContent": ["// Re-export common types for backward compatibility\nexport {\n type UserInfo,\n type TeamMember,\n type TeamObjectPermission,\n type TeamInfo,\n type CreateTeamResponse,\n type VirtualKey,\n type ModelInfo,\n type UsageModelBreakdown,\n type UsageKeyBreakdown,\n type UsageDailyPoint,\n type UsageDailyModelPoint,\n type UsageMetrics,\n type TeamMemberUsage,\n type TeamMemberUsageRow,\n type GenerateKeyRequest,\n type UpdateKeyRequest,\n type GenerateKeyResponse,\n type AuditLogEntry,\n type PaginatedAuditLogs,\n type SpendLogEntry,\n type SpendLogsParams,\n type PaginatedSpendLogs,\n} from '@acarmisc/backstage-plugin-litellm-common';\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: {\n vector_stores?: string[];\n mcp_servers?: string[];\n mcp_access_groups?: string[];\n };\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_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 budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n /** Arbitrary metadata stored on the key record. */\n metadata?: Record<string, unknown> | null;\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\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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type UserInfo, type TeamMember, type TeamObjectPermission, type TeamInfo, type CreateTeamResponse, type VirtualKey, type ModelInfo, type UsageModelBreakdown, type UsageKeyBreakdown, type UsageDailyPoint, type UsageDailyModelPoint, type UsageMetrics, type TeamMemberUsage, type TeamMemberUsageRow, type GenerateKeyRequest, type UpdateKeyRequest, type GenerateKeyResponse, type AuditLogEntry, type PaginatedAuditLogs, } from '@acarmisc/backstage-plugin-litellm-common';
|
|
1
|
+
export { type UserInfo, type TeamMember, type TeamObjectPermission, type TeamInfo, type CreateTeamResponse, type VirtualKey, type ModelInfo, type UsageModelBreakdown, type UsageKeyBreakdown, type UsageDailyPoint, type UsageDailyModelPoint, type UsageMetrics, type TeamMemberUsage, type TeamMemberUsageRow, type GenerateKeyRequest, type UpdateKeyRequest, type GenerateKeyResponse, type AuditLogEntry, type PaginatedAuditLogs, type SpendLogEntry, type SpendLogsParams, type PaginatedSpendLogs, } from '@acarmisc/backstage-plugin-litellm-common';
|
|
2
2
|
export interface CreateTeamRequest {
|
|
3
3
|
team_alias: string;
|
|
4
4
|
models?: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"prepack": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@acarmisc/backstage-plugin-litellm-common": "0.
|
|
38
|
+
"@acarmisc/backstage-plugin-litellm-common": "0.3.0",
|
|
39
39
|
"@backstage/backend-plugin-api": "^1.9.1",
|
|
40
40
|
"@backstage/catalog-client": "^1.9.0",
|
|
41
41
|
"@backstage/config": "^1.3.0",
|