@acarmisc/backstage-plugin-litellm-backend 0.8.0 → 0.8.2

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.
@@ -9,8 +9,8 @@ import { UserInfo, ProvisioningDefaults, RoleConfig } from './types';
9
9
  *
10
10
  * When userIdDomain is configured, the entity name is suffixed with the domain
11
11
  * so that LiteLLM user_ids match the organisation's email addresses:
12
- * "user:default/andrea.carmisciano" + "abstract.it"
13
- * → "andrea.carmisciano@abstract.it"
12
+ * "user:default/andrea.carmisciano" + "example.com"
13
+ * → "andrea.carmisciano@example.com"
14
14
  *
15
15
  * Without a domain the bare entity name is returned unchanged, which works for
16
16
  * deployments where LiteLLM users were created with plain usernames.
@@ -16,8 +16,8 @@ exports.resolveUserRole = resolveUserRole;
16
16
  *
17
17
  * When userIdDomain is configured, the entity name is suffixed with the domain
18
18
  * so that LiteLLM user_ids match the organisation's email addresses:
19
- * "user:default/andrea.carmisciano" + "abstract.it"
20
- * → "andrea.carmisciano@abstract.it"
19
+ * "user:default/andrea.carmisciano" + "example.com"
20
+ * → "andrea.carmisciano@example.com"
21
21
  *
22
22
  * Without a domain the bare entity name is returned unchanged, which works for
23
23
  * deployments where LiteLLM users were created with plain usernames.
@@ -168,8 +168,7 @@ async function provisionUser(client, userId, defaults, profile, backstageEntity,
168
168
  }),
169
169
  },
170
170
  };
