@acarmisc/backstage-plugin-litellm-backend 0.17.2 → 0.17.4
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 +68 -26
- package/dist/index.cjs.js.map +3 -3
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +13 -6
- package/dist/routes/bridge.js +36 -4
- package/package.json +1 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge routes exempt from Backstage's default auth policy (they verify the
|
|
3
|
+
* Keycloak JWT themselves). Every route registered in routes/bridge.ts must be
|
|
4
|
+
* listed here, or the framework answers 401 before the bridge verifier runs.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BRIDGE_UNAUTHENTICATED_PATHS: string[];
|
|
1
7
|
export declare const litellmPlugin: import("@backstage/backend-plugin-api").BackendFeature;
|
package/dist/plugin.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.litellmPlugin = void 0;
|
|
3
|
+
exports.litellmPlugin = exports.BRIDGE_UNAUTHENTICATED_PATHS = void 0;
|
|
4
4
|
const backend_plugin_api_1 = require("@backstage/backend-plugin-api");
|
|
5
5
|
const router_1 = require("./router");
|
|
6
6
|
const bridge_1 = require("./bridge");
|
|
7
7
|
const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
8
|
+
/**
|
|
9
|
+
* Bridge routes exempt from Backstage's default auth policy (they verify the
|
|
10
|
+
* Keycloak JWT themselves). Every route registered in routes/bridge.ts must be
|
|
11
|
+
* listed here, or the framework answers 401 before the bridge verifier runs.
|
|
12
|
+
*/
|
|
13
|
+
exports.BRIDGE_UNAUTHENTICATED_PATHS = [
|
|
14
|
+
'/bridge/health',
|
|
15
|
+
'/bridge/keys',
|
|
16
|
+
'/bridge/models',
|
|
17
|
+
'/bridge/user/info',
|
|
18
|
+
];
|
|
8
19
|
exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
|
|
9
20
|
pluginId: 'litellm',
|
|
10
21
|
register(reg) {
|
|
@@ -33,11 +44,7 @@ exports.litellmPlugin = (0, backend_plugin_api_1.createBackendPlugin)({
|
|
|
33
44
|
// policy, otherwise the framework rejects the Keycloak Bearer with
|
|
34
45
|
// "Illegal token" before the bridge verifier ever runs.
|
|
35
46
|
if ((0, bridge_1.readBridgeConfig)(config).enabled) {
|
|
36
|
-
for (const path of
|
|
37
|
-
'/bridge/health',
|
|
38
|
-
'/bridge/keys',
|
|
39
|
-
'/bridge/models',
|
|
40
|
-
]) {
|
|
47
|
+
for (const path of exports.BRIDGE_UNAUTHENTICATED_PATHS) {
|
|
41
48
|
httpRouter.addAuthPolicy({ path, allow: 'unauthenticated' });
|
|
42
49
|
}
|
|
43
50
|
}
|
package/dist/routes/bridge.js
CHANGED
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerBridgeRoutes = registerBridgeRoutes;
|
|
4
4
|
const provisioning_1 = require("../provisioning");
|
|
5
5
|
const bridge_1 = require("../bridge");
|
|
6
|
+
const backstage_plugin_litellm_common_1 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
6
7
|
const keyService_1 = require("../services/keyService");
|
|
7
8
|
const errors_1 = require("../errors");
|
|
8
|
-
const
|
|
9
|
+
const backstage_plugin_litellm_common_2 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
9
10
|
function registerBridgeRoutes(router, ctx, bridgeOpts) {
|
|
10
11
|
const { client, logger, userIdDomain, provisioningEnabled, provisioningDefaults, roleConfigs, allowUnlimitedBudget, teamRequired, keyValidationConfig, } = ctx;
|
|
11
12
|
const { config } = bridgeOpts;
|
|
12
|
-
const generateKeyInputSchema = (0,
|
|
13
|
+
const generateKeyInputSchema = (0, backstage_plugin_litellm_common_2.createGenerateKeyInputSchema)(ctx.keyValidationConfig);
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Bridge endpoints (CLI / Abby)
|
|
15
16
|
//
|
|
@@ -125,11 +126,42 @@ function registerBridgeRoutes(router, ctx, bridgeOpts) {
|
|
|
125
126
|
handleBridgeError(error, res);
|
|
126
127
|
}
|
|
127
128
|
});
|
|
129
|
+
// Caller's LiteLLM identity: user id + team memberships. Lets CLI clients
|
|
130
|
+
// offer a team picker and mint keys under a valid team.
|
|
131
|
+
router.get('/bridge/user/info', async (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
const claims = await requireClaims(req);
|
|
134
|
+
const user = await (0, bridge_1.getOrProvisionUserFromClaims)(client, claims, provisioningEnabled, provisioningDefaults, logger, bridgeIdentity);
|
|
135
|
+
res.json({ user_id: user.user_id, teams: user.teams ?? [] });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
handleBridgeError(error, res);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Model catalogue. With ?team_id=X the list is narrowed to the models the
|
|
142
|
+
// team may use (its `models` plus access groups); the caller must belong
|
|
143
|
+
// to that team. Without it the full catalogue is returned, as before.
|
|
128
144
|
router.get('/bridge/models', async (req, res) => {
|
|
129
145
|
try {
|
|
130
|
-
await requireClaims(req);
|
|
146
|
+
const claims = await requireClaims(req);
|
|
147
|
+
const teamId = typeof req.query.team_id === 'string' ? req.query.team_id : '';
|
|
131
148
|
const models = await client.listModels();
|
|
132
|
-
|
|
149
|
+
if (!teamId) {
|
|
150
|
+
res.json(models);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const user = await (0, bridge_1.getOrProvisionUserFromClaims)(client, claims, provisioningEnabled, provisioningDefaults, logger, bridgeIdentity);
|
|
154
|
+
if (!(user.teams ?? []).includes(teamId)) {
|
|
155
|
+
res.status(403).json({ error: 'Access denied: team is not one of your teams', team_id: teamId });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const team = await client.getTeamInfo(teamId);
|
|
159
|
+
const allowed = team.models;
|
|
160
|
+
if (!allowed || allowed.length === 0 || allowed.includes(backstage_plugin_litellm_common_1.ALL_PROXY_MODELS)) {
|
|
161
|
+
res.json(models);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
res.json(models.filter(m => (0, backstage_plugin_litellm_common_1.isModelAllowed)(m, allowed)));
|
|
133
165
|
}
|
|
134
166
|
catch (error) {
|
|
135
167
|
handleBridgeError(error, res);
|