OneLiteLLM team per company+virt keys
All checks were successful
Build and Push / build (push) Successful in 1m24s
All checks were successful
Build and Push / build (push) Successful in 1m24s
This commit is contained in:
@@ -91,6 +91,10 @@ export async function getGlobalSpend(): Promise<number> {
|
||||
/**
|
||||
* Fetch per-team spend as a map: teamId → spend (CHF).
|
||||
* Uses /team/list which includes current spend per team.
|
||||
*
|
||||
* Since Slice 2, a "team" is the company-level budget shared across all
|
||||
* tenants of the same ZITADEL org. So this map gives company totals, not
|
||||
* per-tenant spend. For per-tenant attribution, use {@link getPerKeySpend}.
|
||||
*/
|
||||
export async function getPerTeamSpend(): Promise<Map<string, number>> {
|
||||
const teams = await listTeams();
|
||||
@@ -102,3 +106,54 @@ export async function getPerTeamSpend(): Promise<Map<string, number>> {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch per-virtual-key spend as a map: keyAlias → spend (CHF).
|
||||
*
|
||||
* Since Slice 2, each PiecedTenant CR owns one virtual key under its
|
||||
* org's team, with `key_alias = tenant.metadata.name`. Filtering by the
|
||||
* key alias is how we get genuinely per-tenant spend.
|
||||
*
|
||||
* Implementation
|
||||
* --------------
|
||||
* Calls `/key/list?return_full_object=true&include_team_keys=true`,
|
||||
* which returns objects with `spend` and `key_alias`. Older LiteLLM
|
||||
* builds may return raw token strings instead — we degrade gracefully
|
||||
* to an empty map in that case rather than throwing, since the admin
|
||||
* health page should still render even if per-tenant numbers are
|
||||
* temporarily unavailable.
|
||||
*
|
||||
* @returns Map<keyAlias, spend>. May be empty if the LiteLLM build
|
||||
* doesn't expose key-alias info; callers must handle that.
|
||||
*/
|
||||
export async function getPerKeySpend(): Promise<Map<string, number>> {
|
||||
const map = new Map<string, number>();
|
||||
try {
|
||||
const data = await litellmFetch(
|
||||
"/key/list?return_full_object=true&include_team_keys=true"
|
||||
);
|
||||
|
||||
// Response shape: { keys: [ { key_alias, spend, token, ... } ] }
|
||||
// or sometimes { data: [...] }, or raw arrays. Be tolerant.
|
||||
const keys: any[] = Array.isArray(data?.keys)
|
||||
? data.keys
|
||||
: Array.isArray(data?.data)
|
||||
? data.data
|
||||
: Array.isArray(data)
|
||||
? data
|
||||
: [];
|
||||
|
||||
for (const k of keys) {
|
||||
// Skip raw-string entries from older API shapes — we can't attribute them.
|
||||
if (typeof k !== "object" || k === null) continue;
|
||||
const alias = k.key_alias ?? k.keyAlias;
|
||||
if (typeof alias !== "string" || !alias) continue;
|
||||
const spend =
|
||||
typeof k.spend === "number" ? k.spend : Number(k.spend) || 0;
|
||||
map.set(alias, spend);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("getPerKeySpend failed, returning empty map:", e);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user