171
- logger.info(`Provisioning new LiteLLM user for Backstage identity: ${userId}` +
172
- (profile.email ? ` (email=${profile.email})` : ''));
171
+ logger.info(`Provisioning new LiteLLM user for Backstage identity: ${userId}${profile.email ? ` (email=${profile.email})` : ''}`);
173
172
  try {
174
173
  await client.createUser(payload);
175
174
  // Defensive /user/update: LiteLLM's /user/new upsert path has been
package/dist/router.js CHANGED
@@ -189,12 +189,18 @@ async function createRouter(options) {
189
189
  ? (0, provisioning_1.toLiteLLMUserId)(tokenEntityRef, userIdDomain)
190
190
  : req.query.user_id;
191
191
  if (!userId) {
192
- throw { status: 403, body: { error: 'Cannot verify key ownership without an authenticated user' } };
192
+ throw Object.assign(new Error('Cannot verify key ownership without an authenticated user'), {
193
+ status: 403,
194
+ body: { error: 'Cannot verify key ownership without an authenticated user' },
195
+ });
193
196
  }
194
197
  const ownKeys = await client.listKeys(userId);
195
198
  const owns = ownKeys.some(k => (k.token ?? k.key) === keyId);
196
199
  if (!owns) {
197
- throw { status: 403, body: { error: 'Access denied: key does not belong to the caller' } };
200
+ throw Object.assign(new Error('Access denied: key does not belong to the caller'), {
201
+ status: 403,
202
+ body: { error: 'Access denied: key does not belong to the caller' },
203
+ });
198
204
  }
199
205
  return { tokenEntityRef, userId };
200
206
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/types.ts"],
4
- "sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n /** Backstage-computed: true when the user is a member of litellm.audit.group */\n can_view_audit?: boolean;\n}\n\nexport interface TeamMember {\n user_id: string;\n user_email?: string;\n role: 'admin' | 'user';\n}\n\nexport interface TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n}\n\nexport interface VirtualKey {\n key: string;\n token: string;\n key_alias?: string;\n created_at: string;\n expires_at?: string;\n spend: number;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n user_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 user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n}\n\nexport interface ModelInfo {\n model_name: string;\n mode: string;\n supports_function_calling?: boolean;\n supports_vision?: boolean;\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n max_input_tokens?: number;\n max_output_tokens?: number;\n}\n\nexport interface UsageModelBreakdown {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageKeyBreakdown {\n key_alias?: string;\n team_id?: string | null;\n models: string[];\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyPoint {\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyModelPoint {\n date: string;\n model: string;\n spend: number;\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n usage_by_model: Record<string, UsageModelBreakdown>;\n usage_by_key: Record<string, UsageKeyBreakdown>;\n daily_usage: UsageDailyPoint[];\n daily_by_model: UsageDailyModelPoint[];\n}\n\nexport interface GenerateKeyRequest {\n alias?: string;\n models?: string[];\n duration?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n user_id?: string;\n team_id?: string;\n key_type?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface UpdateKeyRequest {\n key: string;\n key_alias?: string;\n models?: string[];\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n team_id?: string;\n duration?: string;\n}\n\nexport interface GenerateKeyResponse {\n key: string;\n key_alias?: string;\n expires_at?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\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\nexport interface AuditLogEntry {\n id: string;\n updated_at: string;\n changed_by?: string;\n changed_by_api_key?: string;\n action?: string;\n table_name?: string;\n object_id?: string;\n before_value?: Record<string, unknown> | null;\n updated_values?: Record<string, unknown> | null;\n}\n\nexport interface PaginatedAuditLogs {\n audit_logs: AuditLogEntry[];\n total: number;\n page: number;\n page_size: number;\n total_pages: 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"],
4
+ "sourcesContent": ["export interface UserInfo {\n user_id: string;\n user_email?: string;\n email?: string;\n teams?: string[];\n models?: string[];\n max_budget?: number;\n spend?: number;\n current_spend?: number;\n soft_limit?: number;\n hard_limit?: number;\n /** Backstage-computed: true when the user is a member of litellm.audit.group */\n can_view_audit?: boolean;\n}\n\nexport interface TeamMember {\n user_id: string;\n user_email?: string;\n role: 'admin' | 'user';\n}\n\nexport interface TeamInfo {\n team_id: string;\n team_alias?: string;\n max_budget?: number;\n spend: number;\n members_with_roles?: TeamMember[];\n models?: string[];\n tpm_limit?: number;\n rpm_limit?: number;\n}\n\nexport interface VirtualKey {\n key: string;\n token: string;\n key_alias?: string;\n created_at: string;\n expires_at?: string;\n spend: number;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\n user_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 user_id?: string | null;\n team_id?: string | null;\n created_at: string;\n blocked?: boolean | null;\n}\n\nexport interface ModelInfo {\n model_name: string;\n mode: string;\n supports_function_calling?: boolean;\n supports_vision?: boolean;\n input_cost_per_token?: number;\n output_cost_per_token?: number;\n max_input_tokens?: number;\n max_output_tokens?: number;\n /** 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. */\n access_groups?: string[];\n}\n\nexport interface UsageModelBreakdown {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageKeyBreakdown {\n key_alias?: string;\n team_id?: string | null;\n models: string[];\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyPoint {\n date: string;\n spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageDailyModelPoint {\n date: string;\n model: string;\n spend: number;\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n}\n\nexport interface UsageMetrics {\n total_spend: number;\n total_tokens: number;\n prompt_tokens: number;\n completion_tokens: number;\n api_requests: number;\n successful_requests: number;\n failed_requests: number;\n usage_by_model: Record<string, UsageModelBreakdown>;\n usage_by_key: Record<string, UsageKeyBreakdown>;\n daily_usage: UsageDailyPoint[];\n daily_by_model: UsageDailyModelPoint[];\n}\n\nexport interface GenerateKeyRequest {\n alias?: string;\n models?: string[];\n duration?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n user_id?: string;\n team_id?: string;\n key_type?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface UpdateKeyRequest {\n key: string;\n key_alias?: string;\n models?: string[];\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n team_id?: string;\n duration?: string;\n}\n\nexport interface GenerateKeyResponse {\n key: string;\n key_alias?: string;\n expires_at?: string;\n max_budget?: number;\n tpm_limit?: number;\n rpm_limit?: number;\n models?: string[];\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\nexport interface AuditLogEntry {\n id: string;\n updated_at: string;\n changed_by?: string;\n changed_by_api_key?: string;\n action?: string;\n table_name?: string;\n object_id?: string;\n before_value?: Record<string, unknown> | null;\n updated_values?: Record<string, unknown> | null;\n}\n\nexport interface PaginatedAuditLogs {\n audit_logs: AuditLogEntry[];\n total: number;\n page: number;\n page_size: number;\n total_pages: 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
@@ -71,6 +71,8 @@ export interface ModelInfo {
71
71
  output_cost_per_token?: number;
72
72
  max_input_tokens?: number;
73
73
  max_output_tokens?: number;
74
+ /** 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. */
75
+ access_groups?: string[];
74
76
  }
75
77
  export interface UsageModelBreakdown {
76
78
  total_spend: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -29,6 +29,8 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "build": "node build.js && tsc -p tsconfig.json",
32
+ "lint": "eslint src --ext .ts",
33
+ "test": "tsc -p tsconfig.test.json && node --test dist-test/**/*.test.js dist-test/*.test.js",
32
34
  "prepack": "npm run build",
33
35
  "postpack": ""
34
36
  },