litellm/ui/litellm-dashboard/src/utils/migratedPages.ts
tin-berri 3f3295b33f
feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810)
* feat(spend): track prompt compression saved tokens in daily spend aggregates

Native compression interception now records tokens_before/after/saved into the
request litellm_metadata so savings land in the SpendLog metadata JSON under a
typed compression_savings key. A single normalizer
(extract_compression_saved_tokens) sums that key with Headroom guardrail
tokens_saved; the two writers are disjoint and run at different stages, so
summing never double-counts. The spend-log redactor now preserves purely
numeric compression stats inside guardrail_response so Headroom savings
survive the store_prompts_in_spend_logs=false default. compression_saved_tokens
is threaded through BaseDailySpendTransaction, queue aggregation, the daily
upsert blocks, a new BigInt column on all six daily spend tables, and the
daily activity read path (SpendMetrics, DailySpendMetadata, raw-SQL rollups)

* fix(spend): normalize legacy guardrail shapes and float token stats in compression savings reader

* feat(spend): aggregate compression and prompt caching dollar savings in daily rollups

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(spend): update daily spend aggregation fixtures for savings columns

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(ui): add Cost Optimization dashboard page

New left-nav Cost Optimization page under Observability that surfaces money saved by prompt compression and prompt caching. It reads the daily activity rollup (userDailyActivityCall / get_daily_activity) and never scans SpendLogs, so it stays fast at 1M+ rows.

Renders a Total saved card, per-driver Compression and Prompt caching cards, a savings-over-time area chart, and a savings-by-driver donut, all aggregated in memory from the per-day metrics.compression_savings_spend and metrics.prompt_caching_savings_spend fields.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 17:47:54 -07:00

83 lines
3.1 KiB
TypeScript

import { serverRootPath } from "@/components/networking";
/**
* Single source of truth for pages cut over from the legacy `?page=` switch in
* app/page.tsx to path-based routes under app/(dashboard)/.
*
* Key = legacy page id emitted by the sidebar. Value = route segment under (dashboard)/.
* Add an entry to route the sidebar and deep links to the new path and redirect the
* legacy `?page=` URL; remove it to roll back.
*/
export const MIGRATED_PAGES: Record<string, string> = {
"api-keys": "api-keys",
models: "models-and-endpoints",
api_ref: "api-reference",
// Legacy alias: older bookmarks used the hyphenated ?page=api-reference form.
"api-reference": "api-reference",
"llm-playground": "playground",
projects: "projects",
chat: "chat",
"access-groups": "access-groups",
budgets: "budgets",
workflows: "workflows",
"guardrails-monitor": "guardrails-monitor",
"mcp-servers": "mcp-servers",
"search-tools": "search-tools",
"tag-management": "tag-management",
"vector-stores": "vector-stores",
memory: "memory",
policies: "policies",
guardrails: "guardrails",
prompts: "prompts",
"tool-policies": "tool-policies",
skills: "skills",
// Legacy alias: the old switch matched ?page=claude-code-plugins for the same panel.
"claude-code-plugins": "skills",
caching: "caching",
"cost-tracking": "cost-tracking",
"transform-request": "transform-request",
"ui-theme": "ui-theme",
logs: "logs",
"admin-panel": "admin-panel",
"logging-and-alerts": "logging-and-alerts",
"model-hub-table": "model-hub-table",
// The modern usage dashboard; the legacy ?page=usage report routes to /old-usage.
new_usage: "usage",
usage: "old-usage",
"cost-optimization": "cost-optimization",
agents: "agents",
"router-settings": "router-settings",
users: "users",
teams: "teams",
organizations: "organizations",
};
function uiBase(): string {
// next dev serves the app at the root; only the proxy mounts the static export under /ui
// (and optionally under server_root_path). Inlined at build time, so production is unaffected.
if (process.env.NODE_ENV === "development") {
return "";
}
const root = serverRootPath && serverRootPath !== "/" ? `/${serverRootPath.replace(/^\/+|\/+$/g, "")}` : "";
return `${root}/ui`;
}
/** Absolute (same-origin) href for a migrated route segment, e.g. "api-reference" -> "/ui/api-reference". */
export function migratedHref(routeSegment: string): string {
return `${uiBase()}/${routeSegment.replace(/^\/+/, "")}`;
}
/** Href for a not-yet-migrated page, served by the legacy `?page=` switch at the UI root. */
export function legacyPageHref(pageKey: string): string {
return `${uiBase()}/?page=${pageKey}`;
}
/** Reverse-maps a path-routed location back to its legacy page id, e.g. "/ui/api-reference" -> "api_ref". */
export function legacyKeyForPathname(pathname: string): string | null {
const base = uiBase();
const rel = (pathname.startsWith(base) ? pathname.slice(base.length) : pathname).replace(/^\/+|\/+$/g, "");
for (const [key, segment] of Object.entries(MIGRATED_PAGES)) {
if (rel === segment) return key;
}
return null;
}