@acarmisc/backstage-plugin-litellm-backend 0.1.8 → 0.1.11
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/index.cjs.js +159 -7
- package/dist/index.cjs.js.map +2 -2
- package/dist/types.cjs.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -5658,6 +5658,12 @@ var LiteLLMClient = class {
|
|
|
5658
5658
|
body: JSON.stringify({ json: request })
|
|
5659
5659
|
});
|
|
5660
5660
|
}
|
|
5661
|
+
async updateKey(request) {
|
|
5662
|
+
return this.request("/key/update", {
|
|
5663
|
+
method: "POST",
|
|
5664
|
+
body: JSON.stringify(request)
|
|
5665
|
+
});
|
|
5666
|
+
}
|
|
5661
5667
|
async deleteKeys(request) {
|
|
5662
5668
|
return this.request("/key/delete", {
|
|
5663
5669
|
method: "POST",
|
|
@@ -5671,22 +5677,153 @@ var LiteLLMClient = class {
|
|
|
5671
5677
|
async getTeamInfo(teamId) {
|
|
5672
5678
|
return this.request(`/team/info?team_id=${encodeURIComponent(teamId)}`);
|
|
5673
5679
|
}
|
|
5674
|
-
|
|
5675
|
-
|
|
5680
|
+
emptyUsage() {
|
|
5681
|
+
return {
|
|
5682
|
+
total_spend: 0,
|
|
5683
|
+
total_tokens: 0,
|
|
5684
|
+
prompt_tokens: 0,
|
|
5685
|
+
completion_tokens: 0,
|
|
5686
|
+
api_requests: 0,
|
|
5687
|
+
successful_requests: 0,
|
|
5688
|
+
failed_requests: 0,
|
|
5689
|
+
usage_by_model: {},
|
|
5690
|
+
usage_by_key: {},
|
|
5691
|
+
daily_usage: [],
|
|
5692
|
+
daily_by_model: []
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5695
|
+
/**
|
|
5696
|
+
* Transforms LiteLLM's SpendAnalyticsPaginatedResponse into the flatter
|
|
5697
|
+
* UsageMetrics shape consumed by the frontend charts.
|
|
5698
|
+
*
|
|
5699
|
+
* Source shape (per result row):
|
|
5700
|
+
* { date, metrics, breakdown: { models: { [name]: { metrics, api_key_breakdown: { [keyHash]: { metrics, metadata } } } } } }
|
|
5701
|
+
*
|
|
5702
|
+
* We fan that out into three views the UI consumes:
|
|
5703
|
+
* - daily_usage → spend + request trends over time
|
|
5704
|
+
* - usage_by_model → which models drove cost / traffic
|
|
5705
|
+
* - usage_by_key → which keys drove cost / traffic (with key_alias + team_id from metadata)
|
|
5706
|
+
*/
|
|
5707
|
+
transformDailyActivity(response) {
|
|
5708
|
+
const results = Array.isArray(response?.results) ? response.results : [];
|
|
5709
|
+
const meta = response?.metadata ?? {};
|
|
5710
|
+
const daily_usage = results.map((r) => ({
|
|
5711
|
+
date: r.date,
|
|
5712
|
+
spend: r.metrics?.spend ?? 0,
|
|
5713
|
+
total_tokens: r.metrics?.total_tokens ?? 0,
|
|
5714
|
+
prompt_tokens: r.metrics?.prompt_tokens ?? 0,
|
|
5715
|
+
completion_tokens: r.metrics?.completion_tokens ?? 0,
|
|
5716
|
+
api_requests: r.metrics?.api_requests ?? 0,
|
|
5717
|
+
successful_requests: r.metrics?.successful_requests ?? 0,
|
|
5718
|
+
failed_requests: r.metrics?.failed_requests ?? 0
|
|
5719
|
+
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
5720
|
+
const usage_by_model = {};
|
|
5721
|
+
const usage_by_key = {};
|
|
5722
|
+
const daily_by_model = [];
|
|
5723
|
+
const emptyModelBucket = () => ({
|
|
5724
|
+
total_spend: 0,
|
|
5725
|
+
total_tokens: 0,
|
|
5726
|
+
prompt_tokens: 0,
|
|
5727
|
+
completion_tokens: 0,
|
|
5728
|
+
api_requests: 0,
|
|
5729
|
+
successful_requests: 0,
|
|
5730
|
+
failed_requests: 0
|
|
5731
|
+
});
|
|
5732
|
+
for (const r of results) {
|
|
5733
|
+
const models = r.breakdown?.models ?? {};
|
|
5734
|
+
for (const [name, entry] of Object.entries(models)) {
|
|
5735
|
+
const m = entry?.metrics ?? {};
|
|
5736
|
+
const bucket = usage_by_model[name] ?? emptyModelBucket();
|
|
5737
|
+
bucket.total_spend += m.spend ?? 0;
|
|
5738
|
+
bucket.total_tokens += m.total_tokens ?? 0;
|
|
5739
|
+
bucket.prompt_tokens += m.prompt_tokens ?? 0;
|
|
5740
|
+
bucket.completion_tokens += m.completion_tokens ?? 0;
|
|
5741
|
+
bucket.api_requests += m.api_requests ?? 0;
|
|
5742
|
+
bucket.successful_requests += m.successful_requests ?? 0;
|
|
5743
|
+
bucket.failed_requests += m.failed_requests ?? 0;
|
|
5744
|
+
usage_by_model[name] = bucket;
|
|
5745
|
+
daily_by_model.push({
|
|
5746
|
+
date: r.date,
|
|
5747
|
+
model: name,
|
|
5748
|
+
spend: m.spend ?? 0,
|
|
5749
|
+
prompt_tokens: m.prompt_tokens ?? 0,
|
|
5750
|
+
completion_tokens: m.completion_tokens ?? 0,
|
|
5751
|
+
total_tokens: m.total_tokens ?? 0,
|
|
5752
|
+
api_requests: m.api_requests ?? 0,
|
|
5753
|
+
successful_requests: m.successful_requests ?? 0,
|
|
5754
|
+
failed_requests: m.failed_requests ?? 0
|
|
5755
|
+
});
|
|
5756
|
+
const keyMap = entry?.api_key_breakdown ?? {};
|
|
5757
|
+
for (const [keyHash, keyEntry] of Object.entries(keyMap)) {
|
|
5758
|
+
const km = keyEntry?.metrics ?? {};
|
|
5759
|
+
const kmeta = keyEntry?.metadata ?? {};
|
|
5760
|
+
const kb = usage_by_key[keyHash] ?? {
|
|
5761
|
+
key_alias: kmeta.key_alias,
|
|
5762
|
+
team_id: kmeta.team_id ?? null,
|
|
5763
|
+
models: [],
|
|
5764
|
+
...emptyModelBucket()
|
|
5765
|
+
};
|
|
5766
|
+
if (!kb.key_alias && kmeta.key_alias) kb.key_alias = kmeta.key_alias;
|
|
5767
|
+
if (kb.team_id == null && kmeta.team_id) kb.team_id = kmeta.team_id;
|
|
5768
|
+
if (!kb.models.includes(name)) kb.models.push(name);
|
|
5769
|
+
kb.total_spend += km.spend ?? 0;
|
|
5770
|
+
kb.total_tokens += km.total_tokens ?? 0;
|
|
5771
|
+
kb.prompt_tokens += km.prompt_tokens ?? 0;
|
|
5772
|
+
kb.completion_tokens += km.completion_tokens ?? 0;
|
|
5773
|
+
kb.api_requests += km.api_requests ?? 0;
|
|
5774
|
+
kb.successful_requests += km.successful_requests ?? 0;
|
|
5775
|
+
kb.failed_requests += km.failed_requests ?? 0;
|
|
5776
|
+
usage_by_key[keyHash] = kb;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
return {
|
|
5781
|
+
total_spend: meta.total_spend ?? 0,
|
|
5782
|
+
total_tokens: meta.total_tokens ?? 0,
|
|
5783
|
+
prompt_tokens: meta.total_prompt_tokens ?? 0,
|
|
5784
|
+
completion_tokens: meta.total_completion_tokens ?? 0,
|
|
5785
|
+
api_requests: meta.total_api_requests ?? 0,
|
|
5786
|
+
successful_requests: meta.total_successful_requests ?? 0,
|
|
5787
|
+
failed_requests: meta.total_failed_requests ?? 0,
|
|
5788
|
+
usage_by_model,
|
|
5789
|
+
usage_by_key,
|
|
5790
|
+
daily_usage,
|
|
5791
|
+
daily_by_model
|
|
5792
|
+
};
|
|
5793
|
+
}
|
|
5794
|
+
async getUsage(startDate, endDate, userId, _groupBy) {
|
|
5795
|
+
const params = new URLSearchParams({
|
|
5796
|
+
start_date: startDate,
|
|
5797
|
+
end_date: endDate,
|
|
5798
|
+
page_size: "100"
|
|
5799
|
+
});
|
|
5676
5800
|
if (userId) params.append("user_id", userId);
|
|
5677
|
-
if (groupBy) params.append("group_by", groupBy);
|
|
5678
5801
|
try {
|
|
5679
|
-
|
|
5802
|
+
const response = await this.request(`/user/daily/activity?${params.toString()}`);
|
|
5803
|
+
return this.transformDailyActivity(response);
|
|
5680
5804
|
} catch (err) {
|
|
5681
5805
|
if (err.status === 404 || err.message.includes("not found")) {
|
|
5682
|
-
return
|
|
5806
|
+
return this.emptyUsage();
|
|
5683
5807
|
}
|
|
5684
5808
|
throw err;
|
|
5685
5809
|
}
|
|
5686
5810
|
}
|
|
5687
5811
|
async getTeamUsage(teamId, startDate, endDate) {
|
|
5688
|
-
const params = new URLSearchParams({
|
|
5689
|
-
|
|
5812
|
+
const params = new URLSearchParams({
|
|
5813
|
+
start_date: startDate,
|
|
5814
|
+
end_date: endDate,
|
|
5815
|
+
team_ids: teamId,
|
|
5816
|
+
page_size: "100"
|
|
5817
|
+
});
|
|
5818
|
+
try {
|
|
5819
|
+
const response = await this.request(`/team/daily/activity?${params.toString()}`);
|
|
5820
|
+
return this.transformDailyActivity(response);
|
|
5821
|
+
} catch (err) {
|
|
5822
|
+
if (err.status === 404 || err.message.includes("not found")) {
|
|
5823
|
+
return this.emptyUsage();
|
|
5824
|
+
}
|
|
5825
|
+
throw err;
|
|
5826
|
+
}
|
|
5690
5827
|
}
|
|
5691
5828
|
};
|
|
5692
5829
|
|
|
@@ -5860,6 +5997,21 @@ async function createRouter(options) {
|
|
|
5860
5997
|
res.status(500).json({ error: error.message });
|
|
5861
5998
|
}
|
|
5862
5999
|
});
|
|
6000
|
+
router.post("/keys/:keyId/update", async (req, res) => {
|
|
6001
|
+
try {
|
|
6002
|
+
const { keyId } = req.params;
|
|
6003
|
+
if (!keyId) {
|
|
6004
|
+
res.status(400).json({ error: "keyId is required" });
|
|
6005
|
+
return;
|
|
6006
|
+
}
|
|
6007
|
+
const request = { ...req.body, key: keyId };
|
|
6008
|
+
const result = await client.updateKey(request);
|
|
6009
|
+
res.json(result);
|
|
6010
|
+
} catch (error) {
|
|
6011
|
+
logger.error("Failed to update key", error);
|
|
6012
|
+
res.status(500).json({ error: error.message });
|
|
6013
|
+
}
|
|
6014
|
+
});
|
|
5863
6015
|
router.delete("/keys/:keyId", async (req, res) => {
|
|
5864
6016
|
try {
|
|
5865
6017
|
const { keyId } = req.params;
|