@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 +0 -10
- package/dist/bridge.js +0 -18
- package/dist/client.d.ts +0 -7
- package/dist/client.js +0 -9
- package/dist/index.cjs.js +8 -92
- package/dist/index.cjs.js.map +2 -2
- package/dist/openapi.js +0 -18
- package/dist/router.js +20 -37
- package/dist/types.cjs.js.map +1 -1
- package/dist/types.d.ts +1 -4
- package/package.json +1 -1
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);
|