@acarmisc/backstage-plugin-litellm 0.22.0 → 0.23.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/dist/budget.d.ts +6 -0
- package/dist/index.cjs.js +68 -30
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +68 -30
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2001,6 +2001,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
2001
2001
|
const [mcpError, setMcpError] = useState3(null);
|
|
2002
2002
|
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
2003
2003
|
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
2004
|
+
const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
|
|
2005
|
+
const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
|
|
2004
2006
|
const members = useMemo4(() => team?.members_with_roles ?? [], [team]);
|
|
2005
2007
|
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2006
2008
|
const catalogApi = useApi(catalogApiRef);
|
|
@@ -2036,9 +2038,13 @@ var init_ManageTeamDialog = __esm({
|
|
|
2036
2038
|
if (mode === "edit" && team) {
|
|
2037
2039
|
setAlias(team.team_alias ?? "");
|
|
2038
2040
|
setModels(team.models ?? []);
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2041
|
+
if (hideBudgetForManagers) {
|
|
2042
|
+
setMaxBudget("");
|
|
2043
|
+
} else {
|
|
2044
|
+
setMaxBudget(
|
|
2045
|
+
team.max_budget ? String(team.max_budget) : String(maxBudgetCeiling)
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2042
2048
|
setUnlimited(allowUnlimitedBudget && !team.max_budget);
|
|
2043
2049
|
setBudgetDuration(team.budget_duration ?? DEFAULT_BUDGET_DURATION);
|
|
2044
2050
|
} else {
|
|
@@ -2058,7 +2064,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2058
2064
|
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
2059
2065
|
setMcpError(null);
|
|
2060
2066
|
}
|
|
2061
|
-
}, [open, mode, team, allowUnlimitedBudget, maxBudgetCeiling]);
|
|
2067
|
+
}, [open, mode, team, allowUnlimitedBudget, maxBudgetCeiling, hideBudgetForManagers]);
|
|
2062
2068
|
const handleSubmit = async () => {
|
|
2063
2069
|
setError(null);
|
|
2064
2070
|
if (!alias.trim()) {
|
|
@@ -2070,18 +2076,21 @@ var init_ManageTeamDialog = __esm({
|
|
|
2070
2076
|
return;
|
|
2071
2077
|
}
|
|
2072
2078
|
if (!unlimited) {
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2079
|
+
const budgetLeftBlank = budgetWriteOnly && !maxBudget.trim();
|
|
2080
|
+
if (!budgetLeftBlank) {
|
|
2081
|
+
if (!maxBudget) {
|
|
2082
|
+
setError("Budget is required when unlimited budgets are disabled");
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const budget = parseFloat(maxBudget);
|
|
2086
|
+
if (isNaN(budget) || budget <= 0) {
|
|
2087
|
+
setError("Budget must be a positive number");
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
if (budget > maxBudgetCeiling) {
|
|
2091
|
+
setError(`Budget cannot exceed $${maxBudgetCeiling}`);
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2085
2094
|
}
|
|
2086
2095
|
}
|
|
2087
2096
|
try {
|
|
@@ -2095,10 +2104,16 @@ var init_ManageTeamDialog = __esm({
|
|
|
2095
2104
|
...budgetDuration && { budget_duration: budgetDuration }
|
|
2096
2105
|
};
|
|
2097
2106
|
} else {
|
|
2107
|
+
let budgetField = {};
|
|
2108
|
+
if (unlimited) {
|
|
2109
|
+
budgetField = { max_budget: null };
|
|
2110
|
+
} else if (maxBudget.trim()) {
|
|
2111
|
+
budgetField = { max_budget: parseFloat(maxBudget) };
|
|
2112
|
+
}
|
|
2098
2113
|
payload = {
|
|
2099
2114
|
team_alias: alias.trim(),
|
|
2100
2115
|
models,
|
|
2101
|
-
...
|
|
2116
|
+
...budgetField,
|
|
2102
2117
|
...budgetDuration && { budget_duration: budgetDuration }
|
|
2103
2118
|
};
|
|
2104
2119
|
}
|
|
@@ -2204,13 +2219,13 @@ var init_ManageTeamDialog = __esm({
|
|
|
2204
2219
|
), /* @__PURE__ */ React5.createElement(
|
|
2205
2220
|
TextField3,
|
|
2206
2221
|
{
|
|
2207
|
-
label: "Max Budget ($)",
|
|
2222
|
+
label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
|
|
2208
2223
|
type: "number",
|
|
2209
2224
|
value: maxBudget,
|
|
2210
2225
|
onChange: (e) => setMaxBudget(e.target.value),
|
|
2211
2226
|
disabled: submitting || unlimited,
|
|
2212
2227
|
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
2213
|
-
helperText: `Maximum: $${maxBudgetCeiling}`,
|
|
2228
|
+
helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
|
|
2214
2229
|
fullWidth: true
|
|
2215
2230
|
}
|
|
2216
2231
|
), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
|
|
@@ -2781,7 +2796,19 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2781
2796
|
pct: limitPct(spend, budget)
|
|
2782
2797
|
};
|
|
2783
2798
|
})() : void 0;
|
|
2784
|
-
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0).map((t) => {
|
|
2799
|
+
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0 || t.budget_hidden === true).map((t) => {
|
|
2800
|
+
if (t.budget_hidden === true) {
|
|
2801
|
+
return {
|
|
2802
|
+
kind: "team",
|
|
2803
|
+
label: t.team_alias || "Untitled team",
|
|
2804
|
+
sublabel: t.team_id,
|
|
2805
|
+
budget: 0,
|
|
2806
|
+
spend: 0,
|
|
2807
|
+
budgetDuration: t.budget_duration,
|
|
2808
|
+
pct: Math.min(100, Math.max(0, t.budget_pct ?? 0)),
|
|
2809
|
+
hidden: true
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2785
2812
|
const spend = t.spend ?? 0;
|
|
2786
2813
|
return {
|
|
2787
2814
|
kind: "team",
|
|
@@ -2957,7 +2984,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
2957
2984
|
}
|
|
2958
2985
|
},
|
|
2959
2986
|
limit.sublabel
|
|
2960
|
-
)), /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
|
|
2987
|
+
)), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
|
|
2961
2988
|
Box7,
|
|
2962
2989
|
{
|
|
2963
2990
|
sx: {
|
|
@@ -2968,7 +2995,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
2968
2995
|
mt: 0.75
|
|
2969
2996
|
}
|
|
2970
2997
|
},
|
|
2971
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
2998
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
2972
2999
|
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2973
3000
|
));
|
|
2974
3001
|
};
|
|
@@ -3176,6 +3203,15 @@ function budgetTone3(isOver, isNear) {
|
|
|
3176
3203
|
if (isNear) return "warning";
|
|
3177
3204
|
return "accent";
|
|
3178
3205
|
}
|
|
3206
|
+
function visibleBudgetPct(spend, budget) {
|
|
3207
|
+
if (budget <= 0) return 0;
|
|
3208
|
+
return Math.min(spend / budget * 100, 100);
|
|
3209
|
+
}
|
|
3210
|
+
function budgetStatValue(hidden, budget) {
|
|
3211
|
+
if (hidden) return "Hidden";
|
|
3212
|
+
if (budget > 0) return fmtUsd2(budget);
|
|
3213
|
+
return "Unlimited";
|
|
3214
|
+
}
|
|
3179
3215
|
var fmtUsd2, TeamCard, TeamUsage;
|
|
3180
3216
|
var init_TeamUsage = __esm({
|
|
3181
3217
|
"src/components/TeamUsage.tsx"() {
|
|
@@ -3185,13 +3221,15 @@ var init_TeamUsage = __esm({
|
|
|
3185
3221
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
3186
3222
|
const [expanded, setExpanded] = useState6(false);
|
|
3187
3223
|
const chart = useChartTheme();
|
|
3188
|
-
const
|
|
3189
|
-
const
|
|
3190
|
-
const
|
|
3191
|
-
const
|
|
3192
|
-
const
|
|
3224
|
+
const hidden = team.budget_hidden === true;
|
|
3225
|
+
const budget = hidden ? 0 : team.max_budget ?? 0;
|
|
3226
|
+
const spend = hidden ? 0 : team.spend ?? 0;
|
|
3227
|
+
const budgetPct = hidden ? Math.min(100, Math.max(0, team.budget_pct ?? 0)) : visibleBudgetPct(spend, budget);
|
|
3228
|
+
const isOver = hidden ? team.budget_status === "over" : budget > 0 && spend >= budget;
|
|
3229
|
+
const isNear = hidden ? team.budget_status === "near" : budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
3193
3230
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
3194
3231
|
const renderDailySpendSection = () => {
|
|
3232
|
+
if (hidden) return null;
|
|
3195
3233
|
if (usageLoading) {
|
|
3196
3234
|
return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 22 }));
|
|
3197
3235
|
}
|
|
@@ -3292,11 +3330,11 @@ var init_TeamUsage = __esm({
|
|
|
3292
3330
|
},
|
|
3293
3331
|
/* @__PURE__ */ React8.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3294
3332
|
/* @__PURE__ */ React8.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3295
|
-
/* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value:
|
|
3296
|
-
/* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
3333
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
|
|
3334
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
|
|
3297
3335
|
/* @__PURE__ */ React8.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3298
3336
|
/* @__PURE__ */ React8.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3299
|
-
), budget > 0 && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React8.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React8.createElement(Box8, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React8.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React8.createElement(Box8, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React8.createElement(
|
|
3337
|
+
), hidden && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React8.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React8.createElement(Box8, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React8.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React8.createElement(Box8, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React8.createElement(
|
|
3300
3338
|
TableContainer4,
|
|
3301
3339
|
{
|
|
3302
3340
|
component: Paper6,
|