@acarmisc/backstage-plugin-litellm-backend 0.11.0 → 0.11.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/dist/router.js CHANGED
@@ -46,6 +46,44 @@ const bridge_1 = require("./bridge");
46
46
  const permissions_1 = require("./permissions");
47
47
  const teamAdmin_1 = require("./teamAdmin");
48
48
  const teamCreateInFlight = new Map();
49
+ /** Backoff schedule for `withTeamFetchRetry` — 3 retries over ~1.3s total. */
50
+ const TEAM_FETCH_RETRY_DELAYS_MS = [100, 300, 900];
51
+ /**
52
+ * Only 5xx (and network/timeout) failures are treated as transient — a 4xx
53
+ * like "team not found" or "forbidden" is a deterministic answer from the
54
+ * upstream and retrying it would just add latency for the same result.
55
+ */
56
+ function isRetryableTeamFetchError(err) {
57
+ if (err instanceof client_1.LiteLLMUpstreamError) {
58
+ return err.status >= 500;
59
+ }
60
+ return true;
61
+ }
62
+ /**
63
+ * Retries a LiteLLM team-info fetch with the given backoff schedule.
64
+ *
65
+ * LiteLLM proxies are commonly run behind multiple replicas; a request can
66
+ * land on a replica that hasn't yet caught up with a very recent write
67
+ * (team create/update, membership change), or on one that is transiently
68
+ * unhealthy. Both surface as a failed `getTeamInfo` call that would
69
+ * otherwise be swallowed (e.g. GET /teams silently drops the team from the
70
+ * response) or bubble up as a spurious 500/404 right after a write this
71
+ * same request just made. A short retry smooths over that window without
72
+ * masking a genuine, persistent failure.
73
+ */
74
+ async function withTeamFetchRetry(fn, delaysMs = TEAM_FETCH_RETRY_DELAYS_MS) {
75
+ for (let attempt = 0;; attempt++) {
76
+ try {
77
+ return await fn();
78
+ }
79
+ catch (err) {
80
+ if (attempt >= delaysMs.length || !isRetryableTeamFetchError(err)) {
81
+ throw err;
82
+ }
83
+ await new Promise(resolve => setTimeout(resolve, delaysMs[attempt]));
84
+ }
85
+ }
86
+ }
49
87
  async function createRouter(options) {
50
88
  const { config, logger, auth, discovery, permissions } = options;
51
89
  const baseUrl = config.getString('litellm.baseUrl');
@@ -510,8 +548,8 @@ async function createRouter(options) {
510
548
  res.json([]);
511
549
  return;
512
550
  }
513
- const teams = await Promise.all(userInfo.teams.map(teamId => client.getTeamInfo(teamId).catch(err => {
514
- logger.warn(`Failed to fetch team ${teamId}: ${err.message}`);
551
+ const teams = await Promise.all(userInfo.teams.map(teamId => withTeamFetchRetry(() => client.getTeamInfo(teamId)).catch(err => {
552
+ logger.warn(`Failed to fetch team ${teamId} after retries: ${err.message}`);
515
553
  return null;
516
554
  })));
517
555
  res.json(teams.filter(Boolean));
@@ -773,7 +811,7 @@ async function createRouter(options) {
773
811
  }
774
812
  let existing;
775
813
  try {
776
- existing = await client.getTeamInfo(teamId);
814
+ existing = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
777
815
  }
778
816
  catch (err) {
779
817
  if (err instanceof client_1.LiteLLMUpstreamError && err.status === 404) {
@@ -885,7 +923,7 @@ async function createRouter(options) {
885
923
  member: litellmUserId,
886
924
  owningGroup,
887
925
  });
888
- const updated = await client.getTeamInfo(teamId);
926
+ const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
889
927
  res.json(updated);
890
928
  }
891
929
  catch (err) {
@@ -920,7 +958,7 @@ async function createRouter(options) {
920
958
  member: litellmUserId,
921
959
  owningGroup,
922
960
  });
923
- const updated = await client.getTeamInfo(teamId);
961
+ const updated = await withTeamFetchRetry(() => client.getTeamInfo(teamId));
924
962
  res.json(updated);
925
963
  }
926
964
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",