@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 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.ces.abssrv.it/realms/solution-innovation.
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.ces.abssrv.it/realms/solution-innovation. */
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.ces.abssrv.it/realms/solution-innovation)');
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 = Array.isArray(fallback)
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 == null && kmeta.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 = Array.isArray(fallback) ? fallback : Array.isArray(fallback?.data) ? fallback.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 == null && kmeta.team_id) kb.team_id = kmeta.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}` + (profile.email ? ` (email=${profile.email})` : "")
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.ces.abssrv.it/realms/solution-innovation)"
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 { status: 403, body: { error: "Cannot verify key ownership without an authenticated user" } };
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 { status: 403, body: { error: "Access denied: key does not belong to the caller" } };
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
  }