@acarmisc/backstage-plugin-litellm-common 0.4.0 → 0.6.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/dist/index.cjs.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  BUDGET_WARNING_PCT: () => BUDGET_WARNING_PCT,
26
26
  DEFAULT_KEY_BUDGET_DURATIONS: () => DEFAULT_KEY_BUDGET_DURATIONS,
27
27
  DEFAULT_KEY_DURATIONS: () => DEFAULT_KEY_DURATIONS,
28
+ MCP_SERVER_NAME_RE: () => MCP_SERVER_NAME_RE,
28
29
  RESERVED_METADATA_KEYS: () => RESERVED_METADATA_KEYS,
29
30
  budgetStatusForPct: () => budgetStatusForPct,
30
31
  createGenerateKeyInputSchema: () => createGenerateKeyInputSchema,
@@ -128,6 +129,7 @@ function reservedMetadataKeys(metadata) {
128
129
  (k) => RESERVED_METADATA_KEYS.includes(k) || RESERVED_METADATA_PREFIXES.some((p) => k.startsWith(p))
129
130
  );
130
131
  }
132
+ var MCP_SERVER_NAME_RE = /^[A-Za-z0-9._-]+$/;
131
133
  function createGenerateKeyInputSchema(config) {
132
134
  const durationSchema = config.allowedDurations.length > 0 ? import_zod.z.string().refine(
133
135
  (val) => config.allowedDurations.includes(val),
@@ -155,6 +157,12 @@ function createGenerateKeyInputSchema(config) {
155
157
  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(),
156
158
  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(),
157
159
  team_id: import_zod.z.string().optional(),
160
+ // MCP servers the key may use; must be a subset of the team's (checked
161
+ // server-side). [] denies every MCP server; omitted leaves LiteLLM's
162
+ // default. Names only, so the value is safe to forward as-is.
163
+ mcp_servers: import_zod.z.array(
164
+ import_zod.z.string().min(1).max(128).regex(MCP_SERVER_NAME_RE, 'mcp_servers entries may only use letters, digits, ".", "_" and "-"')
165
+ ).max(50, "mcp_servers array is too large").optional(),
158
166
  // The UI always sends 'llm_api'; no other key type may be requested.
159
167
  key_type: import_zod.z.literal("llm_api").optional(),
160
168
  metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).refine((m) => reservedMetadataKeys(m).length === 0, {
@@ -214,6 +222,7 @@ function isModelAllowed(model, allowed) {
214
222
  BUDGET_WARNING_PCT,
215
223
  DEFAULT_KEY_BUDGET_DURATIONS,
216
224
  DEFAULT_KEY_DURATIONS,
225
+ MCP_SERVER_NAME_RE,
217
226
  RESERVED_METADATA_KEYS,
218
227
  budgetStatusForPct,
219
228
  createGenerateKeyInputSchema,
@@ -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\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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",
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/** Allowed characters in an MCP server name sent to or listed by the bridge. */\nexport const MCP_SERVER_NAME_RE = /^[A-Za-z0-9._-]+$/;\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 // MCP servers the key may use; must be a subset of the team's (checked\n // server-side). [] denies every MCP server; omitted leaves LiteLLM's\n // default. Names only, so the value is safe to forward as-is.\n mcp_servers: z\n .array(\n z\n .string()\n .min(1)\n .max(128)\n .regex(MCP_SERVER_NAME_RE, 'mcp_servers entries may only use letters, digits, \".\", \"_\" and \"-\"'),\n )\n .max(50, 'mcp_servers array is too large')\n .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
  }
@@ -21,6 +21,8 @@ export interface KeyValidationConfig {
21
21
  */
22
22
  allowedBudgetDurations?: string[];
23
23
  }
24
+ /** Allowed characters in an MCP server name sent to or listed by the bridge. */
25
+ export declare const MCP_SERVER_NAME_RE: RegExp;
24
26
  /**
25
27
  * Creates a strict Zod schema for GenerateKeyInput validation.
26
28
  * Rejects unknown fields and enforces server-side ceilings.
@@ -37,6 +39,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
37
39
  tpm_limit: z.ZodOptional<z.ZodNumber>;
38
40
  rpm_limit: z.ZodOptional<z.ZodNumber>;
39
41
  team_id: z.ZodOptional<z.ZodString>;
42
+ mcp_servers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
40
43
  key_type: z.ZodOptional<z.ZodLiteral<"llm_api">>;
41
44
  metadata: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodString>, Record<string, string>, Record<string, string>>>;
42
45
  }, "strict", z.ZodTypeAny, {
@@ -48,6 +51,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
48
51
  tpm_limit?: number | undefined;
49
52
  rpm_limit?: number | undefined;
50
53
  team_id?: string | undefined;
54
+ mcp_servers?: string[] | undefined;
51
55
  key_type?: "llm_api" | undefined;
52
56
  metadata?: Record<string, string> | undefined;
53
57
  }, {
@@ -59,6 +63,7 @@ export declare function createGenerateKeyInputSchema(config: KeyValidationConfig
59
63
  tpm_limit?: number | undefined;
60
64
  rpm_limit?: number | undefined;
61
65
  team_id?: string | undefined;
66
+ mcp_servers?: string[] | undefined;
62
67
  key_type?: "llm_api" | undefined;
63
68
  metadata?: Record<string, string> | undefined;
64
69
  }>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_KEY_BUDGET_DURATIONS = exports.DEFAULT_KEY_DURATIONS = exports.RESERVED_METADATA_KEYS = void 0;
3
+ exports.DEFAULT_KEY_BUDGET_DURATIONS = exports.DEFAULT_KEY_DURATIONS = exports.MCP_SERVER_NAME_RE = exports.RESERVED_METADATA_KEYS = void 0;
4
4
  exports.reservedMetadataKeys = reservedMetadataKeys;
5
5
  exports.createGenerateKeyInputSchema = createGenerateKeyInputSchema;
6
6
  exports.createUpdateKeyInputSchema = createUpdateKeyInputSchema;
@@ -19,6 +19,8 @@ function reservedMetadataKeys(metadata) {
19
19
  return Object.keys(metadata).filter(k => exports.RESERVED_METADATA_KEYS.includes(k) ||
20
20
  RESERVED_METADATA_PREFIXES.some(p => k.startsWith(p)));
21
21
  }
22
+ /** Allowed characters in an MCP server name sent to or listed by the bridge. */
23
+ exports.MCP_SERVER_NAME_RE = /^[A-Za-z0-9._-]+$/;
22
24
  /**
23
25
  * Creates a strict Zod schema for GenerateKeyInput validation.
24
26
  * Rejects unknown fields and enforces server-side ceilings.
@@ -76,6 +78,17 @@ function createGenerateKeyInputSchema(config) {
76
78
  .max(config.maxRpm, `rpm_limit must not exceed ${config.maxRpm}`)
77
79
  .optional(),
78
80
  team_id: zod_1.z.string().optional(),
81
+ // MCP servers the key may use; must be a subset of the team's (checked
82
+ // server-side). [] denies every MCP server; omitted leaves LiteLLM's
83
+ // default. Names only, so the value is safe to forward as-is.
84
+ mcp_servers: zod_1.z
85
+ .array(zod_1.z
86
+ .string()
87
+ .min(1)
88
+ .max(128)
89
+ .regex(exports.MCP_SERVER_NAME_RE, 'mcp_servers entries may only use letters, digits, ".", "_" and "-"'))
90
+ .max(50, 'mcp_servers array is too large')
91
+ .optional(),
79
92
  // The UI always sends 'llm_api'; no other key type may be requested.
80
93
  key_type: zod_1.z.literal('llm_api').optional(),
81
94
  metadata: zod_1.z
package/dist/types.d.ts CHANGED
@@ -179,6 +179,37 @@ export interface TeamMemberUsage {
179
179
  /** Sorted by spend, highest first. */
180
180
  members: TeamMemberUsageRow[];
181
181
  }
182
+ /** One team key with its usage over a period. Never carries the key secret, hash or metadata. */
183
+ export interface TeamKeyUsageRow {
184
+ /** Masked key (e.g. `sk-...abcd`). */
185
+ key: string;
186
+ key_alias?: string;
187
+ /** LiteLLM user id of the owner; null for ownerless keys. */
188
+ user_id: string | null;
189
+ user_email?: string;
190
+ display_name?: string;
191
+ created_at: string;
192
+ expires_at?: string;
193
+ blocked: boolean;
194
+ /** USD; omitted when `budget_hidden` is set on the response. */
195
+ max_budget?: number;
196
+ /** USD in the period; 0 when `budget_hidden` is set. */
197
+ spend: number;
198
+ prompt_tokens: number;
199
+ completion_tokens: number;
200
+ total_tokens: number;
201
+ api_requests: number;
202
+ successful_requests: number;
203
+ failed_requests: number;
204
+ }
205
+ /** Response of `GET /teams/:teamId/usage/keys`. */
206
+ export interface TeamKeyUsage {
207
+ team_id: string;
208
+ /** Dollars are hidden for the caller. */
209
+ budget_hidden?: boolean;
210
+ /** Sorted by spend, then tokens. */
211
+ keys: TeamKeyUsageRow[];
212
+ }
182
213
  export interface GenerateKeyRequest {
183
214
  alias?: string;
184
215
  models?: string[];
@@ -192,6 +223,10 @@ export interface GenerateKeyRequest {
192
223
  rpm_limit?: number;
193
224
  key_type?: string;
194
225
  metadata?: Record<string, string>;
226
+ /** Backend-only: MCP permissions forwarded to LiteLLM, built from the validated `mcp_servers`. */
227
+ object_permission?: {
228
+ mcp_servers?: string[];
229
+ };
195
230
  /** Backend-only: user ID for the key owner (set from authenticated principal). */
196
231
  user_id?: string;
197
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-common",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared types, permissions, and schemas for the LiteLLM Backstage plugin",
5
5
  "backstage": {
6
6
  "role": "common-library",