@acarmisc/backstage-plugin-litellm-common 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # @acarmisc/backstage-plugin-litellm-common
2
+
3
+ Shared code for the **Backstage LiteLLM Governance Plugin**: the `litellm.*`
4
+ permission definitions, the request schemas used to validate key creation and
5
+ updates, budget-status thresholds and the API types shared by the frontend and
6
+ backend packages.
7
+
8
+ You normally don't install it directly — it comes with
9
+ [`@acarmisc/backstage-plugin-litellm`](https://www.npmjs.com/package/@acarmisc/backstage-plugin-litellm)
10
+ and [`@acarmisc/backstage-plugin-litellm-backend`](https://www.npmjs.com/package/@acarmisc/backstage-plugin-litellm-backend).
11
+ Import it when you write a permission policy that refers to the plugin's
12
+ permissions:
13
+
14
+ ```ts
15
+ import {
16
+ litellmKeyCreatePermission,
17
+ litellmTeamManagePermission,
18
+ } from '@acarmisc/backstage-plugin-litellm-common';
19
+ ```
20
+
21
+ See [docs/permissions.md](https://github.com/acarmisc/backstage-plugin-litellm-govai/blob/main/docs/permissions.md)
22
+ for what each permission guards.
23
+
24
+ ## License
25
+
26
+ Apache-2.0
package/dist/index.cjs.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  ALL_PROXY_MODELS: () => ALL_PROXY_MODELS,
24
24
  BUDGET_DANGER_PCT: () => BUDGET_DANGER_PCT,
25
25
  BUDGET_WARNING_PCT: () => BUDGET_WARNING_PCT,
26
+ DEFAULT_KEY_BUDGET_DURATIONS: () => DEFAULT_KEY_BUDGET_DURATIONS,
26
27
  DEFAULT_KEY_DURATIONS: () => DEFAULT_KEY_DURATIONS,
27
28
  RESERVED_METADATA_KEYS: () => RESERVED_METADATA_KEYS,
28
29
  budgetStatusForPct: () => budgetStatusForPct,
@@ -43,6 +44,7 @@ __export(index_exports, {
43
44
  litellmTeamManagePermission: () => litellmTeamManagePermission,
44
45
  litellmTeamMcpManagePermission: () => litellmTeamMcpManagePermission,
45
46
  litellmTeamMembersManagePermission: () => litellmTeamMembersManagePermission,
47
+ litellmTeamUsageReadPermission: () => litellmTeamUsageReadPermission,
46
48
  reservedMetadataKeys: () => reservedMetadataKeys
47
49
  });
48
50
  module.exports = __toCommonJS(index_exports);
@@ -97,6 +99,10 @@ var litellmTeamDeletePermission = (0, import_plugin_permission_common.createPerm
97
99
  name: "litellm.team.delete",
98
100
  attributes: { action: "delete" }
99
101
  });
102
+ var litellmTeamUsageReadPermission = (0, import_plugin_permission_common.createPermission)({
103
+ name: "litellm.team.usage.read",
104
+ attributes: { action: "read" }
105
+ });
100
106
  var litellmPermissions = [
101
107
  litellmKeyCreatePermission,
102
108
  litellmKeyRevokePermission,
@@ -109,7 +115,8 @@ var litellmPermissions = [
109
115
  litellmTeamMembersManagePermission,
110
116
  litellmTeamKnowledgebaseManagePermission,
111
117
  litellmTeamMcpManagePermission,
112
- litellmTeamDeletePermission
118
+ litellmTeamDeletePermission,
119
+ litellmTeamUsageReadPermission
113
120
  ];
114
121
 
115
122
  // src/keySchemas.ts
@@ -130,11 +137,21 @@ function createGenerateKeyInputSchema(config) {
130
137
  ) : import_zod.z.string().regex(/^\d+[smhdwy]$/, {
131
138
  message: 'duration must match format <number><unit> (e.g. "30d", "1h")'
132
139
  });
140
+ const budgetDurations = config.allowedBudgetDurations ?? DEFAULT_KEY_BUDGET_DURATIONS;
141
+ const budgetDurationSchema = budgetDurations.length > 0 ? import_zod.z.string().refine(
142
+ (val) => budgetDurations.includes(val),
143
+ {
144
+ message: `budget_duration must be one of: ${budgetDurations.join(", ")}`
145
+ }
146
+ ) : import_zod.z.string().regex(/^\d+(s|m|h|d|mo)$/, {
147
+ message: 'budget_duration must match format <number><unit> (e.g. "30d", "1mo")'
148
+ });
133
149
  return import_zod.z.object({
134
150
  alias: import_zod.z.string().trim().min(1, "alias is required").max(128, "alias must be at most 128 characters"),
135
151
  models: import_zod.z.array(import_zod.z.string()).max(100, "models array is too large").optional(),
136
152
  duration: durationSchema.optional(),
137
153
  max_budget: import_zod.z.number().positive("max_budget must be a positive number").max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`).nullable().optional(),
154
+ budget_duration: budgetDurationSchema.optional(),
138
155
  tpm_limit: import_zod.z.number().int("tpm_limit must be an integer").positive("tpm_limit must be a positive number").max(config.maxTpm, `tpm_limit must not exceed ${config.maxTpm}`).optional(),
139
156
  rpm_limit: import_zod.z.number().int("rpm_limit must be an integer").positive("rpm_limit must be a positive number").max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`).optional(),
140
157
  team_id: import_zod.z.string().optional(),
@@ -155,6 +172,7 @@ function createUpdateKeyInputSchema(config) {
155
172
  }).strict();
156
173
  }
157
174
  var DEFAULT_KEY_DURATIONS = ["1d", "7d", "30d", "90d"];
175
+ var DEFAULT_KEY_BUDGET_DURATIONS = ["1d", "7d", "30d", "1mo"];
158
176
 
159
177
  // src/budgets.ts
160
178
  var BUDGET_WARNING_PCT = 80;
@@ -194,6 +212,7 @@ function isModelAllowed(model, allowed) {
194
212
  ALL_PROXY_MODELS,
195
213
  BUDGET_DANGER_PCT,
196
214
  BUDGET_WARNING_PCT,
215
+ DEFAULT_KEY_BUDGET_DURATIONS,
197
216
  DEFAULT_KEY_DURATIONS,
198
217
  RESERVED_METADATA_KEYS,
199
218
  budgetStatusForPct,
@@ -214,6 +233,7 @@ function isModelAllowed(model, allowed) {
214
233
  litellmTeamManagePermission,
215
234
  litellmTeamMcpManagePermission,
216
235
  litellmTeamMembersManagePermission,
236
+ litellmTeamUsageReadPermission,
217
237
  reservedMetadataKeys
218
238
  });
219
239
  //# sourceMappingURL=index.cjs.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/permissions.ts", "../src/keySchemas.ts", "../src/budgets.ts", "../src/modelAccess.ts"],
4
- "sourcesContent": ["export * from './permissions';\nexport * from './keySchemas';\nexport * from './types';\nexport * from './budgets';\nexport * from './modelAccess';\n", "import { createPermission } from '@backstage/plugin-permission-common';\n\nexport const litellmKeyCreatePermission = createPermission({\n name: 'litellm.key.create',\n attributes: { action: 'create' },\n});\n\nexport const litellmKeyRevokePermission = createPermission({\n name: 'litellm.key.revoke',\n attributes: { action: 'delete' },\n});\n\nexport const litellmKeyManagePermission = createPermission({\n name: 'litellm.key.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmKeyResetSpendPermission = createPermission({\n name: 'litellm.key.resetSpend',\n attributes: { action: 'update' },\n});\n\nexport const litellmKeyUnblockPermission = createPermission({\n name: 'litellm.key.unblock',\n attributes: { action: 'update' },\n});\n\nexport const litellmAuditReadPermission = createPermission({\n name: 'litellm.audit.read',\n attributes: { action: 'read' },\n});\n\n// Team-management permissions \u2014 delegated to litellm-team-admins group\nexport const litellmTeamCreatePermission = createPermission({\n name: 'litellm.team.create',\n attributes: { action: 'create' },\n});\n\nexport const litellmTeamManagePermission = createPermission({\n name: 'litellm.team.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamMembersManagePermission = createPermission({\n name: 'litellm.team.members.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamKnowledgebaseManagePermission = createPermission({\n name: 'litellm.team.knowledgebase.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamMcpManagePermission = createPermission({\n name: 'litellm.team.mcp.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamDeletePermission = createPermission({\n name: 'litellm.team.delete',\n attributes: { action: 'delete' },\n});\n\nexport const litellmPermissions = [\n litellmKeyCreatePermission,\n litellmKeyRevokePermission,\n litellmKeyManagePermission,\n litellmKeyResetSpendPermission,\n litellmKeyUnblockPermission,\n litellmAuditReadPermission,\n litellmTeamCreatePermission,\n litellmTeamManagePermission,\n litellmTeamMembersManagePermission,\n litellmTeamKnowledgebaseManagePermission,\n litellmTeamMcpManagePermission,\n litellmTeamDeletePermission,\n];\n", "import { z } from 'zod';\n\n/**\n * Configuration for key validation ceilings and allowed values.\n */\n/**\n * Metadata keys the server owns. Clients must not set them: `blocked_by` /\n * `blocked_at` decide who may unblock a key, and `created_*` / `updated_*`\n * record provenance.\n */\nexport const RESERVED_METADATA_KEYS = ['blocked_by', 'blocked_at'] as const;\nconst RESERVED_METADATA_PREFIXES = ['created_', 'updated_'] as const;\n\nexport function reservedMetadataKeys(metadata: Record<string, unknown>): string[] {\n return Object.keys(metadata).filter(\n k =>\n (RESERVED_METADATA_KEYS as readonly string[]).includes(k) ||\n RESERVED_METADATA_PREFIXES.some(p => k.startsWith(p)),\n );\n}\n\nexport interface KeyValidationConfig {\n maxBudget: number;\n maxTpm: number;\n maxRpm: number;\n allowedDurations: string[];\n}\n\n/**\n * Creates a strict Zod schema for GenerateKeyInput validation.\n * Rejects unknown fields and enforces server-side ceilings.\n *\n * @param config Server configuration with ceiling values and allowed durations\n * @returns A Zod object schema that validates the input\n */\nexport function createGenerateKeyInputSchema(config: KeyValidationConfig) {\n // When allowedDurations is set, restrict to that list.\n // Otherwise, allow any string matching the duration format regex.\n const durationSchema = config.allowedDurations.length > 0\n ? z.string().refine(\n (val) => config.allowedDurations.includes(val),\n {\n message: `duration must be one of: ${config.allowedDurations.join(', ')}`,\n },\n )\n : z.string().regex(/^\\d+[smhdwy]$/, {\n message: 'duration must match format <number><unit> (e.g. \"30d\", \"1h\")',\n });\n\n return z\n .object({\n alias: z\n .string()\n .trim()\n .min(1, 'alias is required')\n .max(128, 'alias must be at most 128 characters'),\n models: z\n .array(z.string())\n .max(100, 'models array is too large')\n .optional(),\n duration: durationSchema.optional(),\n max_budget: z\n .number()\n .positive('max_budget must be a positive number')\n .max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`)\n .nullable()\n .optional(),\n tpm_limit: z\n .number()\n .int('tpm_limit must be an integer')\n .positive('tpm_limit must be a positive number')\n .max(config.maxTpm, `tpm_limit must not exceed ${config.maxTpm}`)\n .optional(),\n rpm_limit: z\n .number()\n .int('rpm_limit must be an integer')\n .positive('rpm_limit must be a positive number')\n .max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`)\n .optional(),\n team_id: z.string().optional(),\n // The UI always sends 'llm_api'; no other key type may be requested.\n key_type: z.literal('llm_api').optional(),\n metadata: z\n .record(z.string(), z.string())\n .refine(m => reservedMetadataKeys(m).length === 0, {\n message:\n 'metadata may not set server-owned keys (blocked_by, blocked_at, created_*, updated_*)',\n })\n .optional(),\n })\n .strict();\n}\n\nexport type GenerateKeyInput = z.infer<\n ReturnType<typeof createGenerateKeyInputSchema>\n>;\n\n/**\n * Creates a strict Zod schema for UpdateKeyInput validation.\n * Only allows key_alias, models, max_budget, tpm_limit, and rpm_limit fields.\n * Rejects all other fields including team_id, user_id, spend, blocked, key, and budget_duration.\n *\n * @param config Server configuration with ceiling values\n * @returns A Zod object schema that validates the input\n */\nexport function createUpdateKeyInputSchema(config: KeyValidationConfig) {\n return z\n .object({\n key_alias: z\n .string()\n .trim()\n .min(1, 'key_alias is required')\n .max(128, 'key_alias must be at most 128 characters')\n .optional(),\n models: z\n .array(z.string())\n .max(100, 'models array is too large')\n .optional(),\n max_budget: z\n .number()\n .positive('max_budget must be a positive number')\n .max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`)\n .nullable()\n .optional(),\n tpm_limit: z\n .number()\n .int('tpm_limit must be an integer')\n .positive('tpm_limit must be a positive number')\n .max(config.maxTpm, `tpm_limit must not exceed ${config.maxTpm}`)\n .optional(),\n rpm_limit: z\n .number()\n .int('rpm_limit must be an integer')\n .positive('rpm_limit must be a positive number')\n .max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`)\n .optional(),\n })\n .strict();\n}\n\nexport type UpdateKeyInput = z.infer<\n ReturnType<typeof createUpdateKeyInputSchema>\n>;\n\n/**\n * Default key duration presets. Used as server-side defaults and UI options.\n */\nexport const DEFAULT_KEY_DURATIONS = ['1d', '7d', '30d', '90d'];\n", "/** Budget warning threshold (percent of cap). */\nexport const BUDGET_WARNING_PCT = 80;\n\n/** Budget danger threshold (percent of cap, at or over cap). */\nexport const BUDGET_DANGER_PCT = 100;\n\n/** Consumption status derived from spend vs. cap. */\nexport type BudgetStatus = 'ok' | 'near' | 'over';\n\n/**\n * Compute budget status from spend percentage of cap.\n * Thresholds: near >= 80%, over >= 100%.\n */\nexport function budgetStatusForPct(pct: number): BudgetStatus {\n if (pct >= BUDGET_DANGER_PCT) return 'over';\n if (pct >= BUDGET_WARNING_PCT) return 'near';\n return 'ok';\n}\n\n/**\n * Format a duration string to a human-readable label.\n * E.g., \"1d\" \u2192 \"1 Day\", \"7d\" \u2192 \"7 Days\".\n */\nexport function formatDurationLabel(duration: string): string {\n const match = /^(\\d+)([smhdwy])$/.exec(duration);\n if (!match) return duration;\n\n const [, num, unit] = match;\n const n = Number(num);\n\n const unitLabels: Record<string, string> = {\n s: 'Second',\n m: 'Minute',\n h: 'Hour',\n d: 'Day',\n w: 'Week',\n y: 'Year',\n };\n\n const label = unitLabels[unit] || unit;\n return n === 1 ? `1 ${label}` : `${n} ${label}s`;\n}\n", "/**\n * LiteLLM sentinel: a team's (or user's) `models` list is `[\"all-proxy-models\"]`\n * when it isn't restricted to specific models.\n */\nexport const ALL_PROXY_MODELS = 'all-proxy-models';\n\n/**\n * Whether a model is allowed by an allow-list. The list is unrestricted when it\n * is empty/missing or contains the `all-proxy-models` sentinel. Entries may be\n * literal model names OR access-group names (litellm model_info.access_groups),\n * so a model also passes when one of its access groups is listed.\n */\nexport function isModelAllowed(\n model: { model_name: string; access_groups?: string[] },\n allowed?: string[],\n): boolean {\n if (!allowed || allowed.length === 0) return true;\n if (allowed.includes(ALL_PROXY_MODELS)) return true;\n if (allowed.includes(model.model_name)) return true;\n return !!model.access_groups?.some(group => allowed.includes(group));\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export * from './permissions';\nexport * from './keySchemas';\nexport * from './types';\nexport * from './budgets';\nexport * from './modelAccess';\n", "import { createPermission } from '@backstage/plugin-permission-common';\n\nexport const litellmKeyCreatePermission = createPermission({\n name: 'litellm.key.create',\n attributes: { action: 'create' },\n});\n\nexport const litellmKeyRevokePermission = createPermission({\n name: 'litellm.key.revoke',\n attributes: { action: 'delete' },\n});\n\nexport const litellmKeyManagePermission = createPermission({\n name: 'litellm.key.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmKeyResetSpendPermission = createPermission({\n name: 'litellm.key.resetSpend',\n attributes: { action: 'update' },\n});\n\nexport const litellmKeyUnblockPermission = createPermission({\n name: 'litellm.key.unblock',\n attributes: { action: 'update' },\n});\n\nexport const litellmAuditReadPermission = createPermission({\n name: 'litellm.audit.read',\n attributes: { action: 'read' },\n});\n\n// Team-management permissions \u2014 delegated to litellm-team-admins group\nexport const litellmTeamCreatePermission = createPermission({\n name: 'litellm.team.create',\n attributes: { action: 'create' },\n});\n\nexport const litellmTeamManagePermission = createPermission({\n name: 'litellm.team.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamMembersManagePermission = createPermission({\n name: 'litellm.team.members.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamKnowledgebaseManagePermission = createPermission({\n name: 'litellm.team.knowledgebase.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamMcpManagePermission = createPermission({\n name: 'litellm.team.mcp.manage',\n attributes: { action: 'update' },\n});\n\nexport const litellmTeamDeletePermission = createPermission({\n name: 'litellm.team.delete',\n attributes: { action: 'delete' },\n});\n\n/**\n * Read the per-member usage breakdown of a team the caller belongs to.\n * Only consulted when `litellm.teamUsage.memberBreakdown.enabled` is true and\n * the permission framework is enabled.\n */\nexport const litellmTeamUsageReadPermission = createPermission({\n name: 'litellm.team.usage.read',\n attributes: { action: 'read' },\n});\n\nexport const litellmPermissions = [\n litellmKeyCreatePermission,\n litellmKeyRevokePermission,\n litellmKeyManagePermission,\n litellmKeyResetSpendPermission,\n litellmKeyUnblockPermission,\n litellmAuditReadPermission,\n litellmTeamCreatePermission,\n litellmTeamManagePermission,\n litellmTeamMembersManagePermission,\n litellmTeamKnowledgebaseManagePermission,\n litellmTeamMcpManagePermission,\n litellmTeamDeletePermission,\n litellmTeamUsageReadPermission,\n];\n", "import { z } from 'zod';\n\n/**\n * Configuration for key validation ceilings and allowed values.\n */\n/**\n * Metadata keys the server owns. Clients must not set them: `blocked_by` /\n * `blocked_at` decide who may unblock a key, and `created_*` / `updated_*`\n * record provenance.\n */\nexport const RESERVED_METADATA_KEYS = ['blocked_by', 'blocked_at'] as const;\nconst RESERVED_METADATA_PREFIXES = ['created_', 'updated_'] as const;\n\nexport function reservedMetadataKeys(metadata: Record<string, unknown>): string[] {\n return Object.keys(metadata).filter(\n k =>\n (RESERVED_METADATA_KEYS as readonly string[]).includes(k) ||\n RESERVED_METADATA_PREFIXES.some(p => k.startsWith(p)),\n );\n}\n\nexport interface KeyValidationConfig {\n maxBudget: number;\n maxTpm: number;\n maxRpm: number;\n allowedDurations: string[];\n /**\n * Allowed `budget_duration` values (how often a key's spend resets). An\n * empty list allows any value matching `<n><s|m|h|d|mo>`. Omitted means\n * {@link DEFAULT_KEY_BUDGET_DURATIONS}.\n */\n allowedBudgetDurations?: string[];\n}\n\n/**\n * Creates a strict Zod schema for GenerateKeyInput validation.\n * Rejects unknown fields and enforces server-side ceilings.\n *\n * @param config Server configuration with ceiling values and allowed durations\n * @returns A Zod object schema that validates the input\n */\nexport function createGenerateKeyInputSchema(config: KeyValidationConfig) {\n // When allowedDurations is set, restrict to that list.\n // Otherwise, allow any string matching the duration format regex.\n const durationSchema = config.allowedDurations.length > 0\n ? z.string().refine(\n (val) => config.allowedDurations.includes(val),\n {\n message: `duration must be one of: ${config.allowedDurations.join(', ')}`,\n },\n )\n : z.string().regex(/^\\d+[smhdwy]$/, {\n message: 'duration must match format <number><unit> (e.g. \"30d\", \"1h\")',\n });\n\n const budgetDurations = config.allowedBudgetDurations ?? DEFAULT_KEY_BUDGET_DURATIONS;\n const budgetDurationSchema = budgetDurations.length > 0\n ? z.string().refine(\n (val) => budgetDurations.includes(val),\n {\n message: `budget_duration must be one of: ${budgetDurations.join(', ')}`,\n },\n )\n : z.string().regex(/^\\d+(s|m|h|d|mo)$/, {\n message: 'budget_duration must match format <number><unit> (e.g. \"30d\", \"1mo\")',\n });\n\n return z\n .object({\n alias: z\n .string()\n .trim()\n .min(1, 'alias is required')\n .max(128, 'alias must be at most 128 characters'),\n models: z\n .array(z.string())\n .max(100, 'models array is too large')\n .optional(),\n duration: durationSchema.optional(),\n max_budget: z\n .number()\n .positive('max_budget must be a positive number')\n .max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`)\n .nullable()\n .optional(),\n budget_duration: budgetDurationSchema.optional(),\n tpm_limit: z\n .number()\n .int('tpm_limit must be an integer')\n .positive('tpm_limit must be a positive number')\n .max(config.maxTpm, `tpm_limit must not exceed ${config.maxTpm}`)\n .optional(),\n rpm_limit: z\n .number()\n .int('rpm_limit must be an integer')\n .positive('rpm_limit must be a positive number')\n .max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`)\n .optional(),\n team_id: z.string().optional(),\n // The UI always sends 'llm_api'; no other key type may be requested.\n key_type: z.literal('llm_api').optional(),\n metadata: z\n .record(z.string(), z.string())\n .refine(m => reservedMetadataKeys(m).length === 0, {\n message:\n 'metadata may not set server-owned keys (blocked_by, blocked_at, created_*, updated_*)',\n })\n .optional(),\n })\n .strict();\n}\n\nexport type GenerateKeyInput = z.infer<\n ReturnType<typeof createGenerateKeyInputSchema>\n>;\n\n/**\n * Creates a strict Zod schema for UpdateKeyInput validation.\n * Only allows key_alias, models, max_budget, tpm_limit, and rpm_limit fields.\n * Rejects all other fields including team_id, user_id, spend, blocked, key, and budget_duration.\n *\n * @param config Server configuration with ceiling values\n * @returns A Zod object schema that validates the input\n */\nexport function createUpdateKeyInputSchema(config: KeyValidationConfig) {\n return z\n .object({\n key_alias: z\n .string()\n .trim()\n .min(1, 'key_alias is required')\n .max(128, 'key_alias must be at most 128 characters')\n .optional(),\n models: z\n .array(z.string())\n .max(100, 'models array is too large')\n .optional(),\n max_budget: z\n .number()\n .positive('max_budget must be a positive number')\n .max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`)\n .nullable()\n .optional(),\n tpm_limit: z\n .number()\n .int('tpm_limit must be an integer')\n .positive('tpm_limit must be a positive number')\n .max(config.maxTpm, `tpm_limit must not exceed ${config.maxTpm}`)\n .optional(),\n rpm_limit: z\n .number()\n .int('rpm_limit must be an integer')\n .positive('rpm_limit must be a positive number')\n .max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`)\n .optional(),\n })\n .strict();\n}\n\nexport type UpdateKeyInput = z.infer<\n ReturnType<typeof createUpdateKeyInputSchema>\n>;\n\n/**\n * Default key duration presets. Used as server-side defaults and UI options.\n */\nexport const DEFAULT_KEY_DURATIONS = ['1d', '7d', '30d', '90d'];\n\n/**\n * Default `budget_duration` values a key may be generated with.\n */\nexport const DEFAULT_KEY_BUDGET_DURATIONS = ['1d', '7d', '30d', '1mo'];\n", "/** Budget warning threshold (percent of cap). */\nexport const BUDGET_WARNING_PCT = 80;\n\n/** Budget danger threshold (percent of cap, at or over cap). */\nexport const BUDGET_DANGER_PCT = 100;\n\n/** Consumption status derived from spend vs. cap. */\nexport type BudgetStatus = 'ok' | 'near' | 'over';\n\n/**\n * Compute budget status from spend percentage of cap.\n * Thresholds: near >= 80%, over >= 100%.\n */\nexport function budgetStatusForPct(pct: number): BudgetStatus {\n if (pct >= BUDGET_DANGER_PCT) return 'over';\n if (pct >= BUDGET_WARNING_PCT) return 'near';\n return 'ok';\n}\n\n/**\n * Format a duration string to a human-readable label.\n * E.g., \"1d\" \u2192 \"1 Day\", \"7d\" \u2192 \"7 Days\".\n */\nexport function formatDurationLabel(duration: string): string {\n const match = /^(\\d+)([smhdwy])$/.exec(duration);\n if (!match) return duration;\n\n const [, num, unit] = match;\n const n = Number(num);\n\n const unitLabels: Record<string, string> = {\n s: 'Second',\n m: 'Minute',\n h: 'Hour',\n d: 'Day',\n w: 'Week',\n y: 'Year',\n };\n\n const label = unitLabels[unit] || unit;\n return n === 1 ? `1 ${label}` : `${n} ${label}s`;\n}\n", "/**\n * LiteLLM sentinel: a team's (or user's) `models` list is `[\"all-proxy-models\"]`\n * when it isn't restricted to specific models.\n */\nexport const ALL_PROXY_MODELS = 'all-proxy-models';\n\n/**\n * Whether a model is allowed by an allow-list. The list is unrestricted when it\n * is empty/missing or contains the `all-proxy-models` sentinel. Entries may be\n * literal model names OR access-group names (litellm model_info.access_groups),\n * so a model also passes when one of its access groups is listed.\n */\nexport function isModelAllowed(\n model: { model_name: string; access_groups?: string[] },\n allowed?: string[],\n): boolean {\n if (!allowed || allowed.length === 0) return true;\n if (allowed.includes(ALL_PROXY_MODELS)) return true;\n if (allowed.includes(model.model_name)) return true;\n return !!model.access_groups?.some(group => allowed.includes(group));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -14,6 +14,12 @@ export interface KeyValidationConfig {
14
14
  maxTpm: number;
15
15
  maxRpm: number;
16
16
  allowedDurations: string[];
17
+ /**
18
+ * Allowed `budget_duration` values (how often a key's spend resets). An
19
+ * empty list allows any value matching `<n><s|m|h|d|mo>`. Omitted means
20
+ * {@link DEFAULT_KEY_BUDGET_DURATIONS}.
21
+ */
22
+ allowedBudgetDurations?: string[];
17
23
  }
18
24
  /**
19
25
  * Creates a strict Zod schema for GenerateKeyInput validation.
@@ -27,6 +33,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
27
33
  models: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
28
34
  duration: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>> | z.ZodOptional<z.ZodString>;
29
35
  max_budget: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
36
+ budget_duration: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>> | z.ZodOptional<z.ZodString>;
30
37
  tpm_limit: z.ZodOptional<z.ZodNumber>;
31
38
  rpm_limit: z.ZodOptional<z.ZodNumber>;
32
39
  team_id: z.ZodOptional<z.ZodString>;
@@ -37,6 +44,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
37
44
  models?: string[] | undefined;
38
45
  duration?: string | undefined;
39
46
  max_budget?: number | null | undefined;
47
+ budget_duration?: string | undefined;
40
48
  tpm_limit?: number | undefined;
41
49
  rpm_limit?: number | undefined;
42
50
  team_id?: string | undefined;
@@ -47,6 +55,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
47
55
  models?: string[] | undefined;
48
56
  duration?: string | undefined;
49
57
  max_budget?: number | null | undefined;
58
+ budget_duration?: string | undefined;
50
59
  tpm_limit?: number | undefined;
51
60
  rpm_limit?: number | undefined;
52
61
  team_id?: string | undefined;
@@ -86,3 +95,7 @@ export type UpdateKeyInput = z.infer<ReturnType<typeof createUpdateKeyInputSchem
86
95
  * Default key duration presets. Used as server-side defaults and UI options.
87
96
  */
88
97
  export declare const DEFAULT_KEY_DURATIONS: string[];
98
+ /**
99
+ * Default `budget_duration` values a key may be generated with.
100
+ */
101
+ export declare const DEFAULT_KEY_BUDGET_DURATIONS: string[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_KEY_DURATIONS = exports.RESERVED_METADATA_KEYS = void 0;
3
+ exports.DEFAULT_KEY_BUDGET_DURATIONS = exports.DEFAULT_KEY_DURATIONS = exports.RESERVED_METADATA_KEYS = void 0;
4
4
  exports.reservedMetadataKeys = reservedMetadataKeys;
5
5
  exports.createGenerateKeyInputSchema = createGenerateKeyInputSchema;
6
6
  exports.createUpdateKeyInputSchema = createUpdateKeyInputSchema;
@@ -36,6 +36,14 @@ function createGenerateKeyInputSchema(config) {
36
36
  : zod_1.z.string().regex(/^\d+[smhdwy]$/, {
37
37
  message: 'duration must match format <number><unit> (e.g. "30d", "1h")',
38
38
  });
39
+ const budgetDurations = config.allowedBudgetDurations ?? exports.DEFAULT_KEY_BUDGET_DURATIONS;
40
+ const budgetDurationSchema = budgetDurations.length > 0
41
+ ? zod_1.z.string().refine((val) => budgetDurations.includes(val), {
42
+ message: `budget_duration must be one of: ${budgetDurations.join(', ')}`,
43
+ })
44
+ : zod_1.z.string().regex(/^\d+(s|m|h|d|mo)$/, {
45
+ message: 'budget_duration must match format <number><unit> (e.g. "30d", "1mo")',
46
+ });
39
47
  return zod_1.z
40
48
  .object({
41
49
  alias: zod_1.z
@@ -54,6 +62,7 @@ function createGenerateKeyInputSchema(config) {
54
62
  .max(config.maxBudget, `max_budget must not exceed ${config.maxBudget}`)
55
63
  .nullable()
56
64
  .optional(),
65
+ budget_duration: budgetDurationSchema.optional(),
57
66
  tpm_limit: zod_1.z
58
67
  .number()
59
68
  .int('tpm_limit must be an integer')
@@ -124,3 +133,7 @@ function createUpdateKeyInputSchema(config) {
124
133
  * Default key duration presets. Used as server-side defaults and UI options.
125
134
  */
126
135
  exports.DEFAULT_KEY_DURATIONS = ['1d', '7d', '30d', '90d'];
136
+ /**
137
+ * Default `budget_duration` values a key may be generated with.
138
+ */
139
+ exports.DEFAULT_KEY_BUDGET_DURATIONS = ['1d', '7d', '30d', '1mo'];
@@ -10,4 +10,10 @@ export declare const litellmTeamMembersManagePermission: import("@backstage/plug
10
10
  export declare const litellmTeamKnowledgebaseManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
11
11
  export declare const litellmTeamMcpManagePermission: import("@backstage/plugin-permission-common").BasicPermission;
12
12
  export declare const litellmTeamDeletePermission: import("@backstage/plugin-permission-common").BasicPermission;
13
+ /**
14
+ * Read the per-member usage breakdown of a team the caller belongs to.
15
+ * Only consulted when `litellm.teamUsage.memberBreakdown.enabled` is true and
16
+ * the permission framework is enabled.
17
+ */
18
+ export declare const litellmTeamUsageReadPermission: import("@backstage/plugin-permission-common").BasicPermission;
13
19
  export declare const litellmPermissions: import("@backstage/plugin-permission-common").BasicPermission[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
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.litellmKeyUnblockPermission = exports.litellmKeyResetSpendPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = void 0;
3
+ exports.litellmPermissions = exports.litellmTeamUsageReadPermission = exports.litellmTeamDeletePermission = exports.litellmTeamMcpManagePermission = exports.litellmTeamKnowledgebaseManagePermission = exports.litellmTeamMembersManagePermission = exports.litellmTeamManagePermission = exports.litellmTeamCreatePermission = exports.litellmAuditReadPermission = exports.litellmKeyUnblockPermission = exports.litellmKeyResetSpendPermission = exports.litellmKeyManagePermission = exports.litellmKeyRevokePermission = exports.litellmKeyCreatePermission = void 0;
4
4
  const plugin_permission_common_1 = require("@backstage/plugin-permission-common");
5
5
  exports.litellmKeyCreatePermission = (0, plugin_permission_common_1.createPermission)({
6
6
  name: 'litellm.key.create',
@@ -51,6 +51,15 @@ exports.litellmTeamDeletePermission = (0, plugin_permission_common_1.createPermi
51
51
  name: 'litellm.team.delete',
52
52
  attributes: { action: 'delete' },
53
53
  });
54
+ /**
55
+ * Read the per-member usage breakdown of a team the caller belongs to.
56
+ * Only consulted when `litellm.teamUsage.memberBreakdown.enabled` is true and
57
+ * the permission framework is enabled.
58
+ */
59
+ exports.litellmTeamUsageReadPermission = (0, plugin_permission_common_1.createPermission)({
60
+ name: 'litellm.team.usage.read',
61
+ attributes: { action: 'read' },
62
+ });
54
63
  exports.litellmPermissions = [
55
64
  exports.litellmKeyCreatePermission,
56
65
  exports.litellmKeyRevokePermission,
@@ -64,4 +73,5 @@ exports.litellmPermissions = [
64
73
  exports.litellmTeamKnowledgebaseManagePermission,
65
74
  exports.litellmTeamMcpManagePermission,
66
75
  exports.litellmTeamDeletePermission,
76
+ exports.litellmTeamUsageReadPermission,
67
77
  ];
package/dist/types.d.ts CHANGED
@@ -145,6 +145,40 @@ export interface UsageMetrics {
145
145
  daily_usage: UsageDailyPoint[];
146
146
  daily_by_model: UsageDailyModelPoint[];
147
147
  }
148
+ /** One member's share of a team's usage over a period. */
149
+ export interface TeamMemberUsageRow {
150
+ /** LiteLLM user id; null groups activity no current key owner accounts for (deleted or ownerless keys). */
151
+ user_id: string | null;
152
+ user_email?: string;
153
+ /** From the Backstage catalog when a User entity has the member's email. */
154
+ display_name?: string;
155
+ /** LiteLLM team role, when the user is a team member. */
156
+ role?: string;
157
+ /** USD; 0 when `budget_hidden` is set on the response. */
158
+ spend: number;
159
+ /** Share of the team's spend in the period, 0–100. */
160
+ spend_share_pct: number;
161
+ prompt_tokens: number;
162
+ completion_tokens: number;
163
+ total_tokens: number;
164
+ api_requests: number;
165
+ successful_requests: number;
166
+ failed_requests: number;
167
+ /** successful / (successful + failed) as 0–100; null without requests. */
168
+ success_rate: number | null;
169
+ /** Team keys the user owns. */
170
+ key_count: number;
171
+ }
172
+ /** Response of `GET /teams/:teamId/usage/members`. */
173
+ export interface TeamMemberUsage {
174
+ team_id: string;
175
+ /** USD; 0 when `budget_hidden` is set. */
176
+ total_spend: number;
177
+ /** Dollars are hidden for the caller: compare members by `spend_share_pct`. */
178
+ budget_hidden?: boolean;
179
+ /** Sorted by spend, highest first. */
180
+ members: TeamMemberUsageRow[];
181
+ }
148
182
  export interface GenerateKeyRequest {
149
183
  alias?: string;
150
184
  models?: string[];
@@ -152,6 +186,8 @@ export interface GenerateKeyRequest {
152
186
  duration?: string;
153
187
  /** Positive number caps spend; null/undefined means unlimited. */
154
188
  max_budget?: number | null;
189
+ /** How often the key's spend resets, e.g. "30d" or "1mo". */
190
+ budget_duration?: string;
155
191
  tpm_limit?: number;
156
192
  rpm_limit?: number;
157
193
  key_type?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-common",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared types, permissions, and schemas for the LiteLLM Backstage plugin",
5
5
  "backstage": {
6
6
  "role": "common-library",
@@ -29,8 +29,7 @@
29
29
  "scripts": {
30
30
  "build": "node build.js && tsc -p tsconfig.json",
31
31
  "test": "tsc -p tsconfig.test.json && node --test \"dist-test/**/*.test.js\"",
32
- "prepack": "npm run build",
33
- "postpack": ""
32
+ "prepack": "npm run build"
34
33
  },
35
34
  "dependencies": {
36
35
  "@backstage/plugin-permission-common": "^0.9.10",