Files
OmniRoute/src/lib/usage/providerDisplayNames.ts
SeaXen e092280718 fix(usage): reset logs and show provider names in analytics (#7264)
* fix(usage): reset logs and show provider names in analytics

* refactor(db): extract usage purge routines to cleanup module (file-size cap)

Move the generic delete-all/delete-before-cutoff table helpers and the
call-log-artifact purge helpers out of cleanup.ts into a new
cleanup/usagePurge.ts submodule, so this PR's growth in cleanup.ts stays
under the file-size gate cap once combined with other in-flight changes
to the same file. Pure extraction — resetUsageHistory delegates to the
same logic, now imported instead of inlined; no behavior change.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(db): hoist reset targets table + derive total via reduce (max-lines-per-function)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(api): move analytics provider-name enrichment into lib (file-size cap)

src/app/api/usage/analytics/route.ts is a frozen file-size-capped file
(baseline: 942 lines, zero headroom on this branch). The provider
display-name enrichment added for the byProvider breakdown (id -> name/
prefix lookup via provider_nodes) pushed it to 971 lines, tripping the
check:file-size ratchet.

Move the new getProviderDisplayName/getProviderDisplayNames helpers, plus
the byProvider row-building they modified, into a new leaf module
src/lib/usage/providerDisplayNames.ts (buildByProviderRows). The route now
just imports and calls it. No behavior change: same lookup, same fallback
to the raw provider id, same row shape.

Net effect: route.ts drops from 941 (pre-change) to 930 lines (-11),
comfortably restoring headroom instead of exceeding the frozen cap.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(usage): fall back to static catalog name in provider display resolution

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 21:18:18 -03:00

89 lines
3.0 KiB
TypeScript

/**
* Provider display-name resolution for the usage analytics `byProvider`
* breakdown (`src/app/api/usage/analytics/route.ts`).
*
* Raw `usage_history.provider` values are internal provider ids (e.g. a
* dynamic compatible-provider uuid-suffixed id). This module maps those ids
* to the friendly `name`/`prefix` configured on the matching `provider_nodes`
* row, falling back to the raw id when no node matches, so analytics rows
* show a readable label instead of an internal id.
*
* @module lib/usage/providerDisplayNames
*/
import { getProviderNodes } from "@/models";
import { getProviderById } from "@/shared/constants/providers";
function toStringValue(value: unknown, fallback = ""): string {
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
}
function roundCost(value: number): number {
return Math.round(value * 1_000_000) / 1_000_000;
}
function getProviderDisplayName(
provider: unknown,
providerDisplayNames: Map<string, string>
): string {
const rawProvider = toStringValue(provider, "unknown");
// Configured node name wins; static catalog covers built-ins (e.g. codex →
// "OpenAI Codex") the nodes table doesn't know about; raw id is the last resort.
return (
providerDisplayNames.get(rawProvider) || getProviderById(rawProvider)?.name || rawProvider
);
}
async function getProviderDisplayNames(): Promise<Map<string, string>> {
const displayNames = new Map<string, string>();
const providerNodes = (await getProviderNodes()) as Array<{
id?: unknown;
name?: unknown;
prefix?: unknown;
}>;
for (const node of providerNodes) {
const id = toStringValue(node.id);
if (!id) continue;
const displayName = toStringValue(node.name) || toStringValue(node.prefix) || id;
displayNames.set(id, displayName);
}
return displayNames;
}
export interface ByProviderRow {
provider: string;
requests: number;
promptTokens: number;
completionTokens: number;
totalTokens: number;
avgLatencyMs: number;
successRatePct: number | string;
cost: number;
}
/**
* Builds the `byProvider` analytics rows, resolving each row's raw provider
* id to its configured display name.
*/
export async function buildByProviderRows(
providerRows: Array<Record<string, unknown>>,
providerCostByProvider: Map<string, number>
): Promise<ByProviderRow[]> {
const providerDisplayNames = await getProviderDisplayNames();
return providerRows.map((row) => ({
provider: getProviderDisplayName(row.provider, providerDisplayNames),
requests: Number(row.requests),
promptTokens: Number(row.promptTokens),
completionTokens: Number(row.completionTokens),
totalTokens: Number(row.totalTokens),
avgLatencyMs: Math.round(Number(row.avgLatencyMs)),
successRatePct:
Number(row.requests) > 0
? Number((Number(row.successfulRequests) / Number(row.requests)) * 100).toFixed(2)
: 0,
cost: roundCost(providerCostByProvider.get(toStringValue(row.provider)) || 0),
}));
}