Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 0 additions & 21 deletions gui/src/format-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,3 @@ export function formatTokens(n: number, locale: string): string {
if (n < 1_000_000_000_000) return `${trim((n / 1_000_000_000).toFixed(1))}B`;
return `${trim((n / 1_000_000_000_000).toFixed(1))}T`;
}

/**
* A token total with its cached subset beside it: `5.8만 c5.7만`, `58K c57K`.
*
* A cached request's total is mostly cache. Printing the total alone makes a
* 58,000-token prompt that is 57,000 cache read and 1,000 fresh look like an
* ordinary 58,000-token prompt, and it reads as a different, smaller request
* than the log row directly below it, which already shows the companion. The
* `c` marker matches the `logs.tokens.cacheRead` label, which reads
* "cache read (c)".
*
* The companion is omitted only when there is no cache to report. It is NOT
* omitted when the cached subset equals the total: a turn served entirely from
* cache is the most interesting row on the page, and hiding its marker there
* would blank exactly the case this exists for.
*/
export function formatTokensWithCache(total: number, cached: number | undefined, locale: string): string {
const base = formatTokens(total, locale);
if (cached === undefined || !Number.isFinite(cached) || cached <= 0) return base;
return `${base} c${formatTokens(cached, locale)}`;
}
15 changes: 1 addition & 14 deletions gui/src/pages/Logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { useI18n, LOCALES, type TFn } from "../i18n/shared";
import { formatProviderDisplayName } from "../provider-icons";
import { formatTokens } from "../format-tokens";
import { formatTokensWithCache } from "../format-tokens";
import { hashLogConversationQuery } from "../log-conversation-id";
import { statusCodeInfo } from "../status-codes";
import { IconX } from "../icons";
Expand Down Expand Up @@ -364,27 +363,19 @@ function formatLogDateTime(ts: number, localeTag?: string, timeZone?: string): s
function summarizeFilteredLogs(entries: LogEntry[]): {
requests: number;
totalTokens: number;
cachedInputTokens: number;
estimatedCostUsd: number;
priorityLowerBound: boolean;
unpricedRequests: number;
unmeteredRequests: number;
} {
let totalTokens = 0;
let cachedInputTokens = 0;
for (const entry of entries) {
const tokens = displayTokenTotal(entry);
if (tokens !== undefined) totalTokens += tokens;
// The banner sits directly above rows that already print `c <read>`, so a
// total with no companion read as a different, smaller figure than the rows
// it summarizes.
const read = cacheSplit(entry).read;
if (read !== undefined && read > 0) cachedInputTokens += read;
}
return {
requests: entries.length,
totalTokens,
cachedInputTokens,
...summarizeEstimatedCosts(entries),
};
}
Expand Down Expand Up @@ -696,11 +687,7 @@ export default function Logs({ apiBase }: { apiBase: string }) {
<Notice tone="ok">
{t("logs.conversation.totals", {
requests: conversationTotals.requests,
tokens: formatTokensWithCache(
conversationTotals.totalTokens,
conversationTotals.cachedInputTokens,
localeTag ?? locale,
),
tokens: formatTokens(conversationTotals.totalTokens, localeTag ?? locale),
cost: formatEstimatedUsdValue(
conversationTotals.estimatedCostUsd,
t,
Expand Down
12 changes: 3 additions & 9 deletions gui/src/pages/Usage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { useI18n, type TFn, type Locale } from "../i18n/shared";
import { formatProviderDisplayName } from "../provider-icons";
import { formatTokens, formatTokensWithCache } from "../format-tokens";
import { formatTokens } from "../format-tokens";
import { formatEstimatedUsdValue as formatUsdEstimate } from "../intl-formatters";
import { readSessionListCache, writeSessionListCache } from "../session-list-cache";
import { EmptyState, Notice } from "../ui";
Expand Down Expand Up @@ -64,10 +64,6 @@ interface UsageModel {
totalTokens: number;
inputTokens: number;
outputTokens: number;
// /api/usage has carried these all along; dropping them from the row type is
// what left the token column without its cached companion.
cachedInputTokens?: number;
cacheReadInputTokens?: number;
shareRatio: number;
}

Expand All @@ -78,8 +74,6 @@ interface UsageProvider {
reportedRequests: number;
estimatedRequests: number;
totalTokens: number;
cachedInputTokens?: number;
cacheReadInputTokens?: number;
shareRatio: number;
}

Expand Down Expand Up @@ -567,7 +561,7 @@ function UsageModelsTable({
<td className="muted">{formatProviderDisplayName(model.provider, t)}</td>
<td className="num">{model.requests}</td>
<td className="num">{model.measuredRequests}</td>
<td className="num mono">{formatTokensWithCache(model.totalTokens, model.cacheReadInputTokens ?? model.cachedInputTokens, locale)}</td>
<td className="num mono">{formatTokens(model.totalTokens, locale)}</td>
<td><div className="usage-bar"><div className="usage-bar-fill" style={{ width: `${Math.round(model.shareRatio * 100)}%` }} /></div></td>
</tr>
))}
Expand Down Expand Up @@ -627,7 +621,7 @@ function UsageProvidersTable({
<td className="mono">{formatProviderDisplayName(provider.provider, t)}</td>
<td className="num">{provider.requests}</td>
<td className="num">{provider.measuredRequests}</td>
<td className="num mono">{formatTokensWithCache(provider.totalTokens, provider.cacheReadInputTokens ?? provider.cachedInputTokens, locale)}</td>
<td className="num mono">{formatTokens(provider.totalTokens, locale)}</td>
<td><div className="usage-bar"><div className="usage-bar-fill" style={{ width: `${Math.round(provider.shareRatio * 100)}%` }} /></div></td>
</tr>
))}
Expand Down
10 changes: 2 additions & 8 deletions gui/src/pages/dashboard-overview-head.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IconAlert, IconInfo } from "../icons";
import { type TKey, useT } from "../i18n/shared";
import { formatTokensWithCache } from "../format-tokens";
import { formatTokens } from "../format-tokens";
import { formatUptime } from "../formatUptime";
import { navigateHash } from "../hash-routing";
import type { useDashboardData } from "./use-dashboard-data";
Expand Down Expand Up @@ -81,13 +81,7 @@ export function DashboardOverviewHead({
<div className="stat" aria-busy={healthLoading || undefined}><div className="label">{t("dash.providers")}</div><div className="value">{providers.length}</div></div>
<div className="stat" aria-busy={usageLoading || undefined}>
<div className="label">{t("dash.tokens30d")}</div>
<div className="value mono">{usage30d && usage30d.summary.requests > 0
? formatTokensWithCache(
usage30d.summary.totalTokens,
usage30d.summary.cacheReadInputTokens ?? usage30d.summary.cachedInputTokens,
locale,
)
: "—"}</div>
<div className="value mono">{usage30d && usage30d.summary.requests > 0 ? formatTokens(usage30d.summary.totalTokens, locale) : "—"}</div>
<div className="muted text-label dash-stat-coverage">
{usage30d && usage30d.summary.requests > 0
? t("dash.coverage").replace("{pct}", `${Math.round(usage30d.summary.coverageRatio * 100)}%`)
Expand Down
12 changes: 1 addition & 11 deletions gui/src/pages/dashboard-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,7 @@ export interface SidecarPatch {
};
}
export interface ShadowCallData { enabled: boolean; model: string; sourceModels?: string[] }
export interface UsageSummary30d {
summary: {
requests: number;
totalTokens: number;
coverageRatio: number;
// Already on /api/usage; the tile showed a bare total only because this
// type dropped them.
cachedInputTokens?: number;
cacheReadInputTokens?: number;
};
}
export interface UsageSummary30d { summary: { requests: number; totalTokens: number; coverageRatio: number } }
export type UpdateChannel = "latest" | "preview";
export type Installer = "npm" | "bun" | "source";
export type UpdateJobStatus = "running" | "restarting" | "succeeded" | "failed";
Expand Down
1 change: 0 additions & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,6 @@
"grok-sync.test.ts": "providers/xai",
"grok-writer-boundary.test.ts": "providers/xai",
"gui-api-error.test.ts": "gui",
"gui-format-tokens-cache.test.ts": "gui",
"gui-management-session.test.ts": "gui",
"gui-pair-capability.test.ts": "gui",
"gui-pair-client.test.ts": "gui",
Expand Down
36 changes: 3 additions & 33 deletions src/cli/usage-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ interface CostRow {
model?: string;
requests: number;
totalTokens: number;
/**
* Cache-read subset of the row's tokens. The API sends it; the CLI dropped it,
* so a mostly-cached provider's TOKENS column read as an ordinary total while
* the summary line two rows above already said `cached N`.
*/
cachedInputTokens?: number;
cacheReadInputTokens?: number;
estimatedCostUsd?: number;
}

Expand Down Expand Up @@ -60,8 +53,6 @@ interface UsageReportInput {
ambiguous?: boolean;
requests: number;
totalTokens: number;
cachedInputTokens?: number;
cacheReadInputTokens?: number;
estimatedCostUsd?: number;
}[];
}
Expand All @@ -85,16 +76,6 @@ function count(value: number | undefined): string {
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "—";
}

/**
* A row's token total with its cache-read subset, matching the summary line's
* `cached N` wording rather than inventing a second vocabulary for the tables.
*/
function countWithCache(total: number | undefined, cached: number | undefined): string {
const base = count(total);
if (typeof cached !== "number" || !Number.isFinite(cached) || cached <= 0) return base;
return `${base} (cached ${count(cached)})`;
}

/**
* Matches the dashboard's `~$` with four fraction digits. Estimates below a
* hundredth of a cent still read as a number rather than collapsing to $0.00,
Expand Down Expand Up @@ -160,12 +141,7 @@ export function formatUsageReport(data: UsageReportInput): string[] {
lines.push("");
lines.push(...table(
["PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
providers.map(row => [
row.provider,
count(row.requests),
countWithCache(row.totalTokens, row.cacheReadInputTokens ?? row.cachedInputTokens),
usd(row.estimatedCostUsd),
]),
providers.map(row => [row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
));
}

Expand All @@ -187,7 +163,7 @@ export function formatUsageReport(data: UsageReportInput): string[] {
// wrong conclusion. Mark it rather than presenting it as a single identity.
row.ambiguous ? `${terminalText(row.accountLogLabel)} (ambiguous)` : terminalText(row.accountLogLabel),
count(row.requests),
countWithCache(row.totalTokens, row.cacheReadInputTokens ?? row.cachedInputTokens),
count(row.totalTokens),
usd(row.estimatedCostUsd),
]),
));
Expand All @@ -199,13 +175,7 @@ export function formatUsageReport(data: UsageReportInput): string[] {
const shown = models.slice(0, MAX_MODEL_ROWS);
lines.push(...table(
["MODEL", "PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
shown.map(row => [
row.model ?? "-",
row.provider,
count(row.requests),
countWithCache(row.totalTokens, row.cacheReadInputTokens ?? row.cachedInputTokens),
usd(row.estimatedCostUsd),
]),
shown.map(row => [row.model ?? "-", row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
));
if (models.length > shown.length) {
lines.push(`... ${models.length - shown.length} more (use --json)`);
Expand Down
25 changes: 0 additions & 25 deletions tests/cli/cli-usage-report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,28 +404,3 @@ describe("ocx logs --follow output contract", () => {
}
});
});

describe("cached tokens in the per-row tables", () => {
test("a provider and model row name their cache-read subset", () => {
// The summary line has always said "cached N". The tables below it printed a
// bare total, so a mostly-cached provider looked like an ordinary one.
const lines = formatUsageReport({
range: "today",
summary: { requests: 1, totalTokens: 58_000, cachedInputTokens: 57_000 },
providers: [{ provider: "devin-cli", requests: 1, totalTokens: 58_000, cacheReadInputTokens: 57_000 }],
models: [{ provider: "devin-cli", model: "swe-2", requests: 1, totalTokens: 58_000, cachedInputTokens: 57_000 }],
}).join("\n");
expect(lines).toContain("58,000 (cached 57,000)");
expect(lines.match(/cached 57,000/g)?.length).toBeGreaterThanOrEqual(2);
});

test("a provider that reports no cache keeps a bare total", () => {
const lines = formatUsageReport({
range: "today",
summary: { requests: 1, totalTokens: 58_000 },
providers: [{ provider: "xai", requests: 1, totalTokens: 58_000 }],
}).join("\n");
expect(lines).toContain("58,000");
expect(lines).not.toContain("cached");
});
});
1 change: 0 additions & 1 deletion tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,6 @@
"grok-sync.test.ts": "providers/xai",
"grok-writer-boundary.test.ts": "providers/xai",
"gui-api-error.test.ts": "gui",
"gui-format-tokens-cache.test.ts": "gui",
"gui-management-session.test.ts": "gui",
"gui-pair-capability.test.ts": "gui",
"gui-pair-client.test.ts": "gui",
Expand Down
29 changes: 0 additions & 29 deletions tests/gui/gui-format-tokens-cache.test.ts

This file was deleted.

Loading