@acarmisc/backstage-plugin-litellm-backend 0.26.1 → 0.28.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.
@@ -12,6 +12,23 @@ const withUser_1 = require("./middleware/withUser");
12
12
  function registerTeamUsageRoutes(router, ctx) {
13
13
  const { client, catalogClient, auth, logger, teamBudgetVisibility, } = ctx;
14
14
  const requireUser = (0, withUser_1.createRequireUser)(ctx);
15
+ // A team manager: a member of the team's owning group (team management
16
+ // enabled), or a member of the catalog lead group `<alias><leadGroupSuffix>`
17
+ // for teams synced without an owning_group.
18
+ async function isTeamManager(team, tokenEntityRef) {
19
+ const owningGroup = typeof team.metadata?.owning_group === 'string' ? team.metadata.owning_group : undefined;
20
+ if (ctx.teamMgmtEnabled &&
21
+ owningGroup &&
22
+ (await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger))) {
23
+ return true;
24
+ }
25
+ const suffix = ctx.memberBreakdownCfg.leadGroupSuffix;
26
+ if (!suffix || !team.team_alias)
27
+ return false;
28
+ // Same slug rule as the catalog group names: lower-case, other chars become '-'.
29
+ const slug = team.team_alias.toLowerCase().replace(/[^a-z0-9\-_.]/g, '-');
30
+ return (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, `group:default/${slug}${suffix}`, catalogClient, auth, logger);
31
+ }
15
32
  router.get('/teams/:teamId/usage', requireUser, async (req, res) => {
16
33
  try {
17
34
  const { teamId } = req.params;
@@ -25,24 +42,20 @@ function registerTeamUsageRoutes(router, ctx) {
25
42
  const userInfo = await (0, withUser_1.getProvisionedUser)(ctx, res);
26
43
  // Check membership: user must be in the team OR be a team manager
27
44
  const isMember = userInfo?.teams?.includes(teamId) ?? false;
28
- // A "manager" administers this specific team: team management is enabled
29
- // and the caller belongs to the team's owning group (metadata.owning_group).
45
+ // A "manager" administers this specific team (see isTeamManager).
30
46
  // Membership of the global team-admin group alone is not enough.
31
47
  let isManager = false;
32
48
  // Skip the extra lookup when it can't change the outcome: members always
33
49
  // get through, and manager-ness only matters for members when the budget
34
50
  // hiding flags differ between the two roles.
35
- const managerCheckNeeded = ctx.teamMgmtEnabled &&
51
+ const managerCheckNeeded = (ctx.teamMgmtEnabled || !!ctx.memberBreakdownCfg.leadGroupSuffix) &&
36
52
  (!isMember ||
37
53
  teamBudgetVisibility.hideTeamBudgetForMembers !==
38
54
  teamBudgetVisibility.hideTeamBudgetForManagers);
39
55
  if (managerCheckNeeded) {
40
56
  try {
41
57
  const team = await (0, teamFetch_1.withTeamFetchRetry)(() => client.getTeamInfo(teamId));
42
- const owningGroup = typeof team.metadata?.owning_group === 'string' ? team.metadata.owning_group : undefined;
43
- if (owningGroup) {
44
- isManager = await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger);
45
- }
58
+ isManager = await isTeamManager(team, tokenEntityRef);
46
59
  }
47
60
  catch (err) {
48
61
  const message = err instanceof Error ? err.message : String(err);
@@ -72,75 +85,92 @@ function registerTeamUsageRoutes(router, ctx) {
72
85
  (0, errors_1.sendError)(res, error, logger, 'fetch team usage');
73
86
  }
74
87
  });
75
- // Per-member breakdown of a team's usage. Opt-in (404 when disabled) and
88
+ // Shared gate of the per-member routes. Opt-in (404 when disabled) and
76
89
  // fail-closed: the caller must be a manager of the team (owning_group), or
77
90
  // a member who holds one of memberBreakdown.viewerRoles in the team or is
78
- // granted litellm.team.usage.read.
79
- router.get('/teams/:teamId/usage/members', requireUser, async (req, res) => {
91
+ // granted litellm.team.usage.read. Scoped to the one team in the path, so
92
+ // leading a team never reveals another. Replies and returns null on refusal.
93
+ async function authorizeMemberView(req, res) {
80
94
  const cfg = ctx.memberBreakdownCfg;
81
95
  if (!cfg.enabled) {
82
96
  res.status(404).json({ error: 'Not found' });
83
- return;
97
+ return null;
84
98
  }
99
+ const { teamId } = req.params;
100
+ const { start_date, end_date } = req.query;
101
+ if (!start_date || !end_date) {
102
+ res.status(400).json({ error: 'start_date and end_date are required' });
103
+ return null;
104
+ }
105
+ const tokenEntityRef = res.locals.tokenEntityRef;
106
+ const userInfo = await (0, withUser_1.getProvisionedUser)(ctx, res);
107
+ const callerId = res.locals.userId;
108
+ const isMember = userInfo?.teams?.includes(teamId) ?? false;
109
+ let team;
85
110
  try {
86
- const { teamId } = req.params;
87
- const { start_date, end_date } = req.query;
88
- if (!start_date || !end_date) {
89
- res.status(400).json({ error: 'start_date and end_date are required' });
90
- return;
91
- }
92
- const tokenEntityRef = res.locals.tokenEntityRef;
93
- const userInfo = await (0, withUser_1.getProvisionedUser)(ctx, res);
94
- const callerId = res.locals.userId;
95
- const isMember = userInfo?.teams?.includes(teamId) ?? false;
96
- let team;
97
- try {
98
- team = await (0, teamFetch_1.withTeamFetchRetry)(() => client.getTeamInfo(teamId));
99
- }
100
- catch (err) {
101
- if (err instanceof client_1.LiteLLMUpstreamError && err.status === 404) {
102
- res.status(404).json({ error: 'Team not found' });
103
- return;
104
- }
105
- throw err;
106
- }
107
- const owningGroup = typeof team.metadata?.owning_group === 'string' ? team.metadata.owning_group : undefined;
108
- const isManager = ctx.teamMgmtEnabled &&
109
- !!owningGroup &&
110
- (await (0, provisioning_1.isUserMemberOfGroup)(tokenEntityRef, owningGroup, catalogClient, auth, logger));
111
- if (!isMember && !isManager) {
111
+ team = await (0, teamFetch_1.withTeamFetchRetry)(() => client.getTeamInfo(teamId));
112
+ }
113
+ catch (err) {
114
+ if (err instanceof client_1.LiteLLMUpstreamError && err.status === 404) {
112
115
  res.status(404).json({ error: 'Team not found' });
113
- return;
116
+ return null;
114
117
  }
115
- const role = team.members_with_roles?.find(m => m.user_id === callerId)?.role;
116
- const viaRole = isMember && !!role && cfg.viewerRoles.includes(role);
117
- const allowed = isManager ||
118
- viaRole ||
119
- (isMember &&
120
- cfg.permissionEnabled &&
121
- (await ctx.assertPermission(req, backstage_plugin_litellm_common_1.litellmTeamUsageReadPermission)));
122
- if (!allowed) {
123
- res.status(403).json({
124
- error: 'Access denied: viewing per-member usage needs a team role or permission',
125
- });
118
+ throw err;
119
+ }
120
+ const isManager = await isTeamManager(team, tokenEntityRef);
121
+ if (!isMember && !isManager) {
122
+ res.status(404).json({ error: 'Team not found' });
123
+ return null;
124
+ }
125
+ const role = team.members_with_roles?.find(m => m.user_id === callerId)?.role;
126
+ const viaRole = isMember && !!role && cfg.viewerRoles.includes(role);
127
+ const allowed = isManager ||
128
+ viaRole ||
129
+ (isMember &&
130
+ cfg.permissionEnabled &&
131
+ (await ctx.assertPermission(req, backstage_plugin_litellm_common_1.litellmTeamUsageReadPermission)));
132
+ if (!allowed) {
133
+ res.status(403).json({
134
+ error: 'Access denied: viewing per-member usage needs a team role or permission',
135
+ });
136
+ return null;
137
+ }
138
+ let via = 'permission';
139
+ if (isManager)
140
+ via = 'manager';
141
+ else if (viaRole)
142
+ via = 'teamRole';
143
+ return {
144
+ teamId,
145
+ team,
146
+ startDate: start_date,
147
+ endDate: end_date,
148
+ isManager,
149
+ via,
150
+ };
151
+ }
152
+ const hideFor = (isManager) => isManager
153
+ ? teamBudgetVisibility.hideTeamBudgetForManagers
154
+ : teamBudgetVisibility.hideTeamBudgetForMembers;
155
+ // Per-member breakdown of a team's usage.
156
+ router.get('/teams/:teamId/usage/members', requireUser, async (req, res) => {
157
+ try {
158
+ const gate = await authorizeMemberView(req, res);
159
+ if (!gate)
126
160
  return;
127
- }
161
+ const { teamId, team, startDate, endDate, isManager, via } = gate;
128
162
  const [usage, keys] = await Promise.all([
129
- client.getTeamUsage(teamId, start_date, end_date),
163
+ client.getTeamUsage(teamId, startDate, endDate),
130
164
  client.listTeamKeys(teamId),
131
165
  ]);
132
166
  const profiles = await lookupProfiles((0, teamMemberUsage_1.memberEmails)(team, keys));
133
167
  const breakdown = (0, teamMemberUsage_1.buildTeamMemberUsage)(teamId, usage, keys, team, profiles);
134
- let via = 'permission';
135
- if (isManager)
136
- via = 'manager';
137
- else if (viaRole)
138
- via = 'teamRole';
139
- logger.info('team.usage.members.read', { actor: tokenEntityRef, teamId, via });
140
- const hide = isManager
141
- ? teamBudgetVisibility.hideTeamBudgetForManagers
142
- : teamBudgetVisibility.hideTeamBudgetForMembers;
143
- res.json(hide ? (0, teamMemberUsage_1.redactTeamMemberUsage)(breakdown) : breakdown);
168
+ logger.info('team.usage.members.read', {
169
+ actor: res.locals.tokenEntityRef,
170
+ teamId,
171
+ via,
172
+ });
173
+ res.json(hideFor(isManager) ? (0, teamMemberUsage_1.redactTeamMemberUsage)(breakdown) : breakdown);
144
174
  }
145
175
  catch (error) {
146
176
  if (error instanceof provisioning_1.ProvisioningError) {
@@ -150,6 +180,36 @@ function registerTeamUsageRoutes(router, ctx) {
150
180
  (0, errors_1.sendError)(res, error, logger, 'fetch team member usage');
151
181
  }
152
182
  });
183
+ // The team's keys (alias, owner, status, budget) with per-key usage, so a
184
+ // team lead sees the members' keys and their consumption. Same gate and
185
+ // redaction as the per-member breakdown.
186
+ router.get('/teams/:teamId/usage/keys', requireUser, async (req, res) => {
187
+ try {
188
+ const gate = await authorizeMemberView(req, res);
189
+ if (!gate)
190
+ return;
191
+ const { teamId, team, startDate, endDate, isManager, via } = gate;
192
+ const [usage, keys] = await Promise.all([
193
+ client.getTeamUsage(teamId, startDate, endDate),
194
+ client.listTeamKeys(teamId),
195
+ ]);
196
+ const profiles = await lookupProfiles((0, teamMemberUsage_1.memberEmails)(team, keys));
197
+ const keyUsage = (0, teamMemberUsage_1.buildTeamKeyUsage)(teamId, usage, keys, team, profiles);
198
+ logger.info('team.usage.keys.read', {
199
+ actor: res.locals.tokenEntityRef,
200
+ teamId,
201
+ via,
202
+ });
203
+ res.json(hideFor(isManager) ? (0, teamMemberUsage_1.redactTeamKeyUsage)(keyUsage) : keyUsage);
204
+ }
205
+ catch (error) {
206
+ if (error instanceof provisioning_1.ProvisioningError) {
207
+ res.status(error.status).json(error.body);
208
+ return;
209
+ }
210
+ (0, errors_1.sendError)(res, error, logger, 'fetch team key usage');
211
+ }
212
+ });
153
213
  // Display names from catalog User entities with these emails. Best effort:
154
214
  // a catalog failure only drops the names.
155
215
  async function lookupProfiles(emails) {
@@ -1,5 +1,5 @@
1
1
  import { Config } from '@backstage/config';
2
- import { TeamInfo, TeamMemberUsage, UsageMetrics, VirtualKey } from './types';
2
+ import { TeamInfo, TeamKeyUsage, TeamMemberUsage, UsageMetrics, VirtualKey } from './types';
3
3
  /** `litellm.teamUsage.memberBreakdown`: who may see a team's per-member usage. */
4
4
  export interface MemberBreakdownConfig {
5
5
  /** Off by default: the route returns 404 and the UI hides the table. */
@@ -8,6 +8,8 @@ export interface MemberBreakdownConfig {
8
8
  viewerRoles: string[];
9
9
  /** Whether `litellm.team.usage.read` may grant access (needs `permission.enabled`). */
10
10
  permissionEnabled: boolean;
11
+ /** Suffix of a catalog group named `<team_alias><suffix>` whose members are team leads. */
12
+ leadGroupSuffix?: string;
11
13
  }
12
14
  export declare function readMemberBreakdownConfig(config: Config): MemberBreakdownConfig;
13
15
  /** Catalog profile of a member, keyed by lower-cased email. */
@@ -32,3 +34,11 @@ export declare function buildTeamMemberUsage(teamId: string, usage: UsageMetrics
32
34
  * way to compare members. Tokens and requests are kept.
33
35
  */
34
36
  export declare function redactTeamMemberUsage(usage: TeamMemberUsage): TeamMemberUsage;
37
+ /**
38
+ * Lists a team's keys with their usage over the period, credited by key hash
39
+ * from the team's daily activity. Only fields safe to show a team lead are
40
+ * copied: no secret, hash or metadata. Keys without activity get zero rows.
41
+ */
42
+ export declare function buildTeamKeyUsage(teamId: string, usage: UsageMetrics, keys: VirtualKey[], team: TeamInfo, profiles?: MemberProfiles): TeamKeyUsage;
43
+ /** Hides dollar amounts (period spend and key budgets); tokens and requests stay. */
44
+ export declare function redactTeamKeyUsage(usage: TeamKeyUsage): TeamKeyUsage;
@@ -4,11 +4,14 @@ exports.readMemberBreakdownConfig = readMemberBreakdownConfig;
4
4
  exports.memberEmails = memberEmails;
5
5
  exports.buildTeamMemberUsage = buildTeamMemberUsage;
6
6
  exports.redactTeamMemberUsage = redactTeamMemberUsage;
7
+ exports.buildTeamKeyUsage = buildTeamKeyUsage;
8
+ exports.redactTeamKeyUsage = redactTeamKeyUsage;
7
9
  function readMemberBreakdownConfig(config) {
8
10
  return {
9
11
  enabled: config.getOptionalBoolean('litellm.teamUsage.memberBreakdown.enabled') ?? false,
10
12
  viewerRoles: config.getOptionalStringArray('litellm.teamUsage.memberBreakdown.viewerRoles') ?? [],
11
13
  permissionEnabled: config.getOptionalBoolean('permission.enabled') ?? false,
14
+ leadGroupSuffix: config.getOptionalString('litellm.teamUsage.memberBreakdown.leadGroupSuffix') || undefined,
12
15
  };
13
16
  }
14
17
  /**
@@ -112,3 +115,51 @@ function redactTeamMemberUsage(usage) {
112
115
  members: usage.members.map(m => ({ ...m, spend: 0 })),
113
116
  };
114
117
  }
118
+ /**
119
+ * Lists a team's keys with their usage over the period, credited by key hash
120
+ * from the team's daily activity. Only fields safe to show a team lead are
121
+ * copied: no secret, hash or metadata. Keys without activity get zero rows.
122
+ */
123
+ function buildTeamKeyUsage(teamId, usage, keys, team, profiles = new Map()) {
124
+ const emailById = new Map();
125
+ for (const m of team.members_with_roles ?? []) {
126
+ if (m.user_id && m.user_email)
127
+ emailById.set(m.user_id, m.user_email);
128
+ }
129
+ const rows = keys.map(k => {
130
+ const u = (k.token && usage.usage_by_key?.[k.token]) || undefined;
131
+ const userId = k.user_id ?? null;
132
+ const email = (userId && emailById.get(userId)) || (userId?.includes('@') ? userId : undefined);
133
+ const displayName = email && profiles.get(email.toLowerCase())?.displayName;
134
+ return {
135
+ key: k.key,
136
+ ...(k.key_alias && { key_alias: k.key_alias }),
137
+ user_id: userId,
138
+ ...(email && { user_email: email }),
139
+ ...(displayName && { display_name: displayName }),
140
+ created_at: k.created_at,
141
+ ...(k.expires_at && { expires_at: k.expires_at }),
142
+ blocked: k.blocked === true,
143
+ ...(k.max_budget !== undefined && { max_budget: k.max_budget }),
144
+ spend: u?.total_spend ?? 0,
145
+ prompt_tokens: u?.prompt_tokens ?? 0,
146
+ completion_tokens: u?.completion_tokens ?? 0,
147
+ total_tokens: u?.total_tokens ?? 0,
148
+ api_requests: u?.api_requests ?? 0,
149
+ successful_requests: u?.successful_requests ?? 0,
150
+ failed_requests: u?.failed_requests ?? 0,
151
+ };
152
+ });
153
+ rows.sort((a, b) => b.spend - a.spend ||
154
+ b.total_tokens - a.total_tokens ||
155
+ a.key.localeCompare(b.key));
156
+ return { team_id: teamId, keys: rows };
157
+ }
158
+ /** Hides dollar amounts (period spend and key budgets); tokens and requests stay. */
159
+ function redactTeamKeyUsage(usage) {
160
+ return {
161
+ ...usage,
162
+ budget_hidden: true,
163
+ keys: usage.keys.map(({ max_budget: _hidden, ...k }) => ({ ...k, spend: 0 })),
164
+ };
165
+ }
@@ -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 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: 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\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"],
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: 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\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,4 +1,4 @@
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 TeamMemberUsage, type TeamMemberUsageRow, type GenerateKeyRequest, type UpdateKeyRequest, type GenerateKeyResponse, type AuditLogEntry, type PaginatedAuditLogs, type SpendLogEntry, type SpendLogsParams, type PaginatedSpendLogs, type QualityByModel, type QualityScore, type QualitySummary, type QualityResponse, } from '@acarmisc/backstage-plugin-litellm-common';
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 TeamMemberUsage, type TeamMemberUsageRow, type TeamKeyUsage, type TeamKeyUsageRow, type GenerateKeyRequest, type UpdateKeyRequest, type GenerateKeyResponse, type AuditLogEntry, type PaginatedAuditLogs, type SpendLogEntry, type SpendLogsParams, type PaginatedSpendLogs, type QualityByModel, type QualityScore, type QualitySummary, type QualityResponse, } from '@acarmisc/backstage-plugin-litellm-common';
2
2
  export interface CreateTeamRequest {
3
3
  team_alias: string;
4
4
  models?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.26.1",
3
+ "version": "0.28.0",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -35,7 +35,7 @@
35
35
  "prepack": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@acarmisc/backstage-plugin-litellm-common": "0.5.0",
38
+ "@acarmisc/backstage-plugin-litellm-common": "0.6.0",
39
39
  "@backstage/backend-plugin-api": "^1.9.1",
40
40
  "@backstage/catalog-client": "^1.9.0",
41
41
  "@backstage/config": "^1.3.0",