@acarmisc/backstage-plugin-litellm-backend 0.8.2 → 0.10.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 +101 -0
- package/dist/client.d.ts +61 -1
- package/dist/client.js +137 -1
- package/dist/index.cjs.js +8987 -22
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -1
- package/dist/permissions.d.ts +11 -0
- package/dist/permissions.js +57 -0
- package/dist/plugin.js +12 -2
- package/dist/provisioning.d.ts +6 -1
- package/dist/provisioning.js +16 -0
- package/dist/router.d.ts +5 -1
- package/dist/router.js +646 -2
- package/dist/teamAdmin.d.ts +150 -0
- package/dist/teamAdmin.js +235 -0
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +34 -0
- package/package.json +6 -3
package/config.d.ts
CHANGED
|
@@ -148,6 +148,107 @@ export interface Config {
|
|
|
148
148
|
teamRequired?: boolean;
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Team administration governance. Allows a designated Backstage group to
|
|
153
|
+
* create and manage LiteLLM teams. The feature is disabled (fail-closed)
|
|
154
|
+
* when unset — all fields default to empty/false to prevent accidental
|
|
155
|
+
* delegation of capabilities. Set this block only when you have reviewed
|
|
156
|
+
* the governance policy and are ready to enable the feature.
|
|
157
|
+
*/
|
|
158
|
+
teamAdmin?: {
|
|
159
|
+
/**
|
|
160
|
+
* Backstage group entity ref whose members may manage teams,
|
|
161
|
+
* e.g. "group:default/litellm-team-admins".
|
|
162
|
+
* The plugin ships a group template at catalog/litellm-team-admins.yaml.
|
|
163
|
+
* When omitted the feature is disabled entirely.
|
|
164
|
+
* @visibility backend
|
|
165
|
+
*/
|
|
166
|
+
group?: string;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* LiteLLM model names an admin may assign to a team.
|
|
170
|
+
* Empty array => none assignable (fail-closed).
|
|
171
|
+
* @default []
|
|
172
|
+
* @visibility backend
|
|
173
|
+
*/
|
|
174
|
+
allowedModels?: string[];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* LiteLLM model access-group names an admin may assign to a team.
|
|
178
|
+
* References access_groups defined in litellm.model_info configuration.
|
|
179
|
+
* Empty array => none allowed.
|
|
180
|
+
* @default []
|
|
181
|
+
* @visibility backend
|
|
182
|
+
*/
|
|
183
|
+
allowedModelAccessGroups?: string[];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hard USD ceiling for max_budget an admin may set on a team.
|
|
187
|
+
* When omitted, no admin-settable budget is allowed unless
|
|
188
|
+
* allowUnlimitedBudget is true.
|
|
189
|
+
* @visibility backend
|
|
190
|
+
*/
|
|
191
|
+
maxBudgetCeiling?: number;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Allow an admin to create a team with no budget cap.
|
|
195
|
+
* @default false
|
|
196
|
+
* @visibility backend
|
|
197
|
+
*/
|
|
198
|
+
allowUnlimitedBudget?: boolean;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Vector-store ids/names an admin may attach as team knowledge bases.
|
|
202
|
+
* Empty array => none allowed.
|
|
203
|
+
* @default []
|
|
204
|
+
* @visibility backend
|
|
205
|
+
*/
|
|
206
|
+
allowedVectorStores?: string[];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* MCP server ids/names an admin may attach to a team.
|
|
210
|
+
* Empty array => none allowed.
|
|
211
|
+
* @default []
|
|
212
|
+
* @visibility backend
|
|
213
|
+
*/
|
|
214
|
+
allowedMcpServers?: string[];
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* MCP access-group names an admin may attach to a team.
|
|
218
|
+
* References access_groups defined in litellm.mcp_info configuration.
|
|
219
|
+
* Empty array => none allowed.
|
|
220
|
+
* @default []
|
|
221
|
+
* @visibility backend
|
|
222
|
+
*/
|
|
223
|
+
allowedMcpAccessGroups?: string[];
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Allow an admin to delete a team (vs. only block/deactivate).
|
|
227
|
+
* @default false
|
|
228
|
+
* @visibility backend
|
|
229
|
+
*/
|
|
230
|
+
allowTeamDelete?: boolean;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Object-permission management (attaching knowledge bases / MCP servers
|
|
234
|
+
* to a team). This is the highest-risk surface — attaching a vector store
|
|
235
|
+
* exposes its documents to every team key, and attaching an MCP server
|
|
236
|
+
* grants tool execution. Keep disabled unless a real permission policy
|
|
237
|
+
* (@backstage-community/plugin-rbac or a custom PermissionPolicy) is
|
|
238
|
+
* installed and the allowedVectorStores / allowedMcpServers allowlists
|
|
239
|
+
* are set.
|
|
240
|
+
*/
|
|
241
|
+
objectPermissions?: {
|
|
242
|
+
/**
|
|
243
|
+
* When true, mount the knowledge-base and MCP management routes.
|
|
244
|
+
* Still gated by the permission framework and the allowlists.
|
|
245
|
+
* @default false
|
|
246
|
+
* @visibility backend
|
|
247
|
+
*/
|
|
248
|
+
enabled?: boolean;
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
|
|
151
252
|
bridge?: {
|
|
152
253
|
/**
|
|
153
254
|
* When true, mount the /bridge/keys, /bridge/keys (POST), /bridge/models
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, DeleteKeyRequest, CreateUserRequest, CreateUserResponse, AuditLogsParams, PaginatedAuditLogs } from './types';
|
|
1
|
+
import { LiteLLMConfig, UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, DeleteKeyRequest, CreateUserRequest, CreateUserResponse, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest, AuditLogsParams, PaginatedAuditLogs } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Typed error for failed upstream LiteLLM responses. Preserves the HTTP
|
|
4
4
|
* status and the structured `param` (e.g. `key_alias`) from the upstream
|
|
@@ -89,6 +89,66 @@ export declare class LiteLLMClient {
|
|
|
89
89
|
* team".
|
|
90
90
|
*/
|
|
91
91
|
getTeamInfo(teamId: string): Promise<TeamInfo>;
|
|
92
|
+
/**
|
|
93
|
+
* Normalises a single team row into the TeamInfo contract. Tolerates both
|
|
94
|
+
* the `/team/info` shape (row wrapped in a `team_info` envelope alongside
|
|
95
|
+
* sibling arrays) and a bare row as returned per-item by `/team/list`.
|
|
96
|
+
* `metadata` is always surfaced so callers can read `owning_group`.
|
|
97
|
+
*/
|
|
98
|
+
private toTeamInfo;
|
|
99
|
+
/**
|
|
100
|
+
* Lists every team known to the LiteLLM proxy. Tolerates the observed
|
|
101
|
+
* response shapes — a bare array, `{ teams: [...] }`, or `{ data: [...] }` —
|
|
102
|
+
* and normalises each row through the same unwrap as `getTeamInfo`.
|
|
103
|
+
*
|
|
104
|
+
* This returns the GLOBAL team list and must never be exposed directly to
|
|
105
|
+
* end users; callers are responsible for scoping the result (e.g. to teams
|
|
106
|
+
* whose `metadata.owning_group` matches the caller's admin group).
|
|
107
|
+
*/
|
|
108
|
+
listTeams(): Promise<TeamInfo[]>;
|
|
109
|
+
createTeam(payload: CreateTeamRequest): Promise<CreateTeamResponse>;
|
|
110
|
+
updateTeam(payload: UpdateTeamRequest): Promise<unknown>;
|
|
111
|
+
deleteTeam(teamId: string): Promise<unknown>;
|
|
112
|
+
blockTeam(teamId: string): Promise<unknown>;
|
|
113
|
+
unblockTeam(teamId: string): Promise<unknown>;
|
|
114
|
+
/**
|
|
115
|
+
* Adds a member to a team. LiteLLM's `/team/member_add` nests the member
|
|
116
|
+
* under a `member` object; `role` is 'user' or 'admin' (we only ever send
|
|
117
|
+
* 'user' from Backstage). `max_budget_in_team` optionally caps that member's
|
|
118
|
+
* spend within the team.
|
|
119
|
+
*/
|
|
120
|
+
teamMemberAdd(payload: {
|
|
121
|
+
team_id: string;
|
|
122
|
+
user_id: string;
|
|
123
|
+
role?: 'user';
|
|
124
|
+
max_budget_in_team?: number;
|
|
125
|
+
}): Promise<unknown>;
|
|
126
|
+
/** Removes a member from a team via LiteLLM's `/team/member_delete`. */
|
|
127
|
+
teamMemberDelete(payload: {
|
|
128
|
+
team_id: string;
|
|
129
|
+
user_id: string;
|
|
130
|
+
}): Promise<unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* Lists the vector stores (knowledge bases) registered on the LiteLLM proxy.
|
|
133
|
+
* Tolerates a bare array, `{ data: [...] }`, or `{ vector_stores: [...] }`.
|
|
134
|
+
* Each entry is normalised to `{ id, name? }` — `id` prefers
|
|
135
|
+
* `vector_store_id` then `id` then `name`.
|
|
136
|
+
*/
|
|
137
|
+
listVectorStores(): Promise<Array<{
|
|
138
|
+
id: string;
|
|
139
|
+
name?: string;
|
|
140
|
+
}>>;
|
|
141
|
+
/**
|
|
142
|
+
* Lists the MCP servers registered on the LiteLLM proxy. Path is
|
|
143
|
+
* `/mcp/server/list`; tolerates a bare array, `{ data: [...] }` or
|
|
144
|
+
* `{ servers: [...] }`. Normalised to `{ id, name?, url? }` — `id` prefers
|
|
145
|
+
* `server_id` then `id` then `alias`/`name`.
|
|
146
|
+
*/
|
|
147
|
+
listMcpServers(): Promise<Array<{
|
|
148
|
+
id: string;
|
|
149
|
+
name?: string;
|
|
150
|
+
url?: string;
|
|
151
|
+
}>>;
|
|
92
152
|
private emptyUsage;
|
|
93
153
|
/**
|
|
94
154
|
* Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter
|
package/dist/client.js
CHANGED
|
@@ -300,9 +300,18 @@ class LiteLLMClient {
|
|
|
300
300
|
*/
|
|
301
301
|
async getTeamInfo(teamId) {
|
|
302
302
|
const raw = await this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
|
|
303
|
+
return this.toTeamInfo(raw, teamId);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Normalises a single team row into the TeamInfo contract. Tolerates both
|
|
307
|
+
* the `/team/info` shape (row wrapped in a `team_info` envelope alongside
|
|
308
|
+
* sibling arrays) and a bare row as returned per-item by `/team/list`.
|
|
309
|
+
* `metadata` is always surfaced so callers can read `owning_group`.
|
|
310
|
+
*/
|
|
311
|
+
toTeamInfo(raw, fallbackId) {
|
|
303
312
|
const inner = raw?.team_info ?? {};
|
|
304
313
|
return {
|
|
305
|
-
team_id: raw?.team_id ?? inner.team_id ??
|
|
314
|
+
team_id: raw?.team_id ?? inner.team_id ?? fallbackId ?? '',
|
|
306
315
|
team_alias: inner.team_alias ?? raw?.team_alias,
|
|
307
316
|
max_budget: inner.max_budget ?? raw?.max_budget,
|
|
308
317
|
spend: inner.spend ?? raw?.spend ?? 0,
|
|
@@ -310,8 +319,135 @@ class LiteLLMClient {
|
|
|
310
319
|
models: inner.models ?? raw?.models,
|
|
311
320
|
tpm_limit: inner.tpm_limit ?? raw?.tpm_limit,
|
|
312
321
|
rpm_limit: inner.rpm_limit ?? raw?.rpm_limit,
|
|
322
|
+
metadata: inner.metadata ?? raw?.metadata,
|
|
323
|
+
object_permission: inner.object_permission ?? raw?.object_permission,
|
|
324
|
+
blocked: inner.blocked ?? raw?.blocked,
|
|
325
|
+
team_member_budget: inner.team_member_budget ?? raw?.team_member_budget,
|
|
313
326
|
};
|
|
314
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Lists every team known to the LiteLLM proxy. Tolerates the observed
|
|
330
|
+
* response shapes — a bare array, `{ teams: [...] }`, or `{ data: [...] }` —
|
|
331
|
+
* and normalises each row through the same unwrap as `getTeamInfo`.
|
|
332
|
+
*
|
|
333
|
+
* This returns the GLOBAL team list and must never be exposed directly to
|
|
334
|
+
* end users; callers are responsible for scoping the result (e.g. to teams
|
|
335
|
+
* whose `metadata.owning_group` matches the caller's admin group).
|
|
336
|
+
*/
|
|
337
|
+
async listTeams() {
|
|
338
|
+
const raw = await this.request('/team/list');
|
|
339
|
+
let rows = [];
|
|
340
|
+
if (Array.isArray(raw))
|
|
341
|
+
rows = raw;
|
|
342
|
+
else if (Array.isArray(raw?.teams))
|
|
343
|
+
rows = raw.teams;
|
|
344
|
+
else if (Array.isArray(raw?.data))
|
|
345
|
+
rows = raw.data;
|
|
346
|
+
return rows.map(row => this.toTeamInfo(row));
|
|
347
|
+
}
|
|
348
|
+
async createTeam(payload) {
|
|
349
|
+
return this.request('/team/new', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
body: JSON.stringify(payload),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
async updateTeam(payload) {
|
|
355
|
+
return this.request('/team/update', {
|
|
356
|
+
method: 'POST',
|
|
357
|
+
body: JSON.stringify(payload),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async deleteTeam(teamId) {
|
|
361
|
+
return this.request('/team/delete', {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
body: JSON.stringify({ team_ids: [teamId] }),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
async blockTeam(teamId) {
|
|
367
|
+
return this.request('/team/block', {
|
|
368
|
+
method: 'POST',
|
|
369
|
+
body: JSON.stringify({ team_id: teamId }),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async unblockTeam(teamId) {
|
|
373
|
+
return this.request('/team/unblock', {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
body: JSON.stringify({ team_id: teamId }),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Adds a member to a team. LiteLLM's `/team/member_add` nests the member
|
|
380
|
+
* under a `member` object; `role` is 'user' or 'admin' (we only ever send
|
|
381
|
+
* 'user' from Backstage). `max_budget_in_team` optionally caps that member's
|
|
382
|
+
* spend within the team.
|
|
383
|
+
*/
|
|
384
|
+
async teamMemberAdd(payload) {
|
|
385
|
+
return this.request('/team/member_add', {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
body: JSON.stringify({
|
|
388
|
+
team_id: payload.team_id,
|
|
389
|
+
member: { user_id: payload.user_id, role: payload.role ?? 'user' },
|
|
390
|
+
...(payload.max_budget_in_team !== undefined && {
|
|
391
|
+
max_budget_in_team: payload.max_budget_in_team,
|
|
392
|
+
}),
|
|
393
|
+
}),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/** Removes a member from a team via LiteLLM's `/team/member_delete`. */
|
|
397
|
+
async teamMemberDelete(payload) {
|
|
398
|
+
return this.request('/team/member_delete', {
|
|
399
|
+
method: 'POST',
|
|
400
|
+
body: JSON.stringify({
|
|
401
|
+
team_id: payload.team_id,
|
|
402
|
+
user_id: payload.user_id,
|
|
403
|
+
}),
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Lists the vector stores (knowledge bases) registered on the LiteLLM proxy.
|
|
408
|
+
* Tolerates a bare array, `{ data: [...] }`, or `{ vector_stores: [...] }`.
|
|
409
|
+
* Each entry is normalised to `{ id, name? }` — `id` prefers
|
|
410
|
+
* `vector_store_id` then `id` then `name`.
|
|
411
|
+
*/
|
|
412
|
+
async listVectorStores() {
|
|
413
|
+
const raw = await this.request('/vector_store/list');
|
|
414
|
+
let rows = [];
|
|
415
|
+
if (Array.isArray(raw))
|
|
416
|
+
rows = raw;
|
|
417
|
+
else if (Array.isArray(raw?.data))
|
|
418
|
+
rows = raw.data;
|
|
419
|
+
else if (Array.isArray(raw?.vector_stores))
|
|
420
|
+
rows = raw.vector_stores;
|
|
421
|
+
return rows
|
|
422
|
+
.map(r => ({
|
|
423
|
+
id: r?.vector_store_id ?? r?.id ?? r?.name ?? '',
|
|
424
|
+
name: r?.vector_store_name ?? r?.name ?? undefined,
|
|
425
|
+
}))
|
|
426
|
+
.filter(r => r.id);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Lists the MCP servers registered on the LiteLLM proxy. Path is
|
|
430
|
+
* `/mcp/server/list`; tolerates a bare array, `{ data: [...] }` or
|
|
431
|
+
* `{ servers: [...] }`. Normalised to `{ id, name?, url? }` — `id` prefers
|
|
432
|
+
* `server_id` then `id` then `alias`/`name`.
|
|
433
|
+
*/
|
|
434
|
+
async listMcpServers() {
|
|
435
|
+
const raw = await this.request('/mcp/server/list');
|
|
436
|
+
let rows = [];
|
|
437
|
+
if (Array.isArray(raw))
|
|
438
|
+
rows = raw;
|
|
439
|
+
else if (Array.isArray(raw?.data))
|
|
440
|
+
rows = raw.data;
|
|
441
|
+
else if (Array.isArray(raw?.servers))
|
|
442
|
+
rows = raw.servers;
|
|
443
|
+
return rows
|
|
444
|
+
.map(r => ({
|
|
445
|
+
id: r?.server_id ?? r?.id ?? r?.alias ?? r?.name ?? '',
|
|
446
|
+
name: r?.alias ?? r?.name ?? undefined,
|
|
447
|
+
url: r?.url ?? undefined,
|
|
448
|
+
}))
|
|
449
|
+
.filter(r => r.id);
|
|
450
|
+
}
|
|
315
451
|
emptyUsage() {
|
|
316
452
|
return {
|
|
317
453
|
total_spend: 0,
|