Skip to content

Show agent-network prompt-cache tokens and cost on hover#731

Merged
braginini merged 8 commits into
mainfrom
anet-cache-cost-tooltip
Jul 26, 2026
Merged

Show agent-network prompt-cache tokens and cost on hover#731
braginini merged 8 commits into
mainfrom
anet-cache-cost-tooltip

Conversation

@mlsmaycon

@mlsmaycon mlsmaycon commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Hovering the access-log Tokens cell now breaks out prompt-cache read/write tokens (and a cache-aware total), the Cost cells show the cache share of the metered cost, and the usage overview chart and daily table surface the day's cache tokens and cache cost the same way. Backed by the new cached_input_tokens / cache_creation_tokens / cache_cost_usd fields from netbirdio/netbird#6900; entries from older management servers render unchanged.

Issue ticket number and link

N/A — pairs with netbirdio/netbird#6900

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

netbirdio/docs#886

E2E tests

Optional: override the image tags used by the Playwright e2e workflow.
Defaults to main when omitted.

management-cloud-tag: main
reverse-proxy-tag: main

Summary by CodeRabbit

  • New Features
    • Added prompt-cache token and cost breakdowns across Agent Network access logs, session views, and the daily usage chart/tables.
    • Token and cost calculations now include cache read/write and expose cache details via improved hover tooltips (including per-bucket cost lines when available).
  • Bug Fixes
    • Cache metrics now gracefully default when missing, ensuring totals and tooltips remain accurate.
  • Tests
    • Added end-to-end coverage for cache-aware totals and tooltip breakdowns on access logs and the usage overview.

The access-log tokens tooltip gains cache read/write rows and a
cache-aware total, the cost cells break out the cache share of the
metered cost on hover, and the usage overview chart and daily table
surface the day's cache tokens and cache cost the same way. Data comes
from the new cached_input_tokens / cache_creation_tokens /
cache_cost_usd API fields; entries from older management servers
render unchanged.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Prompt-cache token and cost fields are added to API mappings and dashboard models, then displayed in access-log metadata, token and cost cells, daily usage tables, chart tooltips, and end-to-end tests.

Changes

Prompt-cache accounting

Layer / File(s) Summary
API contracts and cache mapping
src/modules/agent-network/agentAccessLogApi.ts, src/modules/agent-network/data/mockData.ts
API and dashboard types now include optional cached token and cost fields, which are mapped with zero defaults.
Expanded-row metadata
src/modules/agent-network/AgentAccessLogExpandedRow.tsx
Expanded metadata includes cached token counts, cache cost, per-bucket costs, and cached tokens in the total.
Access-log cache breakdowns
src/modules/agent-network/AgentAccessLogTable.tsx
Flat and session views include cache tokens in totals and show cache-specific token and cost tooltip breakdowns.
Daily usage cache breakdowns and validation
src/modules/agent-network/AgentOverviewPanel.tsx, e2e/tests/agent-network-cache-accounting.spec.ts
Daily buckets, continuity fillers, tables, charts, and Playwright tests expose and verify cached token and cost metrics.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant APIAgentNetworkAccessLog
  participant accessLogFromAgentAPI
  participant AgentAccessLogTable
  participant AgentOverviewPanel
  participant Playwright
  APIAgentNetworkAccessLog->>accessLogFromAgentAPI: Return cache token and cost fields
  accessLogFromAgentAPI->>AgentAccessLogTable: Map cache fields into access-log models
  accessLogFromAgentAPI->>AgentOverviewPanel: Map cache fields into daily buckets
  AgentAccessLogTable-->>AgentAccessLogTable: Render token and cost breakdowns
  AgentOverviewPanel-->>AgentOverviewPanel: Render table and chart cache details
  Playwright->>AgentAccessLogTable: Verify access-log cache tooltips
  Playwright->>AgentOverviewPanel: Verify daily cache details
Loading

Suggested reviewers: braginini

Poem

A rabbit checks the token flow,
Cache reads hop and cache writes glow.
Costs split cleanly, totals rise,
Daily charts reveal surprise.
🐇 “Metadata’s ready—off we go!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main user-facing change: prompt-cache tokens and cost shown on hover for agent-network views.
Description check ✅ Passed The description matches the required template and includes the issue reference, docs selection with URL, and E2E tag fields.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch anet-cache-cost-tooltip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/agent-network/AgentAccessLogTable.tsx (1)

