@acarmisc/backstage-plugin-litellm-backend 0.15.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 +35 -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 +4248 -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 -197
- package/package.json +6 -3
- package/dist/permissions.d.ts +0 -11
- package/dist/permissions.js +0 -57
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { UserInfo } from '../types';
|
|
2
|
+
import { LiteLLMClient } from '../client';
|
|
3
|
+
/**
|
|
4
|
+
* Clock abstraction for testing. Allows tests to fake time without real delays.
|
|
5
|
+
*/
|
|
6
|
+
export interface Clock {
|
|
7
|
+
now(): number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Simple LRU cache with per-entry TTL.
|
|
11
|
+
* - Max ~500 entries; LRU eviction when full.
|
|
12
|
+
* - Each entry has its own expiry time.
|
|
13
|
+
* - Tracks pending promises for single-flight deduplication.
|
|
14
|
+
*/
|
|
15
|
+
export declare class UserInfoCache {
|
|
16
|
+
private static readonly MAX_ENTRIES;
|
|
17
|
+
private cache;
|
|
18
|
+
private pending;
|
|
19
|
+
private clock;
|
|
20
|
+
private ttlMs;
|
|
21
|
+
constructor(ttlMs?: number, clock?: Clock);
|
|
22
|
+
/**
|
|
23
|
+
* Get a cached value, or null if not found or expired.
|
|
24
|
+
*/
|
|
25
|
+
get(userId: string): UserInfo | null;
|
|
26
|
+
/**
|
|
27
|
+
* Set a cache entry (does not expire).
|
|
28
|
+
*/
|
|
29
|
+
set(userId: string, value: UserInfo): void;
|
|
30
|
+
/**
|
|
31
|
+
* Invalidate a single user's cache entry.
|
|
32
|
+
*/
|
|
33
|
+
invalidate(userId: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Clear all cache entries and pending promises.
|
|
36
|
+
*/
|
|
37
|
+
clear(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Internal: track a pending promise for single-flight deduplication.
|
|
40
|
+
* Returns the promise, which is stored and shared across concurrent requests.
|
|
41
|
+
*/
|
|
42
|
+
private getPending;
|
|
43
|
+
/**
|
|
44
|
+
* Load a value through the cache with single-flight deduplication.
|
|
45
|
+
* If the value is already cached and not expired, return it.
|
|
46
|
+
* If another request is loading the same userId, wait for that promise.
|
|
47
|
+
* Otherwise, call the loader and cache the result (unless null or error).
|
|
48
|
+
*
|
|
49
|
+
* Errors and null results are NOT cached.
|
|
50
|
+
*/
|
|
51
|
+
load(userId: string, loader: () => Promise<UserInfo | null>): Promise<UserInfo | null>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wraps a LiteLLMClient to inject getUserInfo caching and automatic cache
|
|
55
|
+
* invalidation on mutations.
|
|
56
|
+
*
|
|
57
|
+
* Returns a Proxy that:
|
|
58
|
+
* - Routes getUserInfo(userId) through the cache
|
|
59
|
+
* - Invalidates the cache (clear all) after any method matching the mutation pattern
|
|
60
|
+
* - Delegates all other methods to the wrapped client unchanged
|
|
61
|
+
*/
|
|
62
|
+
export declare function withUserInfoCache(client: LiteLLMClient, options?: {
|
|
63
|
+
ttlMs?: number;
|
|
64
|
+
clock?: Clock;
|
|
65
|
+
}): LiteLLMClient;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserInfoCache = void 0;
|
|
4
|
+
exports.withUserInfoCache = withUserInfoCache;
|
|
5
|
+
/**
|
|
6
|
+
* Default system clock using Date.now().
|
|
7
|
+
*/
|
|
8
|
+
const SystemClock = {
|
|
9
|
+
now: () => Date.now(),
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Simple LRU cache with per-entry TTL.
|
|
13
|
+
* - Max ~500 entries; LRU eviction when full.
|
|
14
|
+
* - Each entry has its own expiry time.
|
|
15
|
+
* - Tracks pending promises for single-flight deduplication.
|
|
16
|
+
*/
|
|
17
|
+
class UserInfoCache {
|
|
18
|
+
constructor(ttlMs = 10000, clock = SystemClock) {
|
|
19
|
+
this.cache = new Map();
|
|
20
|
+
this.pending = new Map();
|
|
21
|
+
this.ttlMs = ttlMs;
|
|
22
|
+
this.clock = clock;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get a cached value, or null if not found or expired.
|
|
26
|
+
*/
|
|
27
|
+
get(userId) {
|
|
28
|
+
const entry = this.cache.get(userId);
|
|
29
|
+
if (!entry)
|
|
30
|
+
return null;
|
|
31
|
+
if (this.clock.now() >= entry.expiresAt) {
|
|
32
|
+
// Expired; remove and return null
|
|
33
|
+
this.cache.delete(userId);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return entry.value;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Set a cache entry (does not expire).
|
|
40
|
+
*/
|
|
41
|
+
set(userId, value) {
|
|
42
|
+
// Evict LRU entry if at capacity
|
|
43
|
+
if (this.cache.size >= UserInfoCache.MAX_ENTRIES && !this.cache.has(userId)) {
|
|
44
|
+
const firstKey = this.cache.keys().next().value;
|
|
45
|
+
if (firstKey)
|
|
46
|
+
this.cache.delete(firstKey);
|
|
47
|
+
}
|
|
48
|
+
const expiresAt = this.clock.now() + this.ttlMs;
|
|
49
|
+
this.cache.set(userId, { value, expiresAt });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Invalidate a single user's cache entry.
|
|
53
|
+
*/
|
|
54
|
+
invalidate(userId) {
|
|
55
|
+
this.cache.delete(userId);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Clear all cache entries and pending promises.
|
|
59
|
+
*/
|
|
60
|
+
clear() {
|
|
61
|
+
this.cache.clear();
|
|
62
|
+
this.pending.clear();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Internal: track a pending promise for single-flight deduplication.
|
|
66
|
+
* Returns the promise, which is stored and shared across concurrent requests.
|
|
67
|
+
*/
|
|
68
|
+
getPending(userId, loader) {
|
|
69
|
+
const existing = this.pending.get(userId);
|
|
70
|
+
if (existing)
|
|
71
|
+
return existing;
|
|
72
|
+
const promise = loader().finally(() => {
|
|
73
|
+
// Clean up pending once resolved or rejected
|
|
74
|
+
this.pending.delete(userId);
|
|
75
|
+
});
|
|
76
|
+
this.pending.set(userId, promise);
|
|
77
|
+
return promise;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Load a value through the cache with single-flight deduplication.
|
|
81
|
+
* If the value is already cached and not expired, return it.
|
|
82
|
+
* If another request is loading the same userId, wait for that promise.
|
|
83
|
+
* Otherwise, call the loader and cache the result (unless null or error).
|
|
84
|
+
*
|
|
85
|
+
* Errors and null results are NOT cached.
|
|
86
|
+
*/
|
|
87
|
+
async load(userId, loader) {
|
|
88
|
+
// Check cache first
|
|
89
|
+
const cached = this.get(userId);
|
|
90
|
+
if (cached)
|
|
91
|
+
return cached;
|
|
92
|
+
// Use single-flight for concurrent requests
|
|
93
|
+
const result = await this.getPending(userId, loader);
|
|
94
|
+
// Cache only non-null success results
|
|
95
|
+
if (result) {
|
|
96
|
+
this.set(userId, result);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.UserInfoCache = UserInfoCache;
|
|
102
|
+
UserInfoCache.MAX_ENTRIES = 500;
|
|
103
|
+
/**
|
|
104
|
+
* Wraps a LiteLLMClient to inject getUserInfo caching and automatic cache
|
|
105
|
+
* invalidation on mutations.
|
|
106
|
+
*
|
|
107
|
+
* Returns a Proxy that:
|
|
108
|
+
* - Routes getUserInfo(userId) through the cache
|
|
109
|
+
* - Invalidates the cache (clear all) after any method matching the mutation pattern
|
|
110
|
+
* - Delegates all other methods to the wrapped client unchanged
|
|
111
|
+
*/
|
|
112
|
+
function withUserInfoCache(client, options) {
|
|
113
|
+
const cache = new UserInfoCache(options?.ttlMs ?? 10000, options?.clock);
|
|
114
|
+
// Methods whose names match this pattern indicate a mutation that should
|
|
115
|
+
// invalidate the cache to ensure consistency.
|
|
116
|
+
const isMutatingMethod = /^(generate|update|delete|block|unblock|reset|create|add|remove|set|regenerate|provision|teamMember)/i;
|
|
117
|
+
return new Proxy(client, {
|
|
118
|
+
get(target, prop) {
|
|
119
|
+
// Wrap getUserInfo through the cache
|
|
120
|
+
if (prop === 'getUserInfo') {
|
|
121
|
+
return async (userId) => {
|
|
122
|
+
if (!userId) {
|
|
123
|
+
// No userId => call upstream directly (shouldn't happen in normal flow)
|
|
124
|
+
return target.getUserInfo(userId);
|
|
125
|
+
}
|
|
126
|
+
return cache.load(userId, () => target.getUserInfo(userId));
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Get the method from the target
|
|
130
|
+
const method = target[prop];
|
|
131
|
+
if (typeof method !== 'function') {
|
|
132
|
+
return method;
|
|
133
|
+
}
|
|
134
|
+
// If the method name matches the mutation pattern, wrap it to invalidate cache
|
|
135
|
+
if (isMutatingMethod.test(String(prop))) {
|
|
136
|
+
return async (...args) => {
|
|
137
|
+
const result = await method.apply(target, args);
|
|
138
|
+
// Invalidate entire cache after any mutation (simple and safe)
|
|
139
|
+
cache.clear();
|
|
140
|
+
return result;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// For other methods, bind and return as-is
|
|
144
|
+
return method.bind(target);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
package/dist/teamAdmin.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Request } from 'express';
|
|
2
2
|
import { Config } from '@backstage/config';
|
|
3
|
-
import { AuthService, PermissionsService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { AuthService, PermissionsService, LoggerService } from '@backstage/backend-plugin-api';
|
|
4
4
|
import { BasicPermission } from '@backstage/plugin-permission-common';
|
|
5
5
|
import { CatalogClient } from '@backstage/catalog-client';
|
|
6
6
|
/**
|
|
@@ -108,7 +108,7 @@ export declare function assertTeamAdmin(opts: {
|
|
|
108
108
|
catalogClient: CatalogClient;
|
|
109
109
|
teamAdminGroup: string;
|
|
110
110
|
permission: BasicPermission;
|
|
111
|
-
logger:
|
|
111
|
+
logger: LoggerService;
|
|
112
112
|
}): Promise<TeamAdminCheck>;
|
|
113
113
|
/**
|
|
114
114
|
* Reads the team-admin governance block from config, applying fail-closed
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Config } from '@backstage/config';
|
|
2
2
|
import { TeamInfo, UsageMetrics } from './types';
|
|
3
|
+
import { BudgetStatus } from '@acarmisc/backstage-plugin-litellm-common';
|
|
3
4
|
/**
|
|
4
5
|
* Independent "hide team budget" switches for the two team audiences.
|
|
5
6
|
*
|
|
@@ -23,7 +24,7 @@ export interface TeamBudgetVisibility {
|
|
|
23
24
|
* Consumption status derived from spend vs. cap. Mirrors the frontend
|
|
24
25
|
* `budgetTone` thresholds (80% near, 100% over) so both sides agree.
|
|
25
26
|
*/
|
|
26
|
-
export type TeamBudgetStatus =
|
|
27
|
+
export type TeamBudgetStatus = BudgetStatus;
|
|
27
28
|
export declare function teamBudgetStatusFor(spend: number, budget: number): TeamBudgetStatus;
|
|
28
29
|
export declare function readTeamBudgetVisibility(config: Config): TeamBudgetVisibility;
|
|
29
30
|
/**
|
|
@@ -4,12 +4,12 @@ exports.teamBudgetStatusFor = teamBudgetStatusFor;
|
|
|
4
4
|
exports.readTeamBudgetVisibility = readTeamBudgetVisibility;
|
|
5
5
|
exports.redactTeamBudget = redactTeamBudget;
|
|
6
6
|
exports.redactTeamUsage = redactTeamUsage;
|
|
7
|
+
const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
7
8
|
function teamBudgetStatusFor(spend, budget) {
|
|
8
|
-
if (budget
|
|
9
|
-
return '
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return 'ok';
|
|
9
|
+
if (budget <= 0)
|
|
10
|
+
return 'ok';
|
|
11
|
+
const pct = (spend / budget) * 100;
|
|
12
|
+
return (0, backstage_plugin_litellm_common_1.budgetStatusForPct)(pct);
|
|
13
13
|
}
|
|
14
14
|
function readTeamBudgetVisibility(config) {
|
|
15
15
|
return {
|
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": ["export
|
|
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 GenerateKeyRequest,\n type UpdateKeyRequest,\n type GenerateKeyResponse,\n type AuditLogEntry,\n type PaginatedAuditLogs,\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: number;\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/** One row from LiteLLM's `/spend/logs` (per-request spend log entry). */\nexport interface SpendLogEntry {\n request_id?: string;\n /** ISO timestamp of the call. */\n startTime?: string;\n endTime?: string;\n spend?: number;\n total_tokens?: number;\n prompt_tokens?: number;\n completion_tokens?: number;\n model?: string;\n /** Virtual key alias or hash the request was billed to. */\n api_key?: string;\n user?: string;\n team_id?: string;\n /**\n * Tags attached to the request (e.g. `channel:backstage`,\n * `session:<thread>`, `invoked-by:<user>`). Shape varies across LiteLLM\n * versions (array or object) and is normalised to a string[].\n */\n request_tags?: string[] | Record<string, string>;\n /** LiteLLM `metadata` blob; carries trace ids and session grouping. */\n metadata?: Record<string, unknown> | string | null;\n}\n\nexport interface SpendLogsParams {\n /** ISO date `YYYY-MM-DD` (inclusive). */\n start_date: string;\n /** ISO date `YYYY-MM-DD` (inclusive). */\n end_date: string;\n /** Restrict to the given virtual key. */\n api_key?: string;\n user_id?: string;\n team_id?: string;\n /** Max rows to return (LiteLLM caps at 1000 per page). */\n page_size?: number;\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
|
@@ -1,62 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
user_id: string;
|
|
3
|
-
user_email?: string;
|
|
4
|
-
email?: string;
|
|
5
|
-
teams?: string[];
|
|
6
|
-
models?: string[];
|
|
7
|
-
max_budget?: number;
|
|
8
|
-
spend?: number;
|
|
9
|
-
current_spend?: number;
|
|
10
|
-
soft_limit?: number;
|
|
11
|
-
hard_limit?: number;
|
|
12
|
-
/** Spend-reset period for max_budget, e.g. "30d". */
|
|
13
|
-
budget_duration?: string;
|
|
14
|
-
/** Backstage-computed: true when the user is a member of litellm.audit.group */
|
|
15
|
-
can_view_audit?: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface TeamMember {
|
|
18
|
-
user_id: string;
|
|
19
|
-
user_email?: string;
|
|
20
|
-
role: 'admin' | 'user';
|
|
21
|
-
}
|
|
22
|
-
export interface TeamObjectPermission {
|
|
23
|
-
/** Vector store ids/names attached to the team. */
|
|
24
|
-
vector_stores?: string[];
|
|
25
|
-
/** MCP server ids/names attached to the team. */
|
|
26
|
-
mcp_servers?: string[];
|
|
27
|
-
/** MCP access group names attached to the team. */
|
|
28
|
-
mcp_access_groups?: string[];
|
|
29
|
-
}
|
|
30
|
-
export interface TeamInfo {
|
|
31
|
-
team_id: string;
|
|
32
|
-
team_alias?: string;
|
|
33
|
-
max_budget?: number;
|
|
34
|
-
/** Spend-reset period for max_budget, e.g. "30d". */
|
|
35
|
-
budget_duration?: string;
|
|
36
|
-
spend: number;
|
|
37
|
-
/**
|
|
38
|
-
* Share of max_budget consumed (0-100), present only on redacted records.
|
|
39
|
-
* Set by the backend when litellm.display.hideTeamBudgetFor* is enabled so
|
|
40
|
-
* clients can render the consumption level without dollar amounts.
|
|
41
|
-
*/
|
|
42
|
-
budget_pct?: number;
|
|
43
|
-
/** ok | near (>=80%) | over (>=100%), present only on redacted records. */
|
|
44
|
-
budget_status?: 'ok' | 'near' | 'over';
|
|
45
|
-
/** True when max_budget/spend were stripped by budget hiding. */
|
|
46
|
-
budget_hidden?: boolean;
|
|
47
|
-
members_with_roles?: TeamMember[];
|
|
48
|
-
models?: string[];
|
|
49
|
-
tpm_limit?: number;
|
|
50
|
-
rpm_limit?: number;
|
|
51
|
-
/** Arbitrary metadata stored on the team record. */
|
|
52
|
-
metadata?: Record<string, unknown>;
|
|
53
|
-
/** Knowledge bases (vector stores) and MCP servers attached to the team. */
|
|
54
|
-
object_permission?: TeamObjectPermission;
|
|
55
|
-
/** Whether the team is blocked/deactivated. */
|
|
56
|
-
blocked?: boolean;
|
|
57
|
-
/** Max budget available to individual team members (if set per-member). */
|
|
58
|
-
team_member_budget?: number;
|
|
59
|
-
}
|
|
1
|
+
export { type UserInfo, type TeamMember, type TeamObjectPermission, type TeamInfo, type CreateTeamResponse, type VirtualKey, type ModelInfo, type UsageModelBreakdown, type UsageKeyBreakdown, type UsageDailyPoint, type UsageDailyModelPoint, type UsageMetrics, type GenerateKeyRequest, type UpdateKeyRequest, type GenerateKeyResponse, type AuditLogEntry, type PaginatedAuditLogs, } from '@acarmisc/backstage-plugin-litellm-common';
|
|
60
2
|
export interface CreateTeamRequest {
|
|
61
3
|
team_alias: string;
|
|
62
4
|
models?: string[];
|
|
@@ -65,33 +7,16 @@ export interface CreateTeamRequest {
|
|
|
65
7
|
tpm_limit?: number;
|
|
66
8
|
rpm_limit?: number;
|
|
67
9
|
metadata?: Record<string, unknown>;
|
|
68
|
-
object_permission?:
|
|
10
|
+
object_permission?: {
|
|
11
|
+
vector_stores?: string[];
|
|
12
|
+
mcp_servers?: string[];
|
|
13
|
+
mcp_access_groups?: string[];
|
|
14
|
+
};
|
|
69
15
|
}
|
|
70
16
|
export interface UpdateTeamRequest extends Partial<CreateTeamRequest> {
|
|
71
17
|
team_id: string;
|
|
72
18
|
blocked?: boolean;
|
|
73
19
|
}
|
|
74
|
-
export interface CreateTeamResponse {
|
|
75
|
-
team_id: string;
|
|
76
|
-
team_alias?: string;
|
|
77
|
-
}
|
|
78
|
-
export interface VirtualKey {
|
|
79
|
-
key: string;
|
|
80
|
-
token: string;
|
|
81
|
-
key_alias?: string;
|
|
82
|
-
created_at: string;
|
|
83
|
-
expires_at?: string;
|
|
84
|
-
spend: number;
|
|
85
|
-
max_budget?: number;
|
|
86
|
-
budget_duration?: string;
|
|
87
|
-
tpm_limit?: number;
|
|
88
|
-
rpm_limit?: number;
|
|
89
|
-
models?: string[];
|
|
90
|
-
user_id?: string;
|
|
91
|
-
/** Team the key is bound to, when any (mirrors LiteLLM /user/info team_id). */
|
|
92
|
-
team_id?: string;
|
|
93
|
-
blocked?: boolean;
|
|
94
|
-
}
|
|
95
20
|
/**
|
|
96
21
|
* Shape of a single entry inside LiteLLM's `/user/info` `keys` array.
|
|
97
22
|
* Differs from VirtualKey: uses `expires` (not `expires_at`), exposes
|
|
@@ -113,104 +38,8 @@ export interface LiteLLMUserKey {
|
|
|
113
38
|
team_id?: string | null;
|
|
114
39
|
created_at: string;
|
|
115
40
|
blocked?: boolean | null;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
model_name: string;
|
|
119
|
-
mode: string;
|
|
120
|
-
supports_function_calling?: boolean;
|
|
121
|
-
supports_vision?: boolean;
|
|
122
|
-
input_cost_per_token?: number;
|
|
123
|
-
output_cost_per_token?: number;
|
|
124
|
-
max_input_tokens?: number;
|
|
125
|
-
max_output_tokens?: number;
|
|
126
|
-
/** Access group names this model belongs to (litellm.model_info.access_groups). A team's `models` list can reference a group name instead of a literal model_name. */
|
|
127
|
-
access_groups?: string[];
|
|
128
|
-
}
|
|
129
|
-
export interface UsageModelBreakdown {
|
|
130
|
-
total_spend: number;
|
|
131
|
-
total_tokens: number;
|
|
132
|
-
prompt_tokens: number;
|
|
133
|
-
completion_tokens: number;
|
|
134
|
-
api_requests: number;
|
|
135
|
-
successful_requests: number;
|
|
136
|
-
failed_requests: number;
|
|
137
|
-
}
|
|
138
|
-
export interface UsageKeyBreakdown {
|
|
139
|
-
key_alias?: string;
|
|
140
|
-
team_id?: string | null;
|
|
141
|
-
models: string[];
|
|
142
|
-
total_spend: number;
|
|
143
|
-
total_tokens: number;
|
|
144
|
-
prompt_tokens: number;
|
|
145
|
-
completion_tokens: number;
|
|
146
|
-
api_requests: number;
|
|
147
|
-
successful_requests: number;
|
|
148
|
-
failed_requests: number;
|
|
149
|
-
}
|
|
150
|
-
export interface UsageDailyPoint {
|
|
151
|
-
date: string;
|
|
152
|
-
spend: number;
|
|
153
|
-
total_tokens: number;
|
|
154
|
-
prompt_tokens: number;
|
|
155
|
-
completion_tokens: number;
|
|
156
|
-
api_requests: number;
|
|
157
|
-
successful_requests: number;
|
|
158
|
-
failed_requests: number;
|
|
159
|
-
}
|
|
160
|
-
export interface UsageDailyModelPoint {
|
|
161
|
-
date: string;
|
|
162
|
-
model: string;
|
|
163
|
-
spend: number;
|
|
164
|
-
prompt_tokens: number;
|
|
165
|
-
completion_tokens: number;
|
|
166
|
-
total_tokens: number;
|
|
167
|
-
api_requests: number;
|
|
168
|
-
successful_requests: number;
|
|
169
|
-
failed_requests: number;
|
|
170
|
-
}
|
|
171
|
-
export interface UsageMetrics {
|
|
172
|
-
total_spend: number;
|
|
173
|
-
total_tokens: number;
|
|
174
|
-
prompt_tokens: number;
|
|
175
|
-
completion_tokens: number;
|
|
176
|
-
api_requests: number;
|
|
177
|
-
successful_requests: number;
|
|
178
|
-
failed_requests: number;
|
|
179
|
-
usage_by_model: Record<string, UsageModelBreakdown>;
|
|
180
|
-
usage_by_key: Record<string, UsageKeyBreakdown>;
|
|
181
|
-
daily_usage: UsageDailyPoint[];
|
|
182
|
-
daily_by_model: UsageDailyModelPoint[];
|
|
183
|
-
}
|
|
184
|
-
export interface GenerateKeyRequest {
|
|
185
|
-
alias?: string;
|
|
186
|
-
models?: string[];
|
|
187
|
-
duration?: string;
|
|
188
|
-
max_budget?: number;
|
|
189
|
-
tpm_limit?: number;
|
|
190
|
-
rpm_limit?: number;
|
|
191
|
-
user_id?: string;
|
|
192
|
-
team_id?: string;
|
|
193
|
-
key_type?: string;
|
|
194
|
-
metadata?: Record<string, string>;
|
|
195
|
-
}
|
|
196
|
-
export interface UpdateKeyRequest {
|
|
197
|
-
key: string;
|
|
198
|
-
key_alias?: string;
|
|
199
|
-
models?: string[];
|
|
200
|
-
max_budget?: number;
|
|
201
|
-
tpm_limit?: number;
|
|
202
|
-
rpm_limit?: number;
|
|
203
|
-
team_id?: string;
|
|
204
|
-
duration?: string;
|
|
205
|
-
}
|
|
206
|
-
export interface GenerateKeyResponse {
|
|
207
|
-
key: string;
|
|
208
|
-
key_alias?: string;
|
|
209
|
-
expires_at?: string;
|
|
210
|
-
max_budget?: number;
|
|
211
|
-
tpm_limit?: number;
|
|
212
|
-
rpm_limit?: number;
|
|
213
|
-
models?: string[];
|
|
41
|
+
/** Arbitrary metadata stored on the key record. */
|
|
42
|
+
metadata?: Record<string, unknown> | null;
|
|
214
43
|
}
|
|
215
44
|
export interface DeleteKeyRequest {
|
|
216
45
|
keys: string[];
|
|
@@ -267,24 +96,6 @@ export interface CreateUserResponse {
|
|
|
267
96
|
models?: string[];
|
|
268
97
|
teams?: string[];
|
|
269
98
|
}
|
|
270
|
-
export interface AuditLogEntry {
|
|
271
|
-
id: string;
|
|
272
|
-
updated_at: string;
|
|
273
|
-
changed_by?: string;
|
|
274
|
-
changed_by_api_key?: string;
|
|
275
|
-
action?: string;
|
|
276
|
-
table_name?: string;
|
|
277
|
-
object_id?: string;
|
|
278
|
-
before_value?: Record<string, unknown> | null;
|
|
279
|
-
updated_values?: Record<string, unknown> | null;
|
|
280
|
-
}
|
|
281
|
-
export interface PaginatedAuditLogs {
|
|
282
|
-
audit_logs: AuditLogEntry[];
|
|
283
|
-
total: number;
|
|
284
|
-
page: number;
|
|
285
|
-
page_size: number;
|
|
286
|
-
total_pages: number;
|
|
287
|
-
}
|
|
288
99
|
/** One row from LiteLLM's `/spend/logs` (per-request spend log entry). */
|
|
289
100
|
export interface SpendLogEntry {
|
|
290
101
|
request_id?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acarmisc/backstage-plugin-litellm-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "The Backstage backend plugin for LiteLLM governance",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -30,20 +30,23 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "node build.js && tsc -p tsconfig.json",
|
|
32
32
|
"lint": "eslint src --ext .ts",
|
|
33
|
-
"test": "tsc -p tsconfig.test.json && node --test dist-test/**/*.test.js
|
|
33
|
+
"test": "tsc -p tsconfig.test.json && node --test \"dist-test/**/*.test.js\"",
|
|
34
34
|
"api-report": "npm run build && api-extractor run --local --verbose",
|
|
35
35
|
"prepack": "npm run build",
|
|
36
36
|
"postpack": ""
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@acarmisc/backstage-plugin-litellm-common": "0.1.0",
|
|
39
40
|
"@backstage/backend-plugin-api": "^1.9.1",
|
|
40
41
|
"@backstage/catalog-client": "^1.9.0",
|
|
41
42
|
"@backstage/catalog-model": "^1.7.0",
|
|
42
43
|
"@backstage/config": "^1.3.0",
|
|
44
|
+
"@backstage/errors": "^1.3.0",
|
|
43
45
|
"@backstage/plugin-permission-common": "^0.9.10",
|
|
44
46
|
"@backstage/types": "^1.2.0",
|
|
45
47
|
"express": "^4.18.2",
|
|
46
|
-
"jose": "^5.9.6"
|
|
48
|
+
"jose": "^5.9.6",
|
|
49
|
+
"zod": "^3.22.0"
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
49
52
|
"@backstage/cli": "^0.36.2",
|
package/dist/permissions.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export declare const litellmKeyCreatePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
2
|
-
export declare const litellmKeyRevokePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
3
|
-
export declare const litellmKeyManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
4
|
-
export declare const litellmAuditReadPermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
5
|
-
export declare const litellmTeamCreatePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
6
|
-
export declare const litellmTeamManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
7
|
-
export declare const litellmTeamMembersManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
8
|
-
export declare const litellmTeamKnowledgebaseManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
9
|
-
export declare const litellmTeamMcpManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
10
|
-
export declare const litellmTeamDeletePermission: import("@backstage/plugin-permission-common").BasicPermission;
|
|
11
|
-
export declare const litellmPermissions: import("@backstage/plugin-permission-common").BasicPermission[];
|
package/dist/permissions.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.litellmPermissions = exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = void 0;
|
|
4
|
-
const plugin_permission_common_1 = require("@backstage/plugin-permission-common");
|
|
5
|
-
exports.litellmKeyCreatePermission = (0, plugin_permission_common_1.createPermission)({
|
|
6
|
-
name: 'litellm.key.create',
|
|
7
|
-
attributes: { action: 'create' },
|
|
8
|
-
});
|
|
9
|
-
exports.litellmKeyRevokePermission = (0, plugin_permission_common_1.createPermission)({
|
|
10
|
-
name: 'litellm.key.revoke',
|
|
11
|
-
attributes: { action: 'delete' },
|
|
12
|
-
});
|
|
13
|
-
exports.litellmKeyManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
14
|
-
name: 'litellm.key.manage',
|
|
15
|
-
attributes: { action: 'update' },
|
|
16
|
-
});
|
|
17
|
-
exports.litellmAuditReadPermission = (0, plugin_permission_common_1.createPermission)({
|
|
18
|
-
name: 'litellm.audit.read',
|
|
19
|
-
attributes: { action: 'read' },
|
|
20
|
-
});
|
|
21
|
-
// Team-management permissions — delegated to litellm-team-admins group
|
|
22
|
-
exports.litellmTeamCreatePermission = (0, plugin_permission_common_1.createPermission)({
|
|
23
|
-
name: 'litellm.team.create',
|
|
24
|
-
attributes: { action: 'create' },
|
|
25
|
-
});
|
|
26
|
-
exports.litellmTeamManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
27
|
-
name: 'litellm.team.manage',
|
|
28
|
-
attributes: { action: 'update' },
|
|
29
|
-
});
|
|
30
|
-
exports.litellmTeamMembersManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
31
|
-
name: 'litellm.team.members.manage',
|
|
32
|
-
attributes: { action: 'update' },
|
|
33
|
-
});
|
|
34
|
-
exports.litellmTeamKnowledgebaseManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
35
|
-
name: 'litellm.team.knowledgebase.manage',
|
|
36
|
-
attributes: { action: 'update' },
|
|
37
|
-
});
|
|
38
|
-
exports.litellmTeamMcpManagePermission = (0, plugin_permission_common_1.createPermission)({
|
|
39
|
-
name: 'litellm.team.mcp.manage',
|
|
40
|
-
attributes: { action: 'update' },
|
|
41
|
-
});
|
|
42
|
-
exports.litellmTeamDeletePermission = (0, plugin_permission_common_1.createPermission)({
|
|
43
|
-
name: 'litellm.team.delete',
|
|
44
|
-
attributes: { action: 'delete' },
|
|
45
|
-
});
|
|
46
|
-
exports.litellmPermissions = [
|
|
47
|
-
exports.litellmKeyCreatePermission,
|
|
48
|
-
exports.litellmKeyRevokePermission,
|
|
49
|
-
exports.litellmKeyManagePermission,
|
|
50
|
-
exports.litellmAuditReadPermission,
|
|
51
|
-
exports.litellmTeamCreatePermission,
|
|
52
|
-
exports.litellmTeamManagePermission,
|
|
53
|
-
exports.litellmTeamMembersManagePermission,
|
|
54
|
-
exports.litellmTeamKnowledgebaseManagePermission,
|
|
55
|
-
exports.litellmTeamMcpManagePermission,
|
|
56
|
-
exports.litellmTeamDeletePermission,
|
|
57
|
-
];
|