Show agent-network prompt-cache tokens and cost on hover#731
Conversation
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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPrompt-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. ChangesPrompt-cache accounting
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
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 liftDistinguish additive and included cache-token shapes before computing totals. The dashboard maps
cached_input_tokens/cache_creation_tokensdirectly from the API, but OpenAI-style cache accounting treats cached prompt tokens as a subset ofinput_tokens, soinput + cacheReadcan double-count that provider. Keep these totals consistent acrossTokensCell, 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 valueMetadata key naming inconsistency:
plg.cost.usd_cachebreaks theplg.llm.*prefix.The new cache token keys use
plg.llm.cached_input_tokens/plg.llm.cache_creation_tokens, matching the existingplg.llm.cost_usd, but the cache cost key uses a different namespace (plg.cost.usd_cache). Considerplg.llm.cache_cost_usdfor 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
📒 Files selected for processing (5)
src/modules/agent-network/AgentAccessLogExpandedRow.tsxsrc/modules/agent-network/AgentAccessLogTable.tsxsrc/modules/agent-network/AgentOverviewPanel.tsxsrc/modules/agent-network/agentAccessLogApi.tssrc/modules/agent-network/data/mockData.ts
| // 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> | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 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.
| // 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.
| 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> | ||
| ); | ||
| }, |
There was a problem hiding this comment.
🎯 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.
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
e2e/tests/agent-network-cache-accounting.spec.ts
The output count renders behind an sr-only prefix, so an exact text match never resolves; validated against a live render of the table.
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_usdfields 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:
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
mainwhen omitted.management-cloud-tag: main
reverse-proxy-tag: main
Summary by CodeRabbit