993-1007: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Distinguish additive and included cache-token shapes before computing totals. The dashboard maps cached_input_tokens/cache_creation_tokens directly from the API, but OpenAI-style cache accounting treats cached prompt tokens as a subset of input_tokens, so input + cacheRead can double-count that provider. Keep these totals consistent across TokensCell, the expanded-row metadata, and the cache-only row behavior, plus the session total used for sorting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/agent-network/AgentAccessLogTable.tsx` around lines 993 - 1007,
Update TokensCell in src/modules/agent-network/AgentAccessLogTable.tsx (lines
993-1007) to distinguish additive Anthropic cache buckets from OpenAI-style
included cache tokens before calculating totals, preserving cache-only rows.
Apply the same accounting in the expanded-row metadata in
src/modules/agent-network/AgentAccessLogExpandedRow.tsx (lines 29-48), and reuse
the same rule for the session total used by sorting in AgentAccessLogTable.tsx.
🧹 Nitpick comments (1)
src/modules/agent-network/AgentAccessLogExpandedRow.tsx (1)

44-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Metadata key naming inconsistency: plg.cost.usd_cache breaks the plg.llm.* prefix.

The new cache token keys use plg.llm.cached_input_tokens / plg.llm.cache_creation_tokens, matching the existing plg.llm.cost_usd, but the cache cost key uses a different namespace (plg.cost.usd_cache). Consider plg.llm.cache_cost_usd for consistency with the rest of the schema.

✏️ Proposed rename
-    metadata["plg.cost.usd_cache"] = (entry.cacheCostUsd ?? 0).toFixed(6);
+    metadata["plg.llm.cache_cost_usd"] = (entry.cacheCostUsd ?? 0).toFixed(6);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/agent-network/AgentAccessLogExpandedRow.tsx` around lines 44 -
47, Rename the cache cost metadata key in the cache metadata block from
plg.cost.usd_cache to plg.llm.cache_cost_usd, keeping the existing
cacheRead/cacheWrite condition and formatted cost value unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/agent-network/AgentAccessLogTable.tsx`:
- Around line 1079-1130: Update CostCell so the tooltip’s “input + output”
bucket is clamped to a minimum of zero when computing costUsd minus cache, while
preserving the existing cache and total displays.

In `@src/modules/agent-network/AgentOverviewPanel.tsx`:
- Around line 383-391: Update the afterBody callback to gate cache details
according to the selected metric: require positive cacheCost when metric is
"cost", and require positive cacheRead + cacheWrite otherwise. Preserve the
existing empty tooltip behavior and metric-specific formatting.
- Around line 214-237: Update the daily-table filtering predicate near the
token-column definition to consider cacheRead and cacheWrite alongside the
existing token and cost fields. Ensure days with only cache usage are retained,
while preserving the current exclusion behavior for completely empty days.

---

Outside diff comments:
In `@src/modules/agent-network/AgentAccessLogTable.tsx`:
- Around line 993-1007: Update TokensCell in
src/modules/agent-network/AgentAccessLogTable.tsx (lines 993-1007) to
distinguish additive Anthropic cache buckets from OpenAI-style included cache
tokens before calculating totals, preserving cache-only rows. Apply the same
accounting in the expanded-row metadata in
src/modules/agent-network/AgentAccessLogExpandedRow.tsx (lines 29-48), and reuse
the same rule for the session total used by sorting in AgentAccessLogTable.tsx.

---

Nitpick comments:
In `@src/modules/agent-network/AgentAccessLogExpandedRow.tsx`:
- Around line 44-47: Rename the cache cost metadata key in the cache metadata
block from plg.cost.usd_cache to plg.llm.cache_cost_usd, keeping the existing
cacheRead/cacheWrite condition and formatted cost value unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1549cb30-0867-440c-9545-6f973e8fe87b

📥 Commits

Reviewing files that changed from the base of the PR and between 5a5e04a and dddce9b.

📒 Files selected for processing (5)
  • src/modules/agent-network/AgentAccessLogExpandedRow.tsx
  • src/modules/agent-network/AgentAccessLogTable.tsx
  • src/modules/agent-network/AgentOverviewPanel.tsx
  • src/modules/agent-network/agentAccessLogApi.ts
  • src/modules/agent-network/data/mockData.ts

Comment on lines +1079 to +1130
// CostCell renders the metered USD cost with a hover breakdown of how much of
// it was billed for prompt-cache usage (cache read + write buckets).
function CostCell({
costUsd,
cacheCostUsd,
}: {
costUsd: number;
cacheCostUsd?: number;
}) {
const cache = cacheCostUsd ?? 0;
const display = (
<span
className={
"text-nb-gray-300 text-[0.82rem] px-3 py-2 font-mono whitespace-nowrap"
}
>
${costUsd.toFixed(4)}
</span>
);
if (cache <= 0) return display;
return (
<FullTooltip
content={
<div className={"text-xs flex flex-col gap-1 font-mono"}>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>
${(costUsd - cache).toFixed(4)}
</span>
<span className={"text-nb-gray-400 font-sans"}>input + output</span>
</div>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>${cache.toFixed(4)}</span>
<span className={"text-nb-gray-400 font-sans"}>cache</span>
</div>
<div
className={
"border-t border-nb-gray-800 mt-0.5 pt-1 flex items-center gap-2 text-nb-gray-400 whitespace-nowrap"
}
>
<span className={"font-medium text-nb-gray-200"}>
${costUsd.toFixed(4)}
</span>
<span className={"font-sans"}>total</span>
</div>
</div>
}
>
{display}
</FullTooltip>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp the "input + output" cost bucket to avoid a negative display.

costUsd - cache isn't clamped; if cacheCostUsd ever equals or exceeds costUsd (rounding, stale/inconsistent backend data), the tooltip would show $0.0000 or a negative dollar amount for "input + output".

🛡️ Proposed fix
-            <span className={"font-medium"}>
-              ${(costUsd - cache).toFixed(4)}
-            </span>
+            <span className={"font-medium"}>
+              ${Math.max(0, costUsd - cache).toFixed(4)}
+            </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// CostCell renders the metered USD cost with a hover breakdown of how much of
// it was billed for prompt-cache usage (cache read + write buckets).
function CostCell({
costUsd,
cacheCostUsd,
}: {
costUsd: number;
cacheCostUsd?: number;
}) {
const cache = cacheCostUsd ?? 0;
const display = (
<span
className={
"text-nb-gray-300 text-[0.82rem] px-3 py-2 font-mono whitespace-nowrap"
}
>
${costUsd.toFixed(4)}
</span>
);
if (cache <= 0) return display;
return (
<FullTooltip
content={
<div className={"text-xs flex flex-col gap-1 font-mono"}>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>
${(costUsd - cache).toFixed(4)}
</span>
<span className={"text-nb-gray-400 font-sans"}>input + output</span>
</div>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>${cache.toFixed(4)}</span>
<span className={"text-nb-gray-400 font-sans"}>cache</span>
</div>
<div
className={
"border-t border-nb-gray-800 mt-0.5 pt-1 flex items-center gap-2 text-nb-gray-400 whitespace-nowrap"
}
>
<span className={"font-medium text-nb-gray-200"}>
${costUsd.toFixed(4)}
</span>
<span className={"font-sans"}>total</span>
</div>
</div>
}
>
{display}
</FullTooltip>
);
}
// CostCell renders the metered USD cost with a hover breakdown of how much of
// it was billed for prompt-cache usage (cache read + write buckets).
function CostCell({
costUsd,
cacheCostUsd,
}: {
costUsd: number;
cacheCostUsd?: number;
}) {
const cache = cacheCostUsd ?? 0;
const display = (
<span
className={
"text-nb-gray-300 text-[0.82rem] px-3 py-2 font-mono whitespace-nowrap"
}
>
${costUsd.toFixed(4)}
</span>
);
if (cache <= 0) return display;
return (
<FullTooltip
content={
<div className={"text-xs flex flex-col gap-1 font-mono"}>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>
${Math.max(0, costUsd - cache).toFixed(4)}
</span>
<span className={"text-nb-gray-400 font-sans"}>input + output</span>
</div>
<div className={"flex items-center gap-2 whitespace-nowrap"}>
<span className={"font-medium"}>${cache.toFixed(4)}</span>
<span className={"text-nb-gray-400 font-sans"}>cache</span>
</div>
<div
className={
"border-t border-nb-gray-800 mt-0.5 pt-1 flex items-center gap-2 text-nb-gray-400 whitespace-nowrap"
}
>
<span className={"font-medium text-nb-gray-200"}>
${costUsd.toFixed(4)}
</span>
<span className={"font-sans"}>total</span>
</div>
</div>
}
>
{display}
</FullTooltip>
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/agent-network/AgentAccessLogTable.tsx` around lines 1079 - 1130,
Update CostCell so the tooltip’s “input + output” bucket is clamped to a minimum
of zero when computing costUsd minus cache, while preserving the existing cache
and total displays.

