@acarmisc/backstage-plugin-litellm-backend 0.14.0 → 0.16.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.
Files changed (53) hide show
  1. package/config.d.ts +115 -22
  2. package/dist/bridge.d.ts +23 -16
  3. package/dist/bridge.js +51 -11
  4. package/dist/client.d.ts +7 -2
  5. package/dist/client.js +36 -9
  6. package/dist/errors.d.ts +64 -0
  7. package/dist/errors.js +205 -0
  8. package/dist/http/respondTeam.d.ts +15 -0
  9. package/dist/http/respondTeam.js +27 -0
  10. package/dist/http/teamFetch.d.ts +14 -0
  11. package/dist/http/teamFetch.js +44 -0
  12. package/dist/index.cjs.js +4249 -3375
  13. package/dist/index.cjs.js.map +4 -4
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +15 -13
  16. package/dist/openapi.js +17 -0
  17. package/dist/plugin.js +2 -2
  18. package/dist/provisioning.d.ts +17 -11
  19. package/dist/provisioning.js +36 -22
  20. package/dist/router.d.ts +3 -4
  21. package/dist/router.js +188 -1349
  22. package/dist/routes/bridge.d.ts +8 -0
  23. package/dist/routes/bridge.js +130 -0
  24. package/dist/routes/config.d.ts +7 -0
  25. package/dist/routes/config.js +89 -0
  26. package/dist/routes/context.d.ts +55 -0
  27. package/dist/routes/context.js +2 -0
  28. package/dist/routes/keys.d.ts +8 -0
  29. package/dist/routes/keys.js +471 -0
  30. package/dist/routes/middleware/withUser.d.ts +18 -0
  31. package/dist/routes/middleware/withUser.js +41 -0
  32. package/dist/routes/objectPermissions.d.ts +3 -0
  33. package/dist/routes/objectPermissions.js +196 -0
  34. package/dist/routes/opencode.d.ts +3 -0
  35. package/dist/routes/opencode.js +239 -0
  36. package/dist/routes/teamUsage.d.ts +3 -0
  37. package/dist/routes/teamUsage.js +73 -0
  38. package/dist/routes/teams.d.ts +3 -0
  39. package/dist/routes/teams.js +367 -0
  40. package/dist/services/keyService.d.ts +61 -0
  41. package/dist/services/keyService.js +179 -0
  42. package/dist/services/modelAccess.d.ts +9 -0
  43. package/dist/services/modelAccess.js +30 -0
  44. package/dist/services/userInfoCache.d.ts +65 -0
  45. package/dist/services/userInfoCache.js +147 -0
  46. package/dist/teamAdmin.d.ts +2 -2
  47. package/dist/teamBudgetVisibility.d.ts +2 -1
  48. package/dist/teamBudgetVisibility.js +5 -5
  49. package/dist/types.cjs.js.map +1 -1
  50. package/dist/types.d.ts +8 -195
  51. package/package.json +6 -3
  52. package/dist/permissions.d.ts +0 -11
  53. package/dist/permissions.js +0 -57
