@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.
- package/config.d.ts +115 -22
- package/dist/bridge.d.ts +23 -16
- package/dist/bridge.js +51 -11
- package/dist/client.d.ts +7 -2
- package/dist/client.js +36 -9
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +205 -0
- package/dist/http/respondTeam.d.ts +15 -0
- package/dist/http/respondTeam.js +27 -0
- package/dist/http/teamFetch.d.ts +14 -0
- package/dist/http/teamFetch.js +44 -0
- package/dist/index.cjs.js +4249 -3375
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -13
- package/dist/openapi.js +17 -0
- package/dist/plugin.js +2 -2
- package/dist/provisioning.d.ts +17 -11
- package/dist/provisioning.js +36 -22
- package/dist/router.d.ts +3 -4
- package/dist/router.js +188 -1349
- package/dist/routes/bridge.d.ts +8 -0
- package/dist/routes/bridge.js +130 -0
- package/dist/routes/config.d.ts +7 -0
- package/dist/routes/config.js +89 -0
- package/dist/routes/context.d.ts +55 -0
- package/dist/routes/context.js +2 -0
- package/dist/routes/keys.d.ts +8 -0
- package/dist/routes/keys.js +471 -0
- package/dist/routes/middleware/withUser.d.ts +18 -0
- package/dist/routes/middleware/withUser.js +41 -0
- package/dist/routes/objectPermissions.d.ts +3 -0
- package/dist/routes/objectPermissions.js +196 -0
- package/dist/routes/opencode.d.ts +3 -0
- package/dist/routes/opencode.js +239 -0
- package/dist/routes/teamUsage.d.ts +3 -0
- package/dist/routes/teamUsage.js +73 -0
- package/dist/routes/teams.d.ts +3 -0
- package/dist/routes/teams.js +367 -0
- package/dist/services/keyService.d.ts +61 -0
- package/dist/services/keyService.js +179 -0
- package/dist/services/modelAccess.d.ts +9 -0
- package/dist/services/modelAccess.js +30 -0
- package/dist/services/userInfoCache.d.ts +65 -0
- package/dist/services/userInfoCache.js +147 -0
- package/dist/teamAdmin.d.ts +2 -2
- package/dist/teamBudgetVisibility.d.ts +2 -1
- package/dist/teamBudgetVisibility.js +5 -5
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +8 -195
- package/package.json +6 -3
- package/dist/permissions.d.ts +0 -11
- 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 '
|
|
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
|
|
59
|
-
Object.defineProperty(exports, "litellmPermissions", { enumerable: true, get: function () { return
|
|
60
|
-
Object.defineProperty(exports, "litellmKeyCreatePermission", { enumerable: true, get: function () { return
|
|
61
|
-
Object.defineProperty(exports, "litellmKeyRevokePermission", { enumerable: true, get: function () { return
|
|
62
|
-
Object.defineProperty(exports, "litellmKeyManagePermission", { enumerable: true, get: function () { return
|
|
63
|
-
Object.defineProperty(exports, "
|
|
64
|
-
Object.defineProperty(exports, "
|
|
65
|
-
Object.defineProperty(exports, "
|
|
66
|
-
Object.defineProperty(exports, "
|
|
67
|
-
Object.defineProperty(exports, "
|
|
68
|
-
Object.defineProperty(exports, "
|
|
69
|
-
Object.defineProperty(exports, "
|
|
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
|
|
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(
|
|
22
|
+
permissionsRegistry.addPermissions(backstage_plugin_litellm_common_1.litellmPermissions);
|
|
23
23
|
const router = await (0, router_1.createRouter)({
|
|
24
24
|
config,
|
|
25
25
|
logger,
|
package/dist/provisioning.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
103
|
+
export declare function resolveUserRole(userEntityRef: string, roleConfigs: RoleConfig[], catalogClient: CatalogClient, auth: AuthService, logger: LoggerService): Promise<RoleConfig | undefined>;
|
package/dist/provisioning.js
CHANGED
|
@@ -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
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
11
|
-
logger:
|
|
9
|
+
config: RootConfigService;
|
|
10
|
+
logger: LoggerService;
|
|
12
11
|
auth: AuthService;
|
|
13
12
|
discovery: DiscoveryService;
|
|
14
13
|
permissions: PermissionsService;
|