Comment on lines +214 to +237
accessorFn: (row) =>
row.input + row.output + row.cacheRead + row.cacheWrite,
header: ({ column }) => (
<DataTableHeader column={column}>Total Tokens</DataTableHeader>
),
cell: ({ row }) => (
<NumberCell value={row.original.input + row.original.output} />
),
// Total includes the additive prompt-cache buckets; hover breaks them out.
cell: ({ row }) => {
const d = row.original;
const total = d.input + d.output + d.cacheRead + d.cacheWrite;
if (d.cacheRead + d.cacheWrite <= 0)
return <NumberCell value={total} />;
return (
<FullTooltip
content={
<div className={"text-xs flex flex-col gap-1 font-mono"}>
<span>{`cache read: ${d.cacheRead.toLocaleString()}`}</span>
<span>{`cache write: ${d.cacheWrite.toLocaleString()}`}</span>
</div>
}
>
<NumberCell value={total} />
</FullTooltip>
);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep cache-only days in the daily table.

The existing predicate on Line 174 ignores cacheRead and cacheWrite, so a day with only cache usage and no reported legacy tokens/cost is omitted entirely.

Proposed fix
-    () => daily.filter((d) => d.input + d.output > 0 || d.cost > 0),
+    () =>
+      daily.filter(
+        (d) =>
+          d.input + d.output + d.cacheRead + d.cacheWrite > 0 ||
+          d.cost > 0 ||
+          d.cacheCost > 0,
+      ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/agent-network/AgentOverviewPanel.tsx` around lines 214 - 237,
Update the daily-table filtering predicate near the token-column definition to
consider cacheRead and cacheWrite alongside the existing token and cost fields.
Ensure days with only cache usage are retained, while preserving the current
exclusion behavior for completely empty days.

Comment thread src/modules/agent-network/AgentOverviewPanel.tsx Outdated
Hermetic Playwright spec: mocks the access-log and usage-overview
endpoints with rows carrying the cache fields and asserts the Tokens
tooltip's cache read/write rows and cache-aware total, the Cost
tooltip's cache share, and the daily table's cache breakdowns.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/tests/agent-network-cache-accounting.spec.ts`:
- Line 111: Replace the direct page.goto calls in the access-log navigation
steps with the navigateTo helper, including the occurrence around the second
referenced transition. Pass the existing page object and target path so
setup-modal dismissal and scroll-lock cleanup run before assertions.
- Around line 79-97: Update newUsagePage to use the shared dashboardAsOwner
authenticated fixture from helpers/fixtures.ts instead of creating a raw browser
context and calling loginToApp. Apply the localStorage initialization and
mockCacheUsage to the fixture-provided page, and preserve the returned page and
close behavior using the fixture’s cleanup mechanism.
- Around line 101-103: Replace role-, formatted-text-, and exact-text-based
selectors throughout the access-log and usage-overview tests with stable
page.getByTestId() selectors, including the tooltip() helper, row selection,
hover targets, totals, costs, and tooltip assertions. Use the corresponding test
IDs already exposed by the tested UI and preserve the existing test behavior.
- Around line 111-114: Update the access-logs test around the navigation and
writeRow assertion to await page.waitForResponse promises for every mocked cache
API endpoint, using explicit timeouts and matching HTTP methods and URLs, before
inspecting the table. Replace role/text-based row selection with the spec’s
data-testid selectors while preserving the existing visibility assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 47fe9270-4175-45e7-977b-64d8eb4f95a4

📥 Commits

Reviewing files that changed from the base of the PR and between dddce9b and f30d83c.

📒 Files selected for processing (1)
  • e2e/tests/agent-network-cache-accounting.spec.ts

Comment thread e2e/tests/agent-network-cache-accounting.spec.ts
Comment thread e2e/tests/agent-network-cache-accounting.spec.ts
Comment thread e2e/tests/agent-network-cache-accounting.spec.ts Outdated
Comment thread e2e/tests/agent-network-cache-accounting.spec.ts Outdated
mlsmaycon and others added 6 commits July 26, 2026 04:45
@braginini
braginini merged commit 6ca9128 into main Jul 26, 2026
6 checks passed
@braginini
braginini deleted the anet-cache-cost-tooltip branch July 26, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants