@acarmisc/backstage-plugin-litellm-backend 0.29.0 → 0.30.1

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 CHANGED
@@ -461,6 +461,14 @@ export interface Config {
461
461
  * @default []
462
462
  */
463
463
  serviceKeyPrefixes?: string[];
464
+ /**
465
+ * Starter-snippet tabs shown after a key is generated, in this order.
466
+ * Allowed: office365, opencode, claude-code, pi, openai, curl. Omit to
467
+ * show all of them in that order (Office365 first). Must not be empty,
468
+ * contain unknown names, or repeat a tab. Display-only.
469
+ * @visibility frontend
470
+ */
471
+ keySnippets?: Array<'office365' | 'opencode' | 'claude-code' | 'pi' | 'openai' | 'curl'>;
464
472
  };
465
473
 
466
474
  /**
package/dist/index.cjs.js CHANGED
@@ -9167,6 +9167,26 @@ async function resolveUserRole(userEntityRef, roleConfigs, catalogClient, auth,
9167
9167
  }
9168
9168
  }
9169
9169
 
9170
+ // src/keySnippets.ts
9171
+ var KEY_SNIPPET_TABS = ["office365", "opencode", "claude-code", "pi", "openai", "curl"];
9172
+ function parseKeySnippetTabs(raw) {
9173
+ if (raw === void 0) return [...KEY_SNIPPET_TABS];
9174
+ if (raw.length === 0) {
9175
+ throw new Error("litellm.display.keySnippets must list at least one tab");
9176
+ }
9177
+ const allowed = KEY_SNIPPET_TABS;
9178
+ const unknown = raw.filter((tab) => !allowed.includes(tab));
9179
+ if (unknown.length > 0) {
9180
+ throw new Error(
9181
+ `litellm.display.keySnippets: unknown tab(s) ${unknown.join(", ")}. Allowed: ${KEY_SNIPPET_TABS.join(", ")}`
9182
+ );
9183
+ }
9184
+ if (new Set(raw).size !== raw.length) {
9185
+ throw new Error("litellm.display.keySnippets lists a tab more than once");
9186
+ }
9187
+ return raw;
9188
+ }
9189
+
9170
9190
  // src/router.ts
9171
9191
  var import_backstage_plugin_litellm_common11 = require("@acarmisc/backstage-plugin-litellm-common");
9172
9192
 
@@ -9787,7 +9807,8 @@ var openApiSpec = {
9787
9807
  display: { type: "object", properties: {
9788
9808
  hideTeamBudgetForMembers: { type: "boolean" },
9789
9809
  hideTeamBudgetForManagers: { type: "boolean" },
9790
- serviceKeyPrefixes: { type: "array", items: { type: "string" } }
9810
+ serviceKeyPrefixes: { type: "array", items: { type: "string" } },
9811
+ keySnippets: { type: "array", items: { type: "string", enum: ["office365", "opencode", "claude-code", "pi", "openai", "curl"] } }
9791
9812
  } }
9792
9813
  } } } }
9793
9814
  }
@@ -10056,7 +10077,8 @@ function registerConfigRoutes(router, ctx) {
10056
10077
  display: {
10057
10078
  hideTeamBudgetForMembers: ctx.teamBudgetVisibility.hideTeamBudgetForMembers,
10058
10079
  hideTeamBudgetForManagers: ctx.teamBudgetVisibility.hideTeamBudgetForManagers,
10059
- serviceKeyPrefixes: ctx.serviceKeyPrefixes
10080
+ serviceKeyPrefixes: ctx.serviceKeyPrefixes,
10081
+ keySnippets: ctx.keySnippets
10060
10082
  }
10061
10083
  });
10062
10084
  });
@@ -10142,9 +10164,12 @@ function teamMcpServers(team) {
10142
10164
  const listed = team?.object_permission?.mcp_servers ?? [];
10143
10165
  return [...new Set(listed)].filter((n) => import_backstage_plugin_litellm_common3.MCP_SERVER_NAME_RE.test(n));
10144
10166
  }
10145
- function findDisallowedMcpServers(requested, allowed) {
10167
+ function findDisallowedMcpServers(requested, allowed, servers = []) {
10146
10168
  const ok = new Set(allowed);
10147
- return [...new Set(requested)].filter((n) => !ok.has(n));
10169
+ const granted = (n) => ok.has(n) || servers.some(
10170
+ (s) => (s.name === n || s.id === n) && (ok.has(s.id) || s.name !== void 0 && ok.has(s.name))
10171
+ );
10172
+ return [...new Set(requested)].filter((n) => !granted(n));
10148
10173
  }
10149
10174
 
10150
10175
  // src/services/keyService.ts
@@ -10231,7 +10256,12 @@ async function createKeyForUser(user, input, ctx) {
10231
10256
  if (!input.team_id) {
10232
10257
  throw new KeyServiceError(400, { error: "team_id is required when mcp_servers is set" });
10233
10258
  }
10234
- const disallowed = findDisallowedMcpServers(mcpServers, teamMcpServers(await getTeam(input.team_id)));
10259
+ const allowed = teamMcpServers(await getTeam(input.team_id));
10260
+ let disallowed = findDisallowedMcpServers(mcpServers, allowed);
10261
+ if (disallowed.length > 0) {
10262
+ const servers = await client.listMcpServers().catch(() => []);
10263
+ disallowed = findDisallowedMcpServers(mcpServers, allowed, servers);
10264
+ }
10235
10265
  if (disallowed.length > 0) {
10236
10266
  throw new KeyServiceError(403, {
10237
10267
  error: "One or more requested MCP servers are not allowed",
@@ -13673,6 +13703,7 @@ async function createRouter(options) {
13673
13703
  const publicBaseUrl = config.getOptionalString("litellm.publicBaseUrl") ?? null;
13674
13704
  const supportContact = config.getOptionalString("litellm.supportContact");
13675
13705
  const serviceKeyPrefixes = (config.getOptionalStringArray("litellm.display.serviceKeyPrefixes") ?? []).map((p) => p.trim()).filter((p) => p.length > 0);
13706
+ const keySnippets = parseKeySnippetTabs(config.getOptionalStringArray("litellm.display.keySnippets"));
13676
13707
  const baseClient = options.client ?? new LiteLLMClient({ baseUrl, masterKey });
13677
13708
  const userInfoCacheTtlSeconds = config.getOptionalNumber("litellm.cache.userInfoTtlSeconds") ?? 10;
13678
13709
  const client = userInfoCacheTtlSeconds > 0 ? withUserInfoCache(baseClient, { ttlMs: userInfoCacheTtlSeconds * 1e3 }) : baseClient;
@@ -13732,6 +13763,7 @@ async function createRouter(options) {
13732
13763
  publicBaseUrl,
13733
13764
  supportContact,
13734
13765
  serviceKeyPrefixes,
13766
+ keySnippets,
13735
13767
  userIdDomain,
13736
13768
  provisioningEnabled,
13737
13769
  provisioningDefaults,