@acarmisc/backstage-plugin-litellm-backend 0.8.0 → 0.8.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.
- package/config.d.ts +1 -1
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +1 -1
- package/dist/client.js +10 -6
- package/dist/index.cjs.js +19 -7
- package/dist/index.cjs.js.map +2 -2
- package/dist/provisioning.d.ts +2 -2
- package/dist/provisioning.js +3 -4
- package/dist/router.js +8 -2
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +3 -1
package/config.d.ts
CHANGED
|
@@ -158,7 +158,7 @@ export interface Config {
|
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* Keycloak realm issuer used to fetch JWKS and verify the token issuer,
|
|
161
|
-
* e.g. https://auth.
|
|
161
|
+
* e.g. https://auth.example.com/realms/solution-innovation.
|
|
162
162
|
* Required when enabled.
|
|
163
163
|
*/
|
|
164
164
|
issuer?: string;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface TokenVerifier {
|
|
|
17
17
|
}
|
|
18
18
|
export interface BridgeConfig {
|
|
19
19
|
enabled: boolean;
|
|
20
|
-
/** Keycloak realm issuer, e.g. https://auth.
|
|
20
|
+
/** Keycloak realm issuer, e.g. https://auth.example.com/realms/solution-innovation. */
|
|
21
21
|
issuer?: string;
|
|
22
22
|
/** OIDC public client the CLI uses; checked against azp / aud. */
|
|
23
23
|
clientId: string;
|
package/dist/bridge.js
CHANGED
|
@@ -89,7 +89,7 @@ exports.KeycloakJWTVerifier = KeycloakJWTVerifier;
|
|
|
89
89
|
function newDefaultVerifier(cfg) {
|
|
90
90
|
if (!cfg.issuer) {
|
|
91
91
|
throw new BridgeConfigError('litellm.bridge.issuer is required when litellm.bridge.enabled is true ' +
|
|
92
|
-
'(e.g. https://auth.
|
|
92
|
+
'(e.g. https://auth.example.com/realms/solution-innovation)');
|
|
93
93
|
}
|
|
94
94
|
return new KeycloakJWTVerifier({ issuer: cfg.issuer, clientId: cfg.clientId });
|
|
95
95
|
}
|
package/dist/client.js
CHANGED
|
@@ -31,6 +31,13 @@ class LiteLLMUpstreamError extends Error {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
exports.LiteLLMUpstreamError = LiteLLMUpstreamError;
|
|
34
|
+
function extractModelList(fallback) {
|
|
35
|
+
if (Array.isArray(fallback))
|
|
36
|
+
return fallback;
|
|
37
|
+
if (Array.isArray(fallback?.data))
|
|
38
|
+
return fallback.data;
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
34
41
|
class LiteLLMClient {
|
|
35
42
|
constructor(config, timeout = DEFAULT_TIMEOUT) {
|
|
36
43
|
this.baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
@@ -262,6 +269,7 @@ class LiteLLMClient {
|
|
|
262
269
|
output_cost_per_token: info.output_cost_per_token ?? params.output_cost_per_token,
|
|
263
270
|
max_input_tokens: info.max_input_tokens ?? m.max_input_tokens,
|
|
264
271
|
max_output_tokens: info.max_output_tokens ?? m.max_output_tokens,
|
|
272
|
+
access_groups: info.access_groups ?? m.access_groups,
|
|
265
273
|
};
|
|
266
274
|
});
|
|
267
275
|
const filtered = normalised.filter(m => m.model_name);
|
|
@@ -272,11 +280,7 @@ class LiteLLMClient {
|
|
|
272
280
|
// fall through to /models
|
|
273
281
|
}
|
|
274
282
|
const fallback = await this.request('/models');
|
|
275
|
-
const data =
|
|
276
|
-
? fallback
|
|
277
|
-
: Array.isArray(fallback?.data)
|
|
278
|
-
? fallback.data
|
|
279
|
-
: [];
|
|
283
|
+
const data = extractModelList(fallback);
|
|
280
284
|
return data
|
|
281
285
|
.map((m) => ({
|
|
282
286
|
model_name: m.model_name ?? m.id ?? '',
|
|
@@ -400,7 +404,7 @@ class LiteLLMClient {
|
|
|
400
404
|
};
|
|
401
405
|
if (!kb.key_alias && kmeta.key_alias)
|
|
402
406
|
kb.key_alias = kmeta.key_alias;
|
|
403
|
-
if (kb.team_id
|
|
407
|
+
if (kb.team_id === null && kmeta.team_id)
|
|
404
408
|
kb.team_id = kmeta.team_id;
|
|
405
409
|
if (!kb.models.includes(name))
|
|
406
410
|
kb.models.push(name);
|
package/dist/index.cjs.js
CHANGED
|
@@ -81,6 +81,11 @@ var LiteLLMUpstreamError = class extends Error {
|
|
|
81
81
|
this.param = param;
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
|
+
function extractModelList(fallback) {
|
|
85
|
+
if (Array.isArray(fallback)) return fallback;
|
|
86
|
+
if (Array.isArray(fallback?.data)) return fallback.data;
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
84
89
|
var LiteLLMClient = class {
|
|
85
90
|
constructor(config, timeout = DEFAULT_TIMEOUT) {
|
|
86
91
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
@@ -294,7 +299,8 @@ var LiteLLMClient = class {
|
|
|
294
299
|
input_cost_per_token: info.input_cost_per_token ?? params.input_cost_per_token,
|
|
295
300
|
output_cost_per_token: info.output_cost_per_token ?? params.output_cost_per_token,
|
|
296
301
|
max_input_tokens: info.max_input_tokens ?? m.max_input_tokens,
|
|
297
|
-
max_output_tokens: info.max_output_tokens ?? m.max_output_tokens
|
|
302
|
+
max_output_tokens: info.max_output_tokens ?? m.max_output_tokens,
|
|
303
|
+
access_groups: info.access_groups ?? m.access_groups
|
|
298
304
|
};
|
|
299
305
|
});
|
|
300
306
|
const filtered = normalised.filter((m) => m.model_name);
|
|
@@ -302,7 +308,7 @@ var LiteLLMClient = class {
|
|
|
302
308
|
} catch {
|
|
303
309
|
}
|
|
304
310
|
const fallback = await this.request("/models");
|
|
305
|
-
const data =
|
|
311
|
+
const data = extractModelList(fallback);
|
|
306
312
|
return data.map((m) => ({
|
|
307
313
|
model_name: m.model_name ?? m.id ?? "",
|
|
308
314
|
mode: m.mode ?? "chat",
|
|
@@ -421,7 +427,7 @@ var LiteLLMClient = class {
|
|
|
421
427
|
...emptyModelBucket()
|
|
422
428
|
};
|
|
423
429
|
if (!kb.key_alias && kmeta.key_alias) kb.key_alias = kmeta.key_alias;
|
|
424
|
-
if (kb.team_id
|
|
430
|
+
if (kb.team_id === null && kmeta.team_id) kb.team_id = kmeta.team_id;
|
|
425
431
|
if (!kb.models.includes(name)) kb.models.push(name);
|
|
426
432
|
kb.total_spend += km.spend ?? 0;
|
|
427
433
|
kb.total_tokens += km.total_tokens ?? 0;
|
|
@@ -854,7 +860,7 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
|
|
|
854
860
|
}
|
|
855
861
|
};
|
|
856
862
|
logger.info(
|
|
857
|
-
`Provisioning new LiteLLM user for Backstage identity: ${userId}
|
|
863
|
+
`Provisioning new LiteLLM user for Backstage identity: ${userId}${profile.email ? ` (email=${profile.email})` : ""}`
|
|
858
864
|
);
|
|
859
865
|
try {
|
|
860
866
|
await client.createUser(payload);
|
|
@@ -2399,7 +2405,7 @@ var KeycloakJWTVerifier = class {
|
|
|
2399
2405
|
function newDefaultVerifier(cfg) {
|
|
2400
2406
|
if (!cfg.issuer) {
|
|
2401
2407
|
throw new BridgeConfigError(
|
|
2402
|
-
"litellm.bridge.issuer is required when litellm.bridge.enabled is true (e.g. https://auth.
|
|
2408
|
+
"litellm.bridge.issuer is required when litellm.bridge.enabled is true (e.g. https://auth.example.com/realms/solution-innovation)"
|
|
2403
2409
|
);
|
|
2404
2410
|
}
|
|
2405
2411
|
return new KeycloakJWTVerifier({ issuer: cfg.issuer, clientId: cfg.clientId });
|
|
@@ -2619,12 +2625,18 @@ async function createRouter(options) {
|
|
|
2619
2625
|
const tokenEntityRef = await resolveUserId(req, auth);
|
|
2620
2626
|
const userId = tokenEntityRef ? toLiteLLMUserId(tokenEntityRef, userIdDomain) : req.query.user_id;
|
|
2621
2627
|
if (!userId) {
|
|
2622
|
-
throw
|
|
2628
|
+
throw Object.assign(new Error("Cannot verify key ownership without an authenticated user"), {
|
|
2629
|
+
status: 403,
|
|
2630
|
+
body: { error: "Cannot verify key ownership without an authenticated user" }
|
|
2631
|
+
});
|
|
2623
2632
|
}
|
|
2624
2633
|
const ownKeys = await client.listKeys(userId);
|
|
2625
2634
|
const owns = ownKeys.some((k) => (k.token ?? k.key) === keyId);
|
|
2626
2635
|
if (!owns) {
|
|
2627
|
-
throw
|
|
2636
|
+
throw Object.assign(new Error("Access denied: key does not belong to the caller"), {
|
|
2637
|
+
status: 403,
|
|
2638
|
+
body: { error: "Access denied: key does not belong to the caller" }
|
|
2639
|
+
});
|
|
2628
2640
|
}
|
|
2629
2641
|
return { tokenEntityRef, userId };
|
|
2630
2642
|
}
|