fix: Repair Top Agents tab after LibreChat transaction shape change

LibreChat stopped tagging agent transactions with model: agent_xxx around
2026-03; new agent transactions record the underlying LLM and link to the
message via messageId. Aggregate from messages -> transactions and union
with the legacy path so historical and current data both show.

Also create createdAt / messageId / (createdAt, model) indexes on startup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DJP 2026-04-28 17:08:26 -04:00
parent 1673694b11
commit 31358a8b86
2 changed files with 69 additions and 24 deletions

View file

@ -15,9 +15,23 @@ async function connectDB() {
await client.connect();
db = client.db();
console.log('Connected to MongoDB');
await ensureIndexes(db);
return db;
}
async function ensureIndexes(db) {
try {
await Promise.all([
db.collection('transactions').createIndex({ createdAt: -1 }),
db.collection('transactions').createIndex({ messageId: 1 }),
db.collection('messages').createIndex({ createdAt: -1, model: 1 }),
]);
console.log('Indexes ensured');
} catch (e) {
console.error('Index creation failed:', e.message);
}
}
// Health check (no auth)
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });

View file

@ -180,37 +180,68 @@ async function getTopAgents(db, query, limit = 10) {
await refreshAgentsCache(db);
const { startDate, endDate } = getDateRange(query);
const results = await db.collection('transactions').aggregate([
// LibreChat changed transaction shape around 2026-03: agent transactions used
// to record `model: agent_xxx` directly; newer ones record the underlying LLM
// (e.g. gpt-5.2) and link back to a message via `messageId`. Sum across both.
const agentMsgs = await db.collection('messages').find(
{ createdAt: { $gte: startDate, $lte: endDate }, model: { $regex: /^agent_/ } },
{ projection: { messageId: 1, model: 1, conversationId: 1 } }
).toArray();
const msgToAgent = new Map(agentMsgs.map(m => [m.messageId, m.model]));
const newPath = msgToAgent.size === 0 ? [] : await db.collection('transactions').aggregate([
{ $match: {
messageId: { $in: [...msgToAgent.keys()] },
createdAt: { $gte: startDate, $lte: endDate },
} },
{ $group: {
_id: '$messageId',
totalTokens: { $sum: { $abs: '$rawAmount' } },
totalCost: { $sum: { $abs: '$tokenValue' } },
conversationId: { $first: '$conversationId' },
} }
]).toArray();
const legacy = await db.collection('transactions').aggregate([
{ $match: { createdAt: { $gte: startDate, $lte: endDate }, model: { $regex: /^agent_/ } } },
{
$group: {
{ $group: {
_id: '$model',
totalTokens: { $sum: { $abs: '$rawAmount' } },
totalCost: { $sum: { $abs: '$tokenValue' } },
conversations: { $addToSet: '$conversationId' },
}
},
{ $sort: { totalCost: -1 } },
{ $limit: limit },
{
$project: {
agentId: '$_id',
totalTokens: 1,
totalCost: { $divide: ['$totalCost', 1_000_000] },
conversationCount: { $size: '$conversations' },
}
}
} }
]).toArray();
return results.map(r => {
const agent = agentsCache.get(r.agentId);
return {
...r,
agentName: agent ? agent.name : r.agentId,
underlyingModel: agent ? agent.model : 'Unknown',
provider: agent ? agent.provider : 'Unknown',
};
});
const totals = new Map();
const bump = (agentId, tokens, cost, convs) => {
if (!agentId) return;
if (!totals.has(agentId)) {
totals.set(agentId, { agentId, totalTokens: 0, totalCost: 0, convs: new Set() });
}
const a = totals.get(agentId);
a.totalTokens += tokens;
a.totalCost += cost;
for (const c of convs) if (c) a.convs.add(c);
};
for (const t of newPath) bump(msgToAgent.get(t._id), t.totalTokens, t.totalCost, [t.conversationId]);
for (const t of legacy) bump(t._id, t.totalTokens, t.totalCost, t.conversations);
return [...totals.values()]
.sort((a, b) => b.totalCost - a.totalCost)
.slice(0, limit)
.map(a => {
const meta = agentsCache.get(a.agentId);
return {
agentId: a.agentId,
totalTokens: a.totalTokens,
totalCost: a.totalCost / 1_000_000,
conversationCount: a.convs.size,
agentName: meta ? meta.name : a.agentId,
underlyingModel: meta ? meta.model : 'Unknown',
provider: meta ? meta.provider : 'Unknown',
};
});
}
async function getCostBreakdown(db, query) {