@acarmisc/backstage-plugin-litellm-backend 0.6.0 → 0.6.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.
package/dist/bridge.d.ts CHANGED
@@ -75,13 +75,3 @@ export declare function bridgeListKeys(client: LiteLLMClient, claims: BridgeClai
75
75
  export declare function bridgeGenerateKey(client: LiteLLMClient, claims: BridgeClaims, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, logger: {
76
76
  info: (...args: unknown[]) => void;
77
77
  }, request: Partial<GenerateKeyRequest>, userIdDomain?: string): Promise<GenerateKeyResponse>;
78
- /**
79
- * Rotates the caller's key identified by alias, returning a fresh secret. The
80
- * bridge can't recover a usable secret from a listing (it's masked), so a CLI
81
- * that lost its cached key but whose stable alias still exists uses this to
82
- * rotate in place instead of minting a duplicate. Throws 404 when no key with
83
- * that alias exists for the caller — the CLI then mints one.
84
- */
85
- export declare function bridgeRegenerateKey(client: LiteLLMClient, claims: BridgeClaims, provisioningEnabled: boolean, provisioningDefaults: ProvisioningDefaults, logger: {
86
- info: (...args: unknown[]) => void;
87
- }, alias: string, userIdDomain?: string): Promise<GenerateKeyResponse>;
package/dist/bridge.js CHANGED
@@ -7,7 +7,6 @@ exports.resolveBridgeUserId = resolveBridgeUserId;
7
7
  exports.getOrProvisionUserFromClaims = getOrProvisionUserFromClaims;
8
8
  exports.bridgeListKeys = bridgeListKeys;
9
9
  exports.bridgeGenerateKey = bridgeGenerateKey;
10
- exports.bridgeRegenerateKey = bridgeRegenerateKey;
11
10
  /**
12
11
  * Bridge — lets CLI clients (Abby) list/mint LiteLLM virtual keys without ever
13
12
  * holding the LiteLLM master key.
@@ -153,20 +152,3 @@ async function bridgeGenerateKey(client, claims, provisioningEnabled, provisioni
153
152
  };
154
153
  return client.generateKey(enriched);
155
154
  }
156
- /**
157
- * Rotates the caller's key identified by alias, returning a fresh secret. The
158
- * bridge can't recover a usable secret from a listing (it's masked), so a CLI
159
- * that lost its cached key but whose stable alias still exists uses this to
160
- * rotate in place instead of minting a duplicate. Throws 404 when no key with
161
- * that alias exists for the caller — the CLI then mints one.
162
- */
163
- async function bridgeRegenerateKey(client, claims, provisioningEnabled, provisioningDefaults, logger, alias, userIdDomain) {
164
- await getOrProvisionUserFromClaims(client, claims, provisioningEnabled, provisioningDefaults, logger, userIdDomain);
165
- const userId = resolveBridgeUserId(claims, userIdDomain);
166
- const keys = await client.listKeys(userId);
167
- const match = keys.find(k => k.key_alias === alias);
168
- if (!match) {
169
- throw new provisioning_1.ProvisioningError('Key not found', `No key with alias "${alias}" for this identity; mint one first.`, false, 404);
170
- }
171
- return client.regenerateKey(match.token);
172
- }
package/dist/client.d.ts CHANGED
@@ -57,13 +57,6 @@ export declare class LiteLLMClient {
57
57
  unblockKey(key: string): Promise<unknown>;
58
58
  resetKeySpend(key: string): Promise<unknown>;
59
59
  getAuditLogs(params: AuditLogsParams): Promise<PaginatedAuditLogs>;
60
- /**
61
- * Rotates an existing key in place, returning a fresh `sk-` secret while
62
- * keeping the same alias/budget/limits. `token` is the hashed token LiteLLM
63
- * stores (the same value used for delete/update) — passed in the path because
64
- * it isn't an `sk-` prefixed secret, so LiteLLM matches it without re-hashing.
65
- */
66
- regenerateKey(token: string): Promise<GenerateKeyResponse>;
67
60
  /**
68
61
  * Returns the proxy's model catalogue normalised to the ModelInfo shape.
69
62
  *
package/dist/client.js CHANGED
@@ -209,15 +209,6 @@ class LiteLLMClient {
209
209
  query.set('sort_order', params.sort_order);
210
210
  return this.request(`/audit?${query.toString()}`);
211
211
  }
212
- /**
213
- * Rotates an existing key in place, returning a fresh `sk-` secret while
214
- * keeping the same alias/budget/limits. `token` is the hashed token LiteLLM
215
- * stores (the same value used for delete/update) — passed in the path because
216
- * it isn't an `sk-` prefixed secret, so LiteLLM matches it without re-hashing.
217
- */
218
- async regenerateKey(token) {
219
- return this.request(`/key/${encodeURIComponent(token)}/regenerate`, { method: 'POST', body: JSON.stringify({}) });
220
- }
221
212
  /**
222
213
  * Returns the proxy's model catalogue normalised to the ModelInfo shape.
223
214
  *
package/dist/index.cjs.js CHANGED
@@ -251,18 +251,6 @@ var LiteLLMClient = class {
251
251
  if (params.sort_order) query.set("sort_order", params.sort_order);
252
252
  return this.request(`/audit?${query.toString()}`);
253
253
  }
254
- /**
255
- * Rotates an existing key in place, returning a fresh `sk-` secret while
256
- * keeping the same alias/budget/limits. `token` is the hashed token LiteLLM
257
- * stores (the same value used for delete/update) — passed in the path because
258
- * it isn't an `sk-` prefixed secret, so LiteLLM matches it without re-hashing.
259
- */
260
- async regenerateKey(token) {
261
- return this.request(
262
- `/key/${encodeURIComponent(token)}/regenerate`,
263
- { method: "POST", body: JSON.stringify({}) }
264
- );
265
- }
266
254
  /**
267
255
  * Returns the proxy's model catalogue normalised to the ModelInfo shape.
268
256
  *
@@ -571,9 +559,7 @@ var openApiSpec = {
571
559
  duration: { type: "string" },
572
560
  max_budget: { type: "number", nullable: true, description: "Positive number caps spend; null = unlimited" },
573
561
  tpm_limit: { type: "number" },
574
- rpm_limit: { type: "number" },
575
- auto_rotate: { type: "boolean" },
576
- rotation_interval_days: { type: "number" }
562
+ rpm_limit: { type: "number" }
577
563
  } } } }
578
564
  },
579
565
  responses: {
@@ -590,14 +576,6 @@ var openApiSpec = {
590
576
  responses: { "200": { description: "Deleted" }, "403": { description: "Not the key owner" } }
591
577
  }
592
578
  },
593
- "/keys/{keyId}/regenerate": {
594
- post: {
595
- tags: ["Keys"],
596
- summary: "Rotate a key in place (caller must own it)",
597
- parameters: [{ name: "keyId", in: "path", required: true, schema: { type: "string" } }],
598
- responses: { "200": { description: "New secret" }, "403": { description: "Not the key owner" } }
599
- }
600
- },
601
579
  "/keys/{keyId}/update": {
602
580
  post: {
603
581
  tags: ["Keys"],
@@ -718,14 +696,6 @@ var openApiSpec = {
718
696
  responses: { "200": { description: "Generated key" }, "401": { description: "Invalid token" } }
719
697
  }
720
698
  },
721
- "/bridge/keys/regenerate": {
722
- post: {
723
- tags: ["Bridge"],
724
- summary: "Rotate the caller key by alias (Keycloak token auth)",
725
- requestBody: { content: { "application/json": { schema: { type: "object", properties: { alias: { type: "string" } } } } } },
726
- responses: { "200": { description: "New secret" }, "401": { description: "Invalid token" }, "404": { description: "No key with that alias" } }
727
- }
728
- },
729
699
  "/bridge/models": {
730
700
  get: {
731
701
  tags: ["Bridge"],
@@ -2483,28 +2453,6 @@ async function bridgeGenerateKey(client, claims, provisioningEnabled, provisioni
2483
2453
  };
2484
2454
  return client.generateKey(enriched);
2485
2455
  }
2486
- async function bridgeRegenerateKey(client, claims, provisioningEnabled, provisioningDefaults, logger, alias, userIdDomain) {
2487
- await getOrProvisionUserFromClaims(
2488
- client,
2489
- claims,
2490
- provisioningEnabled,
2491
- provisioningDefaults,
2492
- logger,
2493
- userIdDomain
2494
- );
2495
- const userId = resolveBridgeUserId(claims, userIdDomain);
2496
- const keys = await client.listKeys(userId);
2497
- const match = keys.find((k) => k.key_alias === alias);
2498
- if (!match) {
2499
- throw new ProvisioningError(
2500
- "Key not found",
2501
- `No key with alias "${alias}" for this identity; mint one first.`,
2502
- false,
2503
- 404
2504
- );
2505
- }
2506
- return client.regenerateKey(match.token);
2507
- }
2508
2456
 
2509
2457
  // src/router.ts
2510
2458
  async function createRouter(options) {
@@ -2660,23 +2608,6 @@ async function createRouter(options) {
2660
2608
  }
2661
2609
  return false;
2662
2610
  }
2663
- router.post("/keys/:keyId/regenerate", async (req, res) => {
2664
- try {
2665
- const { keyId } = req.params;
2666
- if (!keyId) {
2667
- res.status(400).json({ error: "keyId is required" });
2668
- return;
2669
- }
2670
- const { tokenEntityRef } = await authorizeKeyAction(req, keyId);
2671
- const result = await client.regenerateKey(keyId);
2672
- logger.info({ action: "key.rotate", userId: tokenEntityRef ?? "unknown", keyId });
2673
- res.json(result);
2674
- } catch (error) {
2675
- if (sendOwnershipError(error, res)) return;
2676
- logger.error("Failed to rotate key", error);
2677
- res.status(500).json({ error: error.message });
2678
- }
2679
- });
2680
2611
  router.post("/keys/generate", async (req, res) => {
2681
2612
  try {
2682
2613
  const body = req.body ?? {};
@@ -2731,6 +2662,13 @@ async function createRouter(options) {
2731
2662
  res.status(error.status).json(error.body);
2732
2663
  return;
2733
2664
  }
2665
+ if (typeof error.message === "string" && error.message.includes("Invalid duration format") && req.body?.team_id) {
2666
+ res.status(502).json({
2667
+ error: 'LiteLLM rejected the key duration for this team. The team has a "Team Member Key Duration" set in LiteLLM that is not in a valid <number><unit> format (e.g. "30d") and overrides whatever duration is requested. Fix or clear it in LiteLLM under Teams \u2192 this team \u2192 Team Settings, then retry.',
2668
+ teamId: req.body.team_id
2669
+ });
2670
+ return;
2671
+ }
2734
2672
  logger.error("Failed to generate key", error);
2735
2673
  res.status(500).json({ error: error.message });
2736
2674
  }
@@ -3015,28 +2953,6 @@ async function createRouter(options) {
3015
2953
  handleBridgeError(error, res);
3016
2954
  }
3017
2955
  });
3018
- router.post("/bridge/keys/regenerate", async (req, res) => {
3019
- try {
3020
- const claims = await requireClaims(req);
3021
- const alias = (req.body ?? {}).alias?.trim();
3022
- if (!alias) {
3023
- res.status(400).json({ error: "alias is required" });
3024
- return;
3025
- }
3026
- const result = await bridgeRegenerateKey(
3027
- client,
3028
- claims,
3029
- provisioningEnabled,
3030
- provisioningDefaults,
3031
- logger,
3032
- alias,
3033
- userIdDomain
3034
- );
3035
- res.json(result);
3036
- } catch (error) {
3037
- handleBridgeError(error, res);
3038
- }
3039
- });
3040
2956
  router.get("/bridge/models", async (req, res) => {
3041
2957
  try {
3042
2958
  await requireClaims(req);