@acarmisc/backstage-plugin-litellm-backend 0.30.1 → 0.32.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 +18 -2
- package/dist/index.cjs.js +151 -123
- package/dist/index.cjs.js.map +4 -4
- package/dist/openapi.js +1 -0
- package/dist/provisioning.d.ts +5 -0
- package/dist/provisioning.js +15 -4
- package/dist/router.js +3 -1
- package/dist/routes/config.js +5 -1
- package/dist/services/keyService.js +13 -3
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +12 -2
- package/package.json +3 -3
package/dist/openapi.js
CHANGED
|
@@ -63,6 +63,7 @@ exports.openApiSpec = {
|
|
|
63
63
|
content: { 'application/json': { schema: { type: 'object', properties: {
|
|
64
64
|
baseUrl: { type: 'string' },
|
|
65
65
|
keyGeneration: { type: 'object', properties: {
|
|
66
|
+
allowedDurations: { type: 'array', items: { type: 'string' }, description: 'Durations a new key may have (litellm.keys.allowedDurations); "never" means no expiry' },
|
|
66
67
|
allowUnlimitedBudget: { type: 'boolean' },
|
|
67
68
|
teamRequired: { type: 'boolean' },
|
|
68
69
|
} },
|
package/dist/provisioning.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ export declare function applyRoleOverrides(defaults: ProvisioningDefaults, role:
|
|
|
40
40
|
* tpmLimit: none — LiteLLM global / team limits still apply
|
|
41
41
|
* rpmLimit: none — same
|
|
42
42
|
* metadata: backstage source tag only
|
|
43
|
+
*
|
|
44
|
+
* With `useGatewayBudgetDefaults: true` the maxBudget / budgetDuration
|
|
45
|
+
* fallbacks are dropped: an unset field stays undefined, provisionUser leaves
|
|
46
|
+
* it out of /user/new, and LiteLLM's max_internal_user_budget /
|
|
47
|
+
* internal_user_budget_duration apply instead (internal_user role only).
|
|
43
48
|
*/
|
|
44
49
|
export declare function readProvisioningDefaults(config: Config): {
|
|
45
50
|
enabled: boolean;
|
package/dist/provisioning.js
CHANGED
|
@@ -88,12 +88,20 @@ function applyRoleOverrides(defaults, role) {
|
|
|
88
88
|
* tpmLimit: none — LiteLLM global / team limits still apply
|
|
89
89
|
* rpmLimit: none — same
|
|
90
90
|
* metadata: backstage source tag only
|
|
91
|
+
*
|
|
92
|
+
* With `useGatewayBudgetDefaults: true` the maxBudget / budgetDuration
|
|
93
|
+
* fallbacks are dropped: an unset field stays undefined, provisionUser leaves
|
|
94
|
+
* it out of /user/new, and LiteLLM's max_internal_user_budget /
|
|
95
|
+
* internal_user_budget_duration apply instead (internal_user role only).
|
|
91
96
|
*/
|
|
92
97
|
function readProvisioningDefaults(config) {
|
|
93
98
|
const enabled = config.getOptionalBoolean('litellm.provisioning.enabled') ?? false;
|
|
99
|
+
const useGatewayBudgetDefaults = config.getOptionalBoolean('litellm.provisioning.defaults.useGatewayBudgetDefaults') ?? false;
|
|
100
|
+
const maxBudget = config.getOptionalNumber('litellm.provisioning.defaults.maxBudget');
|
|
101
|
+
const budgetDuration = config.getOptionalString('litellm.provisioning.defaults.budgetDuration');
|
|
94
102
|
const defaults = {
|
|
95
|
-
maxBudget:
|
|
96
|
-
budgetDuration:
|
|
103
|
+
maxBudget: maxBudget ?? (useGatewayBudgetDefaults ? undefined : 10),
|
|
104
|
+
budgetDuration: budgetDuration ?? (useGatewayBudgetDefaults ? undefined : '30d'),
|
|
97
105
|
models: config.getOptionalStringArray('litellm.provisioning.defaults.models') ??
|
|
98
106
|
[],
|
|
99
107
|
teams: config.getOptionalStringArray('litellm.provisioning.defaults.teams') ??
|
|
@@ -180,8 +188,11 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
|
|
|
180
188
|
user_id: userId,
|
|
181
189
|
...(profile.email && { user_email: profile.email }),
|
|
182
190
|
...(profile.displayName && { user_alias: profile.displayName }),
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
// Undefined = left out, so LiteLLM's internal-user defaults apply.
|
|
192
|
+
...(defaults.maxBudget !== undefined && { max_budget: defaults.maxBudget }),
|
|
193
|
+
...(defaults.budgetDuration !== undefined && {
|
|
194
|
+
budget_duration: defaults.budgetDuration,
|
|
195
|
+
}),
|
|
185
196
|
models: defaults.models,
|
|
186
197
|
teams: defaults.teams,
|
|
187
198
|
...(defaults.tpmLimit !== undefined && { tpm_limit: defaults.tpmLimit }),
|
package/dist/router.js
CHANGED
|
@@ -112,7 +112,9 @@ async function createRouter(options) {
|
|
|
112
112
|
const langfuseCfg = (0, langfuse_1.readLangfuseConfig)(config);
|
|
113
113
|
const catalogClient = options.catalogClient ?? new catalog_client_1.CatalogClient({ discoveryApi: discovery });
|
|
114
114
|
if (provisioningEnabled) {
|
|
115
|
-
logger.info(`LiteLLM auto-provisioning enabled — defaults: budget
|
|
115
|
+
logger.info(`LiteLLM auto-provisioning enabled — defaults: budget=${provisioningDefaults.maxBudget === undefined
|
|
116
|
+
? 'gateway default'
|
|
117
|
+
: `$${provisioningDefaults.maxBudget}`}/${provisioningDefaults.budgetDuration ?? 'gateway default'}, models=${provisioningDefaults.models.length
|
|
116
118
|
? provisioningDefaults.models.join(',')
|
|
117
119
|
: 'all'}, teams=[${provisioningDefaults.teams.join(',')}]`);
|
|
118
120
|
}
|
package/dist/routes/config.js
CHANGED
|
@@ -18,7 +18,11 @@ function registerConfigRoutes(router, ctx) {
|
|
|
18
18
|
res.json({
|
|
19
19
|
baseUrl: ctx.publicBaseUrl,
|
|
20
20
|
supportContact: ctx.supportContact,
|
|
21
|
-
keyGeneration: {
|
|
21
|
+
keyGeneration: {
|
|
22
|
+
allowUnlimitedBudget: ctx.allowUnlimitedBudget,
|
|
23
|
+
teamRequired: ctx.teamRequired,
|
|
24
|
+
allowedDurations: ctx.keyValidationConfig.allowedDurations,
|
|
25
|
+
},
|
|
22
26
|
keyActions: { allowOwnerResetSpend: ctx.allowOwnerResetSpend },
|
|
23
27
|
opencode: { enabled: ctx.opencodeCfg.enabled },
|
|
24
28
|
teamManagement: {
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.KeyServiceError = void 0;
|
|
11
11
|
exports.createKeyForUser = createKeyForUser;
|
|
12
12
|
const provisioning_1 = require("../provisioning");
|
|
13
|
+
const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
13
14
|
const modelAccess_1 = require("./modelAccess");
|
|
14
15
|
const mcpAccess_1 = require("./mcpAccess");
|
|
15
16
|
/** Error thrown during key creation — maps to HTTP responses. */
|
|
@@ -145,10 +146,19 @@ async function createKeyForUser(user, input, ctx) {
|
|
|
145
146
|
upstreamRequest.models = input.models;
|
|
146
147
|
}
|
|
147
148
|
// Never mint a non-expiring key by omission: default to 30d, or the first
|
|
148
|
-
// allowed duration when 30d isn't offered.
|
|
149
|
+
// allowed duration other than "never" when 30d isn't offered.
|
|
149
150
|
const durations = ctx.keyValidationConfig.allowedDurations;
|
|
150
|
-
const defaultDuration = durations.length === 0 || durations.includes('30d')
|
|
151
|
-
|
|
151
|
+
const defaultDuration = durations.length === 0 || durations.includes('30d')
|
|
152
|
+
? '30d'
|
|
153
|
+
: durations.find(d => d !== backstage_plugin_litellm_common_1.NEVER_KEY_DURATION);
|
|
154
|
+
const duration = input.duration ?? defaultDuration;
|
|
155
|
+
if (duration === undefined) {
|
|
156
|
+
throw new KeyServiceError(400, { error: 'duration is required' });
|
|
157
|
+
}
|
|
158
|
+
// LiteLLM treats a missing duration as "never expires".
|
|
159
|
+
if (duration !== backstage_plugin_litellm_common_1.NEVER_KEY_DURATION) {
|
|
160
|
+
upstreamRequest.duration = duration;
|
|
161
|
+
}
|
|
152
162
|
if (input.max_budget !== undefined) {
|
|
153
163
|
// max_budget from input can be null (unlimited) or a number
|
|
154
164
|
upstreamRequest.max_budget = input.max_budget ?? undefined;
|
package/dist/types.cjs.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/types.ts"],
|
|
4
|
-
"sourcesContent": ["// Re-export common types for backward compatibility\nexport {\n type UserInfo,\n type TeamMember,\n type TeamObjectPermission,\n type TeamInfo,\n type CreateTeamResponse,\n type VirtualKey,\n type ModelInfo,\n type UsageModelBreakdown,\n type UsageKeyBreakdown,\n type UsageDailyPoint,\n type UsageDailyModelPoint,\n type UsageMetrics,\n type TeamMemberUsage,\n type TeamMemberUsageRow,\n type TeamKeyUsage,\n type TeamKeyUsageRow,\n type GenerateKeyRequest,\n type UpdateKeyRequest,\n type GenerateKeyResponse,\n type AuditLogEntry,\n type PaginatedAuditLogs,\n type SpendLogEntry,\n type SpendLogsParams,\n type PaginatedSpendLogs,\n type QualityByModel,\n type QualityScore,\n type QualitySummary,\n type QualityResponse,\n} from '@acarmisc/backstage-plugin-litellm-common';\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: {\n vector_stores?: string[];\n mcp_servers?: string[];\n mcp_access_groups?: string[];\n };\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_id: string;\n blocked?: boolean;\n}\n\n/**\n * Shape of a single entry inside LiteLLM's `/user/info` `keys` array.\n * Differs from VirtualKey: uses `expires` (not `expires_at`), exposes\n * both a hashed `token` and a masked `key_name`, and fields are nullable\n * rather than optional.\n */\nexport interface LiteLLMUserKey {\n token: string;\n key_name?: string;\n key_alias?: string | null;\n spend?: number;\n expires?: string | null;\n models?: string[];\n tpm_limit?: number | null;\n rpm_limit?: number | null;\n max_budget?: number | null;\n budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n /** Arbitrary metadata stored on the key record. */\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface DeleteKeyRequest {\n keys: string[];\n}\n\nexport interface LiteLLMConfig {\n baseUrl: string;\n masterKey: string;\n}\n\nexport interface ProvisioningDefaults {\n maxBudget
|
|
4
|
+
"sourcesContent": ["// Re-export common types for backward compatibility\nexport {\n type UserInfo,\n type TeamMember,\n type TeamObjectPermission,\n type TeamInfo,\n type CreateTeamResponse,\n type VirtualKey,\n type ModelInfo,\n type UsageModelBreakdown,\n type UsageKeyBreakdown,\n type UsageDailyPoint,\n type UsageDailyModelPoint,\n type UsageMetrics,\n type TeamMemberUsage,\n type TeamMemberUsageRow,\n type TeamKeyUsage,\n type TeamKeyUsageRow,\n type GenerateKeyRequest,\n type UpdateKeyRequest,\n type GenerateKeyResponse,\n type AuditLogEntry,\n type PaginatedAuditLogs,\n type SpendLogEntry,\n type SpendLogsParams,\n type PaginatedSpendLogs,\n type QualityByModel,\n type QualityScore,\n type QualitySummary,\n type QualityResponse,\n} from '@acarmisc/backstage-plugin-litellm-common';\n\nexport interface CreateTeamRequest {\n team_alias: string;\n models?: string[];\n max_budget?: number;\n budget_duration?: string;\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, unknown>;\n object_permission?: {\n vector_stores?: string[];\n mcp_servers?: string[];\n mcp_access_groups?: string[];\n };\n}\n\nexport interface UpdateTeamRequest extends Partial<CreateTeamRequest> {\n team_id: string;\n blocked?: boolean;\n}\n\n/**\n * Shape of a single entry inside LiteLLM's `/user/info` `keys` array.\n * Differs from VirtualKey: uses `expires` (not `expires_at`), exposes\n * both a hashed `token` and a masked `key_name`, and fields are nullable\n * rather than optional.\n */\nexport interface LiteLLMUserKey {\n token: string;\n key_name?: string;\n key_alias?: string | null;\n spend?: number;\n expires?: string | null;\n models?: string[];\n tpm_limit?: number | null;\n rpm_limit?: number | null;\n max_budget?: number | null;\n budget_duration?: string | null;\n user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n /** Arbitrary metadata stored on the key record. */\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface DeleteKeyRequest {\n keys: string[];\n}\n\nexport interface LiteLLMConfig {\n baseUrl: string;\n masterKey: string;\n}\n\nexport interface ProvisioningDefaults {\n /**\n * max_budget sent on /user/new. Undefined (only possible with\n * litellm.provisioning.defaults.useGatewayBudgetDefaults) leaves it out, so\n * LiteLLM's max_internal_user_budget applies.\n */\n maxBudget?: number;\n /**\n * budget_duration sent on /user/new. Undefined (only possible with\n * litellm.provisioning.defaults.useGatewayBudgetDefaults) leaves it out, so\n * LiteLLM's internal_user_budget_duration applies.\n */\n budgetDuration?: string;\n models: string[];\n teams: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n /**\n * LiteLLM user role applied on /user/new. Defaults to \"internal_user\"\n * which grants self-service Create/Delete/View on the user's own keys.\n * Valid values: proxy_admin, proxy_admin_viewer, internal_user,\n * internal_user_viewer, team.\n */\n userRole?: string;\n metadata: Record<string, string>;\n}\n\nexport interface RoleConfig {\n group: string;\n maxBudget?: number;\n budgetDuration?: string;\n models?: string[];\n teams?: string[];\n tpmLimit?: number;\n rpmLimit?: number;\n userRole?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface CreateUserRequest {\n user_id: string;\n user_email?: string;\n user_alias?: string;\n user_role?: string;\n max_budget?: number;\n budget_duration?: string;\n models?: string[];\n teams?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n metadata?: Record<string, string>;\n auto_create_key?: boolean;\n}\n\nexport interface CreateUserResponse {\n user_id: string;\n user_email?: string;\n max_budget?: number;\n models?: string[];\n teams?: string[];\n}\n\n\nexport interface AuditLogsParams {\n page?: number;\n page_size?: number;\n start_date?: string;\n end_date?: string;\n action?: string;\n table_name?: string;\n changed_by?: string;\n sort_by?: string;\n sort_order?: 'asc' | 'desc';\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -49,8 +49,18 @@ export interface LiteLLMConfig {
|
|
|
49
49
|
masterKey: string;
|
|
50
50
|
}
|
|
51
51
|
export interface ProvisioningDefaults {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* max_budget sent on /user/new. Undefined (only possible with
|
|
54
|
+
* litellm.provisioning.defaults.useGatewayBudgetDefaults) leaves it out, so
|
|
55
|
+
* LiteLLM's max_internal_user_budget applies.
|
|
56
|
+
*/
|
|
57
|
+
maxBudget?: number;
|
|
58
|
+
/**
|
|
59
|
+
* budget_duration sent on /user/new. Undefined (only possible with
|
|
60
|
+
* litellm.provisioning.defaults.useGatewayBudgetDefaults) leaves it out, so
|
|
61
|
+
* LiteLLM's internal_user_budget_duration applies.
|
|
62
|
+
*/
|
|
63
|
+
budgetDuration?: string;
|
|
54
64
|
models: string[];
|
|
55
65
|
teams: string[];
|
|
56
66
|
tpmLimit?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"prepack": "npm run build"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@acarmisc/backstage-plugin-litellm-common": "0.
|
|
39
|
-
"@backstage/backend-plugin-api": "^1.
|
|
38
|
+
"@acarmisc/backstage-plugin-litellm-common": "0.7.0",
|
|
39
|
+
"@backstage/backend-plugin-api": "^1.11.0",
|
|
40
40
|
"@backstage/catalog-client": "^1.9.0",
|
|
41
41
|
"@backstage/config": "^1.3.0",
|
|
42
42
|
"@backstage/errors": "^1.3.0",
|