@acarmisc/backstage-plugin-litellm-backend 0.20.0 → 0.22.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/config.d.ts +6 -0
- package/dist/index.cjs.js +13 -6
- package/dist/index.cjs.js.map +2 -2
- package/dist/opencode.d.ts +5 -0
- package/dist/opencode.js +1 -0
- package/dist/routes/opencode.js +21 -5
- package/package.json +1 -1
package/dist/opencode.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface OpencodeConfig {
|
|
|
19
19
|
keyDuration: string;
|
|
20
20
|
/** Default max budget (USD) for keys created via the connect flow. Default 50. */
|
|
21
21
|
maxBudget: number;
|
|
22
|
+
/**
|
|
23
|
+
* Reset period of maxBudget (e.g. "30d"). Unset: maxBudget covers the key's whole life,
|
|
24
|
+
* which runs out early on long-lived keys.
|
|
25
|
+
*/
|
|
26
|
+
budgetDuration?: string;
|
|
22
27
|
/**
|
|
23
28
|
* When true, a team query parameter must reference a team the user is a
|
|
24
29
|
* member of (or manage); otherwise the key is personal-only.
|
package/dist/opencode.js
CHANGED
|
@@ -7,6 +7,7 @@ function readOpencodeConfig(config) {
|
|
|
7
7
|
enabled,
|
|
8
8
|
keyDuration: config.getOptionalString('litellm.opencode.keyDuration') ?? '30d',
|
|
9
9
|
maxBudget: config.getOptionalNumber('litellm.opencode.maxBudget') ?? 50,
|
|
10
|
+
budgetDuration: config.getOptionalString('litellm.opencode.budgetDuration'),
|
|
10
11
|
requireTeam: config.getOptionalBoolean('litellm.opencode.requireTeam') ?? false,
|
|
11
12
|
metadata: config.getOptional('litellm.opencode.metadata') ?? {},
|
|
12
13
|
};
|
package/dist/routes/opencode.js
CHANGED
|
@@ -149,6 +149,13 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
149
149
|
.catch(() => []);
|
|
150
150
|
// Find a healthy existing key (not blocked).
|
|
151
151
|
const reusable = existing.find(k => k.key_alias === alias && !k.blocked && k.user_id === userId);
|
|
152
|
+
// The gateway requires MCP access on the key itself (require_key_mcp_access_defined), and a
|
|
153
|
+
// key may only list what its team has: copy the team's MCP servers so local clients get
|
|
154
|
+
// the team's tools. A team lookup failure only costs the tools, never the connection.
|
|
155
|
+
const teamPerm = teamId ? (await client.getTeamInfo(teamId).catch(() => undefined))?.object_permission : undefined;
|
|
156
|
+
const mcp = teamPerm?.mcp_servers?.length || teamPerm?.mcp_access_groups?.length
|
|
157
|
+
? { mcp_servers: teamPerm.mcp_servers ?? [], mcp_access_groups: teamPerm.mcp_access_groups ?? [] }
|
|
158
|
+
: undefined;
|
|
152
159
|
// Mint a brand-new key bound to this user (and team) under the slot alias.
|
|
153
160
|
const mintNewKey = async () => {
|
|
154
161
|
const profile = await (0, provisioning_1.resolveUserProfile)(tokenEntityRef, catalogClient, auth, logger);
|
|
@@ -156,6 +163,7 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
156
163
|
alias,
|
|
157
164
|
duration: opencodeCfg.keyDuration,
|
|
158
165
|
max_budget: opencodeCfg.maxBudget,
|
|
166
|
+
...(opencodeCfg.budgetDuration && { budget_duration: opencodeCfg.budgetDuration }),
|
|
159
167
|
team_id: teamId,
|
|
160
168
|
metadata: {
|
|
161
169
|
...opencodeCfg.metadata,
|
|
@@ -165,6 +173,7 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
165
173
|
...(teamId && { opencode_team: teamId }),
|
|
166
174
|
},
|
|
167
175
|
user_id: userId,
|
|
176
|
+
...(mcp && { object_permission: mcp }),
|
|
168
177
|
});
|
|
169
178
|
return result.key;
|
|
170
179
|
};
|
|
@@ -176,6 +185,10 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
176
185
|
// Rotate the existing key in place via LiteLLM's key regeneration.
|
|
177
186
|
key = (await client.regenerateKey(keyHashOrId)).key;
|
|
178
187
|
rotated = true;
|
|
188
|
+
// Rotation keeps the old permissions: refresh them so team MCP changes arrive too.
|
|
189
|
+
// object_permission is not in the shared UpdateKeyRequest type yet (common 0.2.0).
|
|
190
|
+
if (key && mcp)
|
|
191
|
+
await client.updateKey({ key, object_permission: mcp });
|
|
179
192
|
}
|
|
180
193
|
catch (err) {
|
|
181
194
|
// Key regeneration isn't available on every LiteLLM edition. Only
|
|
@@ -202,15 +215,18 @@ function registerOpencodeRoutes(router, ctx) {
|
|
|
202
215
|
res.status(502).json({ error: 'LiteLLM returned no key material' });
|
|
203
216
|
return;
|
|
204
217
|
}
|
|
218
|
+
logger.info('opencode.connect', { userId, team: teamId ?? null, rotated });
|
|
219
|
+
// The Backstage connect page calls this with fetchApi (JSON + Bearer) and does the
|
|
220
|
+
// loopback redirect itself, so it can collect one key per team in a single trip.
|
|
221
|
+
if (req.is('application/json')) {
|
|
222
|
+
res.set('Cache-Control', 'no-store');
|
|
223
|
+
res.json({ key, team: teamId ?? null });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
205
226
|
const url = new URL(redirectUri);
|
|
206
227
|
url.searchParams.set('key', key);
|
|
207
228
|
if (teamId)
|
|
208
229
|
url.searchParams.set('team', teamId);
|
|
209
|
-
logger.info('opencode.connect', {
|
|
210
|
-
userId,
|
|
211
|
-
team: teamId ?? null,
|
|
212
|
-
rotated,
|
|
213
|
-
});
|
|
214
230
|
res.redirect(302, url.href);
|
|
215
231
|
}
|
|
216
232
|
catch (error) {
|