package/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ export { readTeamBudgetVisibility, redactTeamBudget, redactTeamUsage, teamBudget
11
11
  export type { TeamBudgetVisibility, TeamBudgetStatus } from './teamBudgetVisibility';
12
12
  export { KeycloakJWTVerifier, newDefaultVerifier, readBridgeConfig, resolveBridgeUserId, getOrProvisionUserFromClaims, bridgeListKeys, bridgeGenerateKey, } from './bridge';
13
13
  export type { BridgeClaims, TokenVerifier, BridgeConfig, KeycloakJWTVerifierOptions, } from './bridge';
14
- export { litellmPermissions, litellmKeyCreatePermission, litellmKeyRevokePermission, litellmKeyManagePermission, litellmAuditReadPermission, litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission, litellmTeamDeletePermission, } from './permissions';
14
+ export { litellmPermissions, litellmKeyCreatePermission, litellmKeyRevokePermission, litellmKeyManagePermission, litellmKeyResetSpendPermission, litellmKeyUnblockPermission, litellmAuditReadPermission, litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission, litellmTeamDeletePermission, } from '@acarmisc/backstage-plugin-litellm-common';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = exports.litellmPermissions = exports.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = exports.teamBudgetStatusFor = exports.redactTeamUsage = exports.redactTeamBudget = exports.readTeamBudgetVisibility = exports.validateTeamPatchInput = exports.validateTeamWriteInput = exports.assertTeamAdmin = exports.isTeamManagementEnabled = exports.readTeamAdminConfig = exports.ProvisioningError = exports.resolveUserRole = exports.applyRoleOverrides = exports.readRoleConfigs = exports.readProvisioningDefaults = exports.provisionUser = exports.getOrProvisionUser = exports.toLiteLLMUserId = exports.resolveUserProfile = exports.resolveUserId = exports.normalizeRequestTags = exports.LiteLLMClient = exports.createRouter = exports.default = exports.litellmPlugin = void 0;
17
+ exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyUnblockPermission = exports.litellmKeyResetSpendPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = exports.litellmPermissions = exports.bridgeGenerateKey = exports.bridgeListKeys = exports.getOrProvisionUserFromClaims = exports.resolveBridgeUserId = exports.readBridgeConfig = exports.newDefaultVerifier = exports.KeycloakJWTVerifier = exports.teamBudgetStatusFor = exports.redactTeamUsage = exports.redactTeamBudget = exports.readTeamBudgetVisibility = exports.validateTeamPatchInput = exports.validateTeamWriteInput = exports.assertTeamAdmin = exports.isTeamManagementEnabled = exports.readTeamAdminConfig = exports.ProvisioningError = exports.resolveUserRole = exports.applyRoleOverrides = exports.readRoleConfigs = exports.readProvisioningDefaults = exports.provisionUser = exports.getOrProvisionUser = exports.toLiteLLMUserId = exports.resolveUserProfile = exports.resolveUserId = exports.normalizeRequestTags = exports.LiteLLMClient = exports.createRouter = exports.default = exports.litellmPlugin = void 0;
18
18
  var plugin_1 = require("./plugin");
19
19
  Object.defineProperty(exports, "litellmPlugin", { enumerable: true, get: function () { return plugin_1.litellmPlugin; } });
20
20
  var plugin_2 = require("./plugin");
@@ -55,15 +55,17 @@ Object.defineProperty(exports, "resolveBridgeUserId", { enumerable: true, get: f
55
55
  Object.defineProperty(exports, "getOrProvisionUserFromClaims", { enumerable: true, get: function () { return bridge_1.getOrProvisionUserFromClaims; } });
56
56
  Object.defineProperty(exports, "bridgeListKeys", { enumerable: true, get: function () { return bridge_1.bridgeListKeys; } });
57
57
  Object.defineProperty(exports, "bridgeGenerateKey", { enumerable: true, get: function () { return bridge_1.bridgeGenerateKey; } });
58
- var permissions_1 = require("./permissions");
59
- Object.defineProperty(exports, "litellmPermissions", { enumerable: true, get: function () { return permissions_1.litellmPermissions; } });
60
- Object.defineProperty(exports, "litellmKeyCreatePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyCreatePermission; } });
61
- Object.defineProperty(exports, "litellmKeyRevokePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyRevokePermission; } });
62
- Object.defineProperty(exports, "litellmKeyManagePermission", { enumerable: true, get: function () { return permissions_1.litellmKeyManagePermission; } });
63
- Object.defineProperty(exports, "litellmAuditReadPermission", { enumerable: true, get: function () { return permissions_1.litellmAuditReadPermission; } });
64
- Object.defineProperty(exports, "litellmTeamCreatePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamCreatePermission; } });
65
- Object.defineProperty(exports, "litellmTeamManagePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamManagePermission; } });
66
- Object.defineProperty(exports, "litellmTeamMembersManagePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamMembersManagePermission; } });
67
- Object.defineProperty(exports, "litellmTeamKnowledgebaseManagePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamKnowledgebaseManagePermission; } });
68
- Object.defineProperty(exports, "litellmTeamMcpManagePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamMcpManagePermission; } });
69
- Object.defineProperty(exports, "litellmTeamDeletePermission", { enumerable: true, get: function () { return permissions_1.litellmTeamDeletePermission; } });
58
+ var backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
59
+ Object.defineProperty(exports, "litellmPermissions", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmPermissions; } });
60
+ Object.defineProperty(exports, "litellmKeyCreatePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmKeyCreatePermission; } });
61
+ Object.defineProperty(exports, "litellmKeyRevokePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmKeyRevokePermission; } });
62
+ Object.defineProperty(exports, "litellmKeyManagePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmKeyManagePermission; } });
63
+ Object.defineProperty(exports, "litellmKeyResetSpendPermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmKeyResetSpendPermission; } });
64
+ Object.defineProperty(exports, "litellmKeyUnblockPermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmKeyUnblockPermission; } });
65
+ Object.defineProperty(exports, "litellmAuditReadPermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmAuditReadPermission; } });
66
+ Object.defineProperty(exports, "litellmTeamCreatePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamCreatePermission; } });
67
+ Object.defineProperty(exports, "litellmTeamManagePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamManagePermission; } });
68
+ Object.defineProperty(exports, "litellmTeamMembersManagePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamMembersManagePermission; } });
69
+ Object.defineProperty(exports, "litellmTeamKnowledgebaseManagePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamKnowledgebaseManagePermission; } });
70
+ Object.defineProperty(exports, "litellmTeamMcpManagePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamMcpManagePermission; } });
71
+ Object.defineProperty(exports, "litellmTeamDeletePermission", { enumerable: true, get: function () { return backstage_plugin_litellm_common_1.litellmTeamDeletePermission; } });
package/dist/openapi.js CHANGED
@@ -121,6 +121,23 @@ exports.openApiSpec = {
121
121
  },
122
122
  },
123
123
  },
124
+ '/keys/prune-expired': {
125
+ post: {
126
+ tags: ['Keys'],
127
+ summary: 'Revoke all expired keys for the caller',
128
+ responses: {
129
+ '200': { description: 'Pruning result', content: { 'application/json': { schema: { type: 'object', properties: {
130
+ pruned: { type: 'number', description: 'Count of successfully deleted keys' },
131
+ failed: { type: 'number', description: 'Count of failures' },
132
+ failures: { type: 'array', items: { type: 'object', properties: {
133
+ keyId: { type: 'string' },
134
+ error: { type: 'string' },
135
+ } }, description: 'Details of failed deletions (optional)' },
136
+ } } } } },
137
+ '403': { description: 'Missing litellmKeyRevokePermission' },
138
+ },
139
+ },
140
+ },
124
141
  '/keys/{keyId}': {
125
142
  delete: {
126
143
  tags: ['Keys'],
package/dist/plugin.js CHANGED
@@ -4,7 +4,7 @@ exports.litellmPlugin = void 0;
4
4
  const backend_plugin_api_1 = require("@backstage/backend-plugin-api");
5
5
  const router_1 = require("./router");
6
6
  const bridge_1 = require("./bridge");
7
- const permissions_1 = require("./permissions");
7
+ const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
8
8
  exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
9
9
  pluginId: 'litellm',
10
10
  register(reg) {
@@ -19,7 +19,7 @@ exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
19
19
  permissionsRegistry: backend_plugin_api_1.coreServices.permissionsRegistry,
20
20
  },
21
21
  async init({ httpRouter, config, logger, auth, discovery, permissions, permissionsRegistry, }) {
22
- permissionsRegistry.addPermissions(permissions_1.litellmPermissions);
22
+ permissionsRegistry.addPermissions(backstage_plugin_litellm_common_1.litellmPermissions);
23
23
  const router = await (0, router_1.createRouter)({
24
24
  config,
25
25
  logger,
@@ -1,5 +1,5 @@
1
1
  import { Config } from '@backstage/config';
2
- import { AuthService, BackstageCredentials } from '@backstage/backend-plugin-api';
2
+ import { AuthService, BackstageCredentials, LoggerService } from '@backstage/backend-plugin-api';
3
3
  import { CatalogClient } from '@backstage/catalog-client';
4
4
  import { Request } from 'express';
5
5
  import { LiteLLMClient } from './client';
@@ -16,6 +16,17 @@ import { UserInfo, ProvisioningDefaults, RoleConfig } from './types';
16
16
  * deployments where LiteLLM users were created with plain usernames.
17
17
  */
18
18
  export declare function toLiteLLMUserId(userEntityRef: string, userIdDomain?: string): string;
19
+ /**
20
+ * Reads `litellm.provisioning.roles`: group-scoped overrides of the base
21
+ * provisioning defaults, evaluated in order (first match wins). Returns an
22
+ * empty list when none are configured. Fields omitted on a role fall back to
23
+ * the base defaults at match time (see applyRoleOverrides).
24
+ */
25
+ export declare function readRoleConfigs(config: Config): RoleConfig[];
26
+ /**
27
+ * Merges role config over defaults. Role fields override defaults only when explicitly set.
28
+ */
29
+ export declare function applyRoleOverrides(defaults: ProvisioningDefaults, role: RoleConfig): ProvisioningDefaults;
19
30
  /**
20
31
  * Reads the provisioning block from config, applying safe defaults for every
21
32
  * field so the feature works out-of-the-box without any YAML required.
@@ -30,11 +41,6 @@ export declare function toLiteLLMUserId(userEntityRef: string, userIdDomain?: st
30
41
  * rpmLimit: none — same
31
42
  * metadata: backstage source tag only
32
43
  */
33
- export declare function readRoleConfigs(config: Config): RoleConfig[];
34
- /**
35
- * Merges role config over defaults. Role fields override defaults only when explicitly set.
36
- */
37
- export declare function applyRoleOverrides(defaults: ProvisioningDefaults, role: RoleConfig): ProvisioningDefaults;
38
44
  export declare function readProvisioningDefaults(config: Config): {
39
45
  enabled: boolean;
40
46
  defaults: ProvisioningDefaults;
@@ -64,12 +70,12 @@ export interface BackstageUserProfile {
64
70
  * entity (e.g. dangerouslyAllowSignInWithoutUserInCatalog was used) — the
65
71
  * caller falls back to deriving identity from userIdDomain.
66
72
  */
67
- export declare function resolveUserProfile(userEntityRef: string, catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<BackstageUserProfile>;
73
+ export declare function resolveUserProfile(userEntityRef: string, catalogClient: CatalogClient, auth: AuthService, logger: LoggerService): Promise<BackstageUserProfile>;
68
74
  /**
69
75
  * Creates a LiteLLM user for the given Backstage identity using the configured
70
76
  * defaults. Returns the UserInfo of the newly created account.
71
77
  */
72
- export declare function provisionUser(client: LiteLLMClient, userId: string, defaults: ProvisioningDefaults, profile: BackstageUserProfile, backstageEntity: string | undefined, logger: any): Promise<UserInfo | null>;
78
+ export declare function provisionUser(client: LiteLLMClient, userId: string, defaults: ProvisioningDefaults, profile: BackstageUserProfile, backstageEntity: string | undefined, logger: LoggerService): Promise<UserInfo | null>;
73
79
  export declare class ProvisioningError extends Error {
74
80
  status: number;
75
81
  body: {
@@ -84,14 +90,14 @@ export declare class ProvisioningError extends Error {
84
90
  * When the user is missing and provisioning is enabled, attempts to create it.
85
91
  * When provisioning is disabled, throws a ProvisioningError with a clear message.
86
92
  */
87
- export declare function getOrProvisionUser(client: LiteLLMClient, tokenEntityRef: string | undefined, userId: string | undefined, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<UserInfo>;
93
+ export declare function getOrProvisionUser(client: LiteLLMClient, tokenEntityRef: string | undefined, userId: string | undefined, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: LoggerService): Promise<UserInfo>;
88
94
  /**
89
95
  * Returns true when the user is a member of the given Backstage group entity ref.
90
96
  * Used for RBAC gates (e.g. audit log visibility) without the role-config machinery.
91
97
  */
92
- export declare function isUserMemberOfGroup(userEntityRef: string, group: string, catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<boolean>;
98
+ export declare function isUserMemberOfGroup(userEntityRef: string, group: string, catalogClient: CatalogClient, auth: AuthService, logger: LoggerService): Promise<boolean>;
93
99
  /**
94
100
  * Fetches the user's Backstage group memberships and returns the first matching
95
101
  * role config (priority order), or undefined when no role matches.
96
102
  */
97
- export declare function resolveUserRole(userEntityRef: string, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: any): Promise<RoleConfig | undefined>;
103
+ export declare function resolveUserRole(userEntityRef: string, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: LoggerService): Promise<RoleConfig | undefined>;
@@ -12,6 +12,7 @@ exports.provisionUser = provisionUser;
12
12
  exports.getOrProvisionUser = getOrProvisionUser;
13
13
  exports.isUserMemberOfGroup = isUserMemberOfGroup;
14
14
  exports.resolveUserRole = resolveUserRole;
15
+ const client_1 = require("./client");
15
16
  /**
16
17
  * Converts a Backstage user entity ref to a LiteLLM user_id.
17
18
  *
@@ -35,18 +36,10 @@ function toLiteLLMUserId(userEntityRef, userIdDomain) {
35
36
  return userIdDomain ? `${name}@${userIdDomain}` : name;
36
37
  }
37
38
  /**
38
- * Reads the provisioning block from config, applying safe defaults for every
39
- * field so the feature works out-of-the-box without any YAML required.
40
- *
41
- * Safe defaults rationale:
42
- * maxBudget: $10 — prevents runaway spend on a forgotten test account
43
- * budgetDuration: 30d — monthly reset, aligns with typical billing cycles
44
- * models: [] — empty means all proxy models are allowed;
45
- * restrict here or at team level for tighter control
46
- * teams: [] — no automatic team assignment; add IDs to enrol users
47
- * tpmLimit: none — LiteLLM global / team limits still apply
48
- * rpmLimit: none — same
49
- * metadata: backstage source tag only
39
+ * Reads `litellm.provisioning.roles`: group-scoped overrides of the base
40
+ * provisioning defaults, evaluated in order (first match wins). Returns an
41
+ * empty list when none are configured. Fields omitted on a role fall back to
42
+ * the base defaults at match time (see applyRoleOverrides).
50
43
  */
51
44
  function readRoleConfigs(config) {
52
45
  const raw = config.getOptional('litellm.provisioning.roles');
@@ -79,6 +72,20 @@ function applyRoleOverrides(defaults, role) {
79
72
  metadata: { ...defaults.metadata, ...(role.metadata ?? {}) },
80
73
  };
81
74
  }
75
+ /**
76
+ * Reads the provisioning block from config, applying safe defaults for every
77
+ * field so the feature works out-of-the-box without any YAML required.
78
+ *
79
+ * Safe defaults rationale:
80
+ * maxBudget: $10 — prevents runaway spend on a forgotten test account
81
+ * budgetDuration: 30d — monthly reset, aligns with typical billing cycles
82
+ * models: [] — empty means all proxy models are allowed;
83
+ * restrict here or at team level for tighter control
84
+ * teams: [] — no automatic team assignment; add IDs to enrol users
85
+ * tpmLimit: none — LiteLLM global / team limits still apply
86
+ * rpmLimit: none — same
87
+ * metadata: backstage source tag only
88
+ */
82
89
  function readProvisioningDefaults(config) {
83
90
  const enabled = config.getOptionalBoolean('litellm.provisioning.enabled') ?? false;
84
91
  const defaults = {
@@ -152,7 +159,8 @@ async function resolveUserProfile(userEntityRef, catalogClient, auth, logger) {
152
159
  };
153
160
  }
154
161
  catch (err) {
155
- logger.warn(`Could not fetch catalog profile for ${userEntityRef}: ${err.message}`);
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ logger.warn(`Could not fetch catalog profile for ${userEntityRef}: ${message}`);
156
164
  return {};
157
165
  }
158
166
  }
@@ -184,7 +192,7 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
184
192
  }),
185
193
  },
186
194
  };
187
- logger.info(`Provisioning new LiteLLM user for Backstage identity: ${userId}${profile.email ? ` (email=${profile.email})` : ''}`);
195
+ logger.debug(`Provisioning new LiteLLM user for Backstage identity: ${userId}${profile.email ? ` (email=${profile.email})` : ''}`);
188
196
  try {
189
197
  await client.createUser(payload);
190
198
  // Defensive /user/update: LiteLLM's /user/new upsert path has been
@@ -202,14 +210,16 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
202
210
  });
203
211
  }
204
212
  catch (updateErr) {
205
- logger.warn(`Defensive /user/update after provisioning ${userId} failed: ${updateErr.message}`);
213
+ const message = updateErr instanceof Error ? updateErr.message : String(updateErr);
214
+ logger.warn(`Defensive /user/update after provisioning ${userId} failed: ${message}`);
206
215
  }
207
216
  }
208
217
  // Fetch the freshly-created user record to return consistent UserInfo shape
209
218
  return await client.getUserInfo(userId);
210
219
  }
211
220
  catch (err) {
212
- logger.error(`Failed to provision LiteLLM user ${userId}: ${err.message}`);
221
+ const message = err instanceof Error ? err.message : String(err);
222
+ logger.error(`Failed to provision LiteLLM user ${userId}: ${message}`);
213
223
  throw err;
214
224
  }
215
225
  }
@@ -299,7 +309,8 @@ async function getOrProvisionUser(client, tokenEntityRef, userId, provisioningEn
299
309
  // but keep the recovery path: if /user/new still 409s (e.g.
300
310
  // multi-replica deploys where the lock is per-process), treat
301
311
  // it as "user exists" and re-fetch.
302
- if (err.status === 409 || /already exists/i.test(err.message ?? '')) {
312
+ if ((err instanceof client_1.LiteLLMUpstreamError && err.status === 409) ||
313
+ (err instanceof Error && /already exists/i.test(err.message ?? ''))) {
303
314
  logger.info(`LiteLLM user ${userId} already exists during provisioning — re-fetching`);
304
315
  const refetched = await client.getUserInfo(userId);
305
316
  if (refetched) {
@@ -315,11 +326,12 @@ async function getOrProvisionUser(client, tokenEntityRef, userId, provisioningEn
315
326
  // browser, otherwise Backstage's fetch middleware treats the
316
327
  // user's Backstage session as expired and forces a re-login.
317
328
  // Only safe client-semantic codes pass through.
318
- const upstreamStatus = err.status;
319
- const passThrough = [400, 404, 409, 422].includes(upstreamStatus)
329
+ const upstreamStatus = err instanceof client_1.LiteLLMUpstreamError ? err.status : undefined;
330
+ const passThrough = (upstreamStatus && [400, 404, 409, 422].includes(upstreamStatus))
320
331
  ? upstreamStatus
321
332
  : 502;
322
- throw new ProvisioningError('LiteLLM auto-provisioning failed', `LiteLLM upstream ${upstreamStatus ?? 'error'}: ${sanitizeUpstreamMessage(err.message)}`, true, passThrough);
333
+ const errMessage = err instanceof Error ? err.message : String(err);
334
+ throw new ProvisioningError('LiteLLM auto-provisioning failed', `LiteLLM upstream ${upstreamStatus ?? 'error'}: ${sanitizeUpstreamMessage(errMessage)}`, true, passThrough);
323
335
  }
324
336
  })();
325
337
  provisioningInFlight.set(userId, provisionPromise);
@@ -347,7 +359,8 @@ async function isUserMemberOfGroup(userEntityRef, group, catalogClient, auth, lo
347
359
  return groups.includes(group);
348
360
  }
349
361
  catch (err) {
350
- logger.warn(`Could not check group membership for ${userEntityRef}: ${err.message}`);
362
+ const message = err instanceof Error ? err.message : String(err);
363
+ logger.warn(`Could not check group membership for ${userEntityRef}: ${message}`);
351
364
  return false;
352
365
  }
353
366
  }
@@ -370,7 +383,8 @@ async function resolveUserRole(userEntityRef, roleConfigs, catalogClient, auth,
370
383
  return roleConfigs.find(rc => groups.includes(rc.group));
371
384
  }
372
385
  catch (err) {
373
- logger.warn(`Could not resolve Backstage groups for ${userEntityRef}: ${err.message}`);
386
+ const message = err instanceof Error ? err.message : String(err);
387
+ logger.warn(`Could not resolve Backstage groups for ${userEntityRef}: ${message}`);
374
388
  return undefined;
375
389
  }
376
390
  }
package/dist/router.d.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { Router } from 'express';
2
- import { Config } from '@backstage/config';
3
- import { AuthService, DiscoveryService, PermissionsService } from '@backstage/backend-plugin-api';
2
+ import { AuthService, DiscoveryService, PermissionsService, LoggerService, RootConfigService } from '@backstage/backend-plugin-api';
4
3
  import { CatalogClient } from '@backstage/catalog-client';
5
4
  import { LiteLLMClient } from './client';
6
5
  import { ProvisioningError } from './provisioning';
7
6
  import { TokenVerifier } from './bridge';
8
7
  export { ProvisioningError };
9
8
  export interface RouterOptions {
10
- config: Config;
11
- logger: any;
9
+ config: RootConfigService;
10
+ logger: LoggerService;
12
11
  auth: AuthService;
13
12
  discovery: DiscoveryService;
14
13
  permissions: PermissionsService;