From 2320e13cc410c73a7354eed693819431d13d8d9c Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 20 Jul 2026 11:58:04 +0000 Subject: [PATCH 1/4] feat(site): add network call badges to AI sessions table Surface the total and blocked Agent Firewall network calls on the AI sessions list. Sessions that did not pass through Agent Firewall show as "Disabled". Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ListSessionsPageView.stories.tsx | 7 ++ .../ListSessionsPage/ListSessionsPageView.tsx | 3 + .../ListSessionsRow.stories.tsx | 24 ++++++ .../ListSessionsPage/ListSessionsRow.tsx | 4 + .../NetworkCallBadges.stories.tsx | 54 ++++++++++++ .../pages/AIBridgePage/NetworkCallBadges.tsx | 84 +++++++++++++++++++ site/src/testHelpers/entities.ts | 4 + 7 files changed, 180 insertions(+) create mode 100644 site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx create mode 100644 site/src/pages/AIBridgePage/NetworkCallBadges.tsx diff --git a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.stories.tsx b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.stories.tsx index ede9692b8ef..ff32f8230c3 100644 --- a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.stories.tsx +++ b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.stories.tsx @@ -106,6 +106,13 @@ export const MultipleSessions: Story = { cache_read_input_tokens: 800 * (i + 1), cache_write_input_tokens: 50 * (i + 1), }, + network_calls: [ + { total: 23, blocked: 2 }, + { total: 5, blocked: 1 }, + { total: 0, blocked: 0 }, + undefined, + { total: 150, blocked: 0 }, + ][i], })), }, }; diff --git a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx index ed85496fb5f..dc9fc05a9f6 100644 --- a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx +++ b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx @@ -86,6 +86,9 @@ export const ListSessionsPageView: FC = ({ Provider Client In/Out Tokens + + Total/blocked network calls + Threads diff --git a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.stories.tsx b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.stories.tsx index 18db56d9385..7528e7ebb12 100644 --- a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.stories.tsx +++ b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.stories.tsx @@ -107,3 +107,27 @@ export const LargeTokenCounts: Story = { }, }, }; + +export const NetworkCallsBlocked: Story = { + args: { + session: { + ...MockSession, + network_calls: { total: 23, blocked: 2 }, + }, + }, +}; + +export const NoNetworkActivity: Story = { + args: { + session: { + ...MockSession, + network_calls: { total: 0, blocked: 0 }, + }, + }, +}; + +export const NetworkDisabled: Story = { + args: { + session: { ...MockSession, network_calls: undefined }, + }, +}; diff --git a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.tsx b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.tsx index a655964575e..82e2cf607b1 100644 --- a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.tsx +++ b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsRow.tsx @@ -13,6 +13,7 @@ import { import { AIBridgeClientIcon } from "#/pages/AIBridgePage/icons/AIBridgeClientIcon"; import { AIBridgeProviderIcon } from "#/pages/AIBridgePage/icons/AIBridgeProviderIcon"; import { DATE_FORMAT, formatDateTime } from "#/utils/time"; +import { NetworkCallBadges } from "../NetworkCallBadges"; import { TokenBadges } from "../TokenBadges"; import { getProviderDisplayName } from "../utils"; @@ -105,6 +106,9 @@ export const ListSessionsRow: FC = ({ /> + + + {session.threads} diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx new file mode 100644 index 00000000000..71281dcbed4 --- /dev/null +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx @@ -0,0 +1,54 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { NetworkCallBadges } from "./NetworkCallBadges"; + +const meta: Meta = { + title: "pages/AIBridgePage/NetworkCallBadges", + component: NetworkCallBadges, +}; + +export default meta; +type Story = StoryObj; + +export const TotalAndBlocked: Story = { + args: { + summary: { total: 23, blocked: 2 }, + }, +}; + +export const NoBlocked: Story = { + args: { + summary: { total: 23, blocked: 0 }, + }, +}; + +export const NoActivity: Story = { + args: { + summary: { total: 0, blocked: 0 }, + }, +}; + +export const Disabled: Story = { + args: { + summary: undefined, + }, +}; + +export const LargeCounts: Story = { + args: { + summary: { total: 12_480, blocked: 320 }, + }, +}; + +export const SizeXs: Story = { + args: { + size: "xs", + summary: { total: 23, blocked: 2 }, + }, +}; + +export const SizeMd: Story = { + args: { + size: "md", + summary: { total: 23, blocked: 2 }, + }, +}; diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx new file mode 100644 index 00000000000..45df879124c --- /dev/null +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx @@ -0,0 +1,84 @@ +import { BanIcon, InfoIcon } from "lucide-react"; +import type { FC } from "react"; +import type { AIBridgeSessionNetworkCallSummary } from "#/api/typesGenerated"; +import { Badge } from "#/components/Badge/Badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "#/components/Tooltip/Tooltip"; + +interface NetworkCallBadgesProps { + size?: "xs" | "sm" | "md"; + // summary is undefined when network call monitoring was not active for the + // session, which renders as "Disabled". + summary: AIBridgeSessionNetworkCallSummary | undefined; +} + +export const NetworkCallBadges: FC = ({ + size = "sm", + summary, +}) => { + if (!summary) { + return ( + + + + + Disabled + + + + + Network call monitoring was not active for this session. + + + + ); + } + + if (summary.total === 0) { + return ( + + No activity + + ); + } + + return ( + + + + + {summary.total.toLocaleString("en-US")} + + + {summary.blocked.toLocaleString("en-US")} + + + + +
+
+ Total calls + {summary.total.toLocaleString("en-US")} +
+
+ Blocked + {summary.blocked.toLocaleString("en-US")} +
+
+
+
+
+ ); +}; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 92f6ac40272..cf6674b2ed5 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -5523,6 +5523,10 @@ export const MockSession: TypesGen.AIBridgeSession = { cache_read_input_tokens: 980, cache_write_input_tokens: 120, }, + network_calls: { + total: 23, + blocked: 2, + }, last_prompt: "But *can* I really fix it?", last_active_at: "2026-03-09T10:28:15.03152Z", }; From 600384b7e882b9c540c67f5b7a5162786302ac68 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Tue, 21 Jul 2026 13:20:28 +0000 Subject: [PATCH 2/4] fix(site/src/pages/AIBridgePage): refine network calls column Apply frontend review feedback and Figma design refinements to the AI sessions network calls column: - Rename the column header to "Network Calls" and match sibling styling. - Fuse the total and blocked counts into a single two-segment panel, consistent with the In/Out Tokens column. - Color the blocked icon and count with the content-warning token. - Make both tooltips keyboard reachable (tabIndex plus sr-only label). - Remove the unused size prop and add keyboard interaction stories. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ListSessionsPage/ListSessionsPageView.tsx | 4 +-- .../NetworkCallBadges.stories.tsx | 27 +++++++++++++++---- .../pages/AIBridgePage/NetworkCallBadges.tsx | 26 +++++++++++------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx index dc9fc05a9f6..bdc65d47e89 100644 --- a/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx +++ b/site/src/pages/AIBridgePage/ListSessionsPage/ListSessionsPageView.tsx @@ -86,9 +86,7 @@ export const ListSessionsPageView: FC = ({ Provider Client In/Out Tokens - - Total/blocked network calls - + Network Calls Threads diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx index 71281dcbed4..5d3c47fcade 100644 --- a/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, screen, userEvent, waitFor } from "storybook/test"; import { NetworkCallBadges } from "./NetworkCallBadges"; const meta: Meta = { @@ -39,16 +40,32 @@ export const LargeCounts: Story = { }, }; -export const SizeXs: Story = { +// Tabbing to the badges reveals the breakdown tooltip without a mouse. +export const TotalAndBlockedKeyboard: Story = { args: { - size: "xs", summary: { total: 23, blocked: 2 }, }, + play: async () => { + await userEvent.tab(); + await waitFor(() => { + const tooltip = screen.getByRole("tooltip"); + expect(tooltip).toHaveTextContent("Total calls"); + expect(tooltip).toHaveTextContent("Blocked"); + }); + }, }; -export const SizeMd: Story = { +// Tabbing to the disabled indicator reveals the reason tooltip without a mouse. +export const DisabledKeyboard: Story = { args: { - size: "md", - summary: { total: 23, blocked: 2 }, + summary: undefined, + }, + play: async () => { + await userEvent.tab(); + await waitFor(() => + expect(screen.getByRole("tooltip")).toHaveTextContent( + "Network call monitoring was not active for this session.", + ), + ); }, }; diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx index 45df879124c..8797e1e2953 100644 --- a/site/src/pages/AIBridgePage/NetworkCallBadges.tsx +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx @@ -10,16 +10,12 @@ import { } from "#/components/Tooltip/Tooltip"; interface NetworkCallBadgesProps { - size?: "xs" | "sm" | "md"; // summary is undefined when network call monitoring was not active for the // session, which renders as "Disabled". summary: AIBridgeSessionNetworkCallSummary | undefined; } -export const NetworkCallBadges: FC = ({ - size = "sm", - summary, -}) => { +export const NetworkCallBadges: FC = ({ summary }) => { if (!summary) { return ( @@ -27,7 +23,8 @@ export const NetworkCallBadges: FC = ({ Disabled - + More info + = ({ - - {summary.total.toLocaleString("en-US")} - + + More info + + {summary.total.toLocaleString("en-US")} + + {summary.blocked.toLocaleString("en-US")} From d0a34f94904585264330329c5804beac3a10102b Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Tue, 21 Jul 2026 13:31:36 +0000 Subject: [PATCH 3/4] docs(ai-coder/ai-gateway): document network calls column in sessions list Add the Network Calls column to the Sessions list column enumeration and explain the total/blocked counts and the No activity and Disabled states. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/ai-coder/ai-gateway/audit.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/ai-coder/ai-gateway/audit.md b/docs/ai-coder/ai-gateway/audit.md index 9842af54683..9be227b6597 100644 --- a/docs/ai-coder/ai-gateway/audit.md +++ b/docs/ai-coder/ai-gateway/audit.md @@ -49,7 +49,12 @@ not just what was called. The sessions page (`http:///ai-gateway/sessions`) lists all sessions in reverse-chronological order. Each row shows the last prompt, initiator, provider, -client, token usage, thread count, and timestamp. +client, token usage, network calls, thread count, and timestamp. + +The network calls column reports the total and blocked +[Agent Firewall](../agent-firewall/index.md) calls for the session. It shows +`No activity` when the session made no calls, and `Disabled` when the session +did not pass through Agent Firewall, so no network calls were monitored. Select one to view its full details. From 87f998e3cb05039eda6700fc86c6207844440ee2 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 22 Jul 2026 11:57:19 +0000 Subject: [PATCH 4/4] fix(site/src/pages/AIBridgePage): improve network call badge accessibility Make the network call tooltip trigger a real button and use the shared InfoTooltip for the disabled indicator, so both expose an accessible name and full keyboard support instead of a role-less focusable span and svg. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../NetworkCallBadges.stories.tsx | 6 ++-- .../pages/AIBridgePage/NetworkCallBadges.tsx | 36 ++++++------------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx index 5d3c47fcade..62d9af8f4a8 100644 --- a/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.stories.tsx @@ -55,15 +55,17 @@ export const TotalAndBlockedKeyboard: Story = { }, }; -// Tabbing to the disabled indicator reveals the reason tooltip without a mouse. +// Tabbing to the disabled indicator's info button and pressing Enter reveals +// the reason popover without a mouse. export const DisabledKeyboard: Story = { args: { summary: undefined, }, play: async () => { await userEvent.tab(); + await userEvent.keyboard("{Enter}"); await waitFor(() => - expect(screen.getByRole("tooltip")).toHaveTextContent( + expect(screen.getByRole("dialog")).toHaveTextContent( "Network call monitoring was not active for this session.", ), ); diff --git a/site/src/pages/AIBridgePage/NetworkCallBadges.tsx b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx index 8797e1e2953..a4e621a94d0 100644 --- a/site/src/pages/AIBridgePage/NetworkCallBadges.tsx +++ b/site/src/pages/AIBridgePage/NetworkCallBadges.tsx @@ -1,7 +1,8 @@ -import { BanIcon, InfoIcon } from "lucide-react"; +import { BanIcon } from "lucide-react"; import type { FC } from "react"; import type { AIBridgeSessionNetworkCallSummary } from "#/api/typesGenerated"; import { Badge } from "#/components/Badge/Badge"; +import { InfoTooltip } from "#/components/InfoTooltip/InfoTooltip"; import { Tooltip, TooltipContent, @@ -18,24 +19,10 @@ interface NetworkCallBadgesProps { export const NetworkCallBadges: FC = ({ summary }) => { if (!summary) { return ( - - - - - Disabled - More info - - - - - Network call monitoring was not active for this session. - - - + + Disabled + + ); } @@ -51,12 +38,11 @@ export const NetworkCallBadges: FC = ({ summary }) => { - - More info {summary.total.toLocaleString("en-US")} @@ -68,7 +54,7 @@ export const NetworkCallBadges: FC = ({ summary }) => { {summary.blocked.toLocaleString("en-US")} - +