@acarmisc/backstage-plugin-litellm-backend 0.18.0 → 0.20.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/README.md +58 -0
- package/config.d.ts +41 -0
- package/dist/bridge.d.ts +4 -6
- package/dist/bridge.js +10 -28
- package/dist/client.d.ts +12 -20
- package/dist/client.js +50 -107
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +4 -1
- package/dist/index.cjs.js +519 -445
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -3
- package/dist/openapi.js +22 -0
- package/dist/provisioning.d.ts +16 -1
- package/dist/provisioning.js +94 -37
- package/dist/router.js +17 -18
- package/dist/routes/bridge.js +10 -19
- package/dist/routes/config.js +3 -0
- package/dist/routes/context.d.ts +7 -1
- package/dist/routes/keys.js +10 -23
- package/dist/routes/middleware/withUser.d.ts +4 -2
- package/dist/routes/middleware/withUser.js +6 -2
- package/dist/routes/objectPermissions.js +4 -8
- package/dist/routes/opencode.js +3 -3
- package/dist/routes/teamUsage.js +111 -3
- package/dist/routes/teams.js +13 -24
- package/dist/services/keyService.d.ts +10 -13
- package/dist/services/keyService.js +15 -47
- package/dist/teamMemberUsage.d.ts +34 -0
- package/dist/teamMemberUsage.js +114 -0
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +1 -37
- package/package.json +4 -8
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readMemberBreakdownConfig = readMemberBreakdownConfig;
|
|
4
|
+
exports.memberEmails = memberEmails;
|
|
5
|
+
exports.buildTeamMemberUsage = buildTeamMemberUsage;
|
|
6
|
+
exports.redactTeamMemberUsage = redactTeamMemberUsage;
|
|
7
|
+
function readMemberBreakdownConfig(config) {
|
|
8
|
+
return {
|
|
9
|
+
enabled: config.getOptionalBoolean('litellm.teamUsage.memberBreakdown.enabled') ?? false,
|
|
10
|
+
viewerRoles: config.getOptionalStringArray('litellm.teamUsage.memberBreakdown.viewerRoles') ?? [],
|
|
11
|
+
permissionEnabled: config.getOptionalBoolean('permission.enabled') ?? false,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Emails worth looking up in the catalog for the members of a breakdown:
|
|
16
|
+
* the team roster's emails and email-shaped user ids.
|
|
17
|
+
*/
|
|
18
|
+
function memberEmails(team, keys) {
|
|
19
|
+
const emails = new Set();
|
|
20
|
+
for (const m of team.members_with_roles ?? []) {
|
|
21
|
+
if (m.user_email)
|
|
22
|
+
emails.add(m.user_email.toLowerCase());
|
|
23
|
+
else if (m.user_id?.includes('@'))
|
|
24
|
+
emails.add(m.user_id.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
if (k.user_id?.includes('@'))
|
|
28
|
+
emails.add(k.user_id.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
return [...emails];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Attributes a team's usage to its members. Each key in the team's daily
|
|
34
|
+
* activity is credited to the key's owner (from `/key/list?team_id=`), so the
|
|
35
|
+
* rows add up to the team total. Activity of keys with no known owner
|
|
36
|
+
* (deleted keys, keys without a user) lands in one `user_id: null` row.
|
|
37
|
+
* Team members without activity get zero rows.
|
|
38
|
+
*/
|
|
39
|
+
function buildTeamMemberUsage(teamId, usage, keys, team, profiles = new Map()) {
|
|
40
|
+
const rows = new Map();
|
|
41
|
+
const rowFor = (userId) => {
|
|
42
|
+
let row = rows.get(userId);
|
|
43
|
+
if (!row) {
|
|
44
|
+
row = {
|
|
45
|
+
user_id: userId,
|
|
46
|
+
spend: 0,
|
|
47
|
+
spend_share_pct: 0,
|
|
48
|
+
prompt_tokens: 0,
|
|
49
|
+
completion_tokens: 0,
|
|
50
|
+
total_tokens: 0,
|
|
51
|
+
api_requests: 0,
|
|
52
|
+
successful_requests: 0,
|
|
53
|
+
failed_requests: 0,
|
|
54
|
+
success_rate: null,
|
|
55
|
+
key_count: 0,
|
|
56
|
+
};
|
|
57
|
+
rows.set(userId, row);
|
|
58
|
+
}
|
|
59
|
+
return row;
|
|
60
|
+
};
|
|
61
|
+
for (const m of team.members_with_roles ?? []) {
|
|
62
|
+
if (!m.user_id)
|
|
63
|
+
continue;
|
|
64
|
+
const row = rowFor(m.user_id);
|
|
65
|
+
row.role = m.role;
|
|
66
|
+
if (m.user_email)
|
|
67
|
+
row.user_email = m.user_email;
|
|
68
|
+
}
|
|
69
|
+
const ownerByKey = new Map();
|
|
70
|
+
for (const k of keys) {
|
|
71
|
+
if (!k.user_id || !k.token)
|
|
72
|
+
continue;
|
|
73
|
+
ownerByKey.set(k.token, k.user_id);
|
|
74
|
+
rowFor(k.user_id).key_count += 1;
|
|
75
|
+
}
|
|
76
|
+
for (const [keyHash, k] of Object.entries(usage.usage_by_key ?? {})) {
|
|
77
|
+
const row = rowFor(ownerByKey.get(keyHash) ?? null);
|
|
78
|
+
row.spend += k.total_spend ?? 0;
|
|
79
|
+
row.prompt_tokens += k.prompt_tokens ?? 0;
|
|
80
|
+
row.completion_tokens += k.completion_tokens ?? 0;
|
|
81
|
+
row.total_tokens += k.total_tokens ?? 0;
|
|
82
|
+
row.api_requests += k.api_requests ?? 0;
|
|
83
|
+
row.successful_requests += k.successful_requests ?? 0;
|
|
84
|
+
row.failed_requests += k.failed_requests ?? 0;
|
|
85
|
+
}
|
|
86
|
+
const members = [...rows.values()];
|
|
87
|
+
const totalSpend = members.reduce((sum, r) => sum + r.spend, 0);
|
|
88
|
+
for (const row of members) {
|
|
89
|
+
row.spend_share_pct = totalSpend > 0 ? (row.spend / totalSpend) * 100 : 0;
|
|
90
|
+
const finished = row.successful_requests + row.failed_requests;
|
|
91
|
+
row.success_rate = finished > 0 ? (row.successful_requests / finished) * 100 : null;
|
|
92
|
+
if (!row.user_email && row.user_id?.includes('@'))
|
|
93
|
+
row.user_email = row.user_id;
|
|
94
|
+
const profile = row.user_email && profiles.get(row.user_email.toLowerCase());
|
|
95
|
+
if (profile && profile.displayName)
|
|
96
|
+
row.display_name = profile.displayName;
|
|
97
|
+
}
|
|
98
|
+
members.sort((a, b) => b.spend - a.spend ||
|
|
99
|
+
b.total_tokens - a.total_tokens ||
|
|
100
|
+
String(a.user_id ?? '').localeCompare(String(b.user_id ?? '')));
|
|
101
|
+
return { team_id: teamId, total_spend: totalSpend, members };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Hides dollar amounts: spend becomes 0 and `spend_share_pct` remains the
|
|
105
|
+
* way to compare members. Tokens and requests are kept.
|
|
106
|
+
*/
|
|
107
|
+
function redactTeamMemberUsage(usage) {
|
|
108
|
+
return {
|
|
109
|
+
...usage,
|
|
110
|
+
total_spend: 0,
|
|
111
|
+
budget_hidden: true,
|
|
112
|
+
members: usage.members.map(m => ({ ...m, spend: 0 })),
|
|
113
|
+
};
|
|
114
|
+
}
|
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 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
|
|
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"],
|
|
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 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, } from '@acarmisc/backstage-plugin-litellm-common';
|
|
2
2
|
export interface CreateTeamRequest {
|
|
3
3
|
team_alias: string;
|
|
4
4
|
models?: string[];
|
|
@@ -96,42 +96,6 @@ export interface CreateUserResponse {
|
|
|
96
96
|
models?: string[];
|
|
97
97
|
teams?: string[];
|
|
98
98
|
}
|
|
99
|
-
/** One row from LiteLLM's `/spend/logs` (per-request spend log entry). */
|
|
100
|
-
export interface SpendLogEntry {
|
|
101
|
-
request_id?: string;
|
|
102
|
-
/** ISO timestamp of the call. */
|
|
103
|
-
startTime?: string;
|
|
104
|
-
endTime?: string;
|
|
105
|
-
spend?: number;
|
|
106
|
-
total_tokens?: number;
|
|
107
|
-
prompt_tokens?: number;
|
|
108
|
-
completion_tokens?: number;
|
|
109
|
-
model?: string;
|
|
110
|
-
/** Virtual key alias or hash the request was billed to. */
|
|
111
|
-
api_key?: string;
|
|
112
|
-
user?: string;
|
|
113
|
-
team_id?: string;
|
|
114
|
-
/**
|
|
115
|
-
* Tags attached to the request (e.g. `channel:backstage`,
|
|
116
|
-
* `session:<thread>`, `invoked-by:<user>`). Shape varies across LiteLLM
|
|
117
|
-
* versions (array or object) and is normalised to a string[].
|
|
118
|
-
*/
|
|
119
|
-
request_tags?: string[] | Record<string, string>;
|
|
120
|
-
/** LiteLLM `metadata` blob; carries trace ids and session grouping. */
|
|
121
|
-
metadata?: Record<string, unknown> | string | null;
|
|
122
|
-
}
|
|
123
|
-
export interface SpendLogsParams {
|
|
124
|
-
/** ISO date `YYYY-MM-DD` (inclusive). */
|
|
125
|
-
start_date: string;
|
|
126
|
-
/** ISO date `YYYY-MM-DD` (inclusive). */
|
|
127
|
-
end_date: string;
|
|
128
|
-
/** Restrict to the given virtual key. */
|
|
129
|
-
api_key?: string;
|
|
130
|
-
user_id?: string;
|
|
131
|
-
team_id?: string;
|
|
132
|
-
/** Max rows to return (LiteLLM caps at 1000 per page). */
|
|
133
|
-
page_size?: number;
|
|
134
|
-
}
|
|
135
99
|
export interface AuditLogsParams {
|
|
136
100
|
page?: number;
|
|
137
101
|
page_size?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -32,21 +32,17 @@
|
|
|
32
32
|
"lint": "eslint src --ext .ts",
|
|
33
33
|
"test": "tsc -p tsconfig.test.json && node --test \"dist-test/**/*.test.js\"",
|
|
34
34
|
"api-report": "npm run build && api-extractor run --local --verbose",
|
|
35
|
-
"prepack": "npm run build"
|
|
36
|
-
"postpack": ""
|
|
35
|
+
"prepack": "npm run build"
|
|
37
36
|
},
|
|
38
37
|
"dependencies": {
|
|
39
|
-
"@acarmisc/backstage-plugin-litellm-common": "0.
|
|
38
|
+
"@acarmisc/backstage-plugin-litellm-common": "0.2.0",
|
|
40
39
|
"@backstage/backend-plugin-api": "^1.9.1",
|
|
41
40
|
"@backstage/catalog-client": "^1.9.0",
|
|
42
|
-
"@backstage/catalog-model": "^1.7.0",
|
|
43
41
|
"@backstage/config": "^1.3.0",
|
|
44
42
|
"@backstage/errors": "^1.3.0",
|
|
45
43
|
"@backstage/plugin-permission-common": "^0.9.10",
|
|
46
|
-
"@backstage/types": "^1.2.0",
|
|
47
44
|
"express": "^4.18.2",
|
|
48
|
-
"jose": "^5.9.6"
|
|
49
|
-
"zod": "^3.22.0"
|
|
45
|
+
"jose": "^5.9.6"
|
|
50
46
|
},
|
|
51
47
|
"devDependencies": {
|
|
52
48
|
"@backstage/cli": "^0.36.2",
|