From adcdbfd5620edd7dac27a6ab8fb14e083b9bb584 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Wed, 25 Feb 2026 02:38:57 +1100 Subject: [PATCH] feat: implement AI Bridge client table column (#22228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #22144 Add client information column to AI Bridge request logs, showing which coding tool initiated each request with matching icons. - Added `Client` column to request logs table header and row, displaying client name with icon badge - Created `AIBridgeClientIcon` component mapping backend client constants to their icons (Claude Code, Codex, Kilo Code, Roo Code, Zed, Cursor, GitHub Copilot) ([ref.](https://github.com/coder/aibridge/blob/11fe0799402a652743104d047140fbeb28f02d24/bridge.go#L33-L41)) - Moved `AIBridgeModelIcon` and `AIBridgeProviderIcon` into `icons/` subdirectory and clean up `props.className` → `className` prop - Added new static icons: `github-copilot.svg`, `kilo-code.svg`, `roo-code.svg` with entries in `icons.json` and `externalImages.ts` - Sorted `externalImages.ts` map alphabetically | Name | Preview | | --- | --- | | GitHub Copilot | image 11 | | Claude | PREVIEW_CLAUDE | | Codex CLI | PREVIEW_CODEX | | Cursor | PREVIEW_CURSOR | | KiloCode | PREVIEW_KILO_CODE | | Roo Code | PREVIEW_ROO_CODE | | Zed | PREVIEW_ZED | --- .../RequestLogsPage/RequestLogsPageView.tsx | 1 + .../RequestLogsRow/RequestLogsRow.tsx | 58 +++++++++++--- .../RequestLogsPage/filter/filter.tsx | 2 +- .../icons/AIBridgeClientIcon.tsx | 76 +++++++++++++++++++ .../{ => icons}/AIBridgeModelIcon.tsx | 7 +- .../{ => icons}/AIBridgeProviderIcon.tsx | 9 ++- site/src/theme/externalImages.ts | 12 ++- site/src/theme/icons.json | 4 + site/static/icon/github-copilot.svg | 12 +++ site/static/icon/kilo-code.svg | 10 +++ site/static/icon/openai-codex.svg | 10 +++ site/static/icon/roo-code.svg | 3 + 12 files changed, 182 insertions(+), 22 deletions(-) create mode 100644 site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx rename site/src/pages/AIBridgePage/RequestLogsPage/{ => icons}/AIBridgeModelIcon.tsx (90%) rename site/src/pages/AIBridgePage/RequestLogsPage/{ => icons}/AIBridgeProviderIcon.tsx (79%) create mode 100644 site/static/icon/github-copilot.svg create mode 100644 site/static/icon/kilo-code.svg create mode 100644 site/static/icon/openai-codex.svg create mode 100644 site/static/icon/roo-code.svg diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx index f2a073c3e9..2ab55bf3df 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx @@ -50,6 +50,7 @@ export const RequestLogsPageView: FC = ({ Timestamp Initiator Tokens + Client Model Tool Calls diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx index 0bcab59612..7edfdba4ec 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx @@ -12,7 +12,8 @@ import { ArrowDownIcon, ArrowUpIcon, ChevronRightIcon } from "lucide-react"; import { type FC, Fragment, useState } from "react"; import { cn } from "utils/cn"; import { formatDate, humanDuration } from "utils/time"; -import { AIBridgeModelIcon } from "../AIBridgeModelIcon"; +import { AIBridgeClientIcon } from "../icons/AIBridgeClientIcon"; +import { AIBridgeModelIcon } from "../icons/AIBridgeModelIcon"; type RequestLogsRowProps = { interception: AIBridgeInterception; @@ -191,10 +192,32 @@ export const RequestLogsRow: FC = ({ interception }) => { - - - -
+
+ + + + +
+ +
+ + {interception.client ?? "Unknown"} + +
+
+ {interception.client} +
+
+
+ + +
+ + +
= ({ interception }) => { {interception.model} -
-
- {interception.model} -
-
+ + {interception.model} + + +
{toolCalls} @@ -268,6 +291,21 @@ export const RequestLogsRow: FC = ({ interception }) => { +
Client:
+
+ +
+ +
+ + {interception.client ?? "Unknown"} + +
+
+
Model:
diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx index e67171b11c..21b7fb1394 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx @@ -7,7 +7,7 @@ import { type SelectFilterOption, } from "components/Filter/SelectFilter"; import type { FC } from "react"; -import { AIBridgeProviderIcon } from "../AIBridgeProviderIcon"; +import { AIBridgeProviderIcon } from "../icons/AIBridgeProviderIcon"; const AIBRIDGE_PROVIDERS: SelectFilterOption[] = [ { diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx new file mode 100644 index 0000000000..0636add37e --- /dev/null +++ b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx @@ -0,0 +1,76 @@ +import type { AIBridgeInterception } from "api/typesGenerated"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; +import { CircleQuestionMarkIcon } from "lucide-react"; +import { cn } from "utils/cn"; + +export const AIBridgeClientIcon = ({ + client, + className, + ...props +}: { + client: AIBridgeInterception["client"]; +} & React.ComponentProps<"svg">) => { + const iconClassName = "flex-shrink-0"; + // This should be kept in sync with the client names in + // the AI Bridge bridge.go file. + // https://github.com/coder/aibridge/blob/main/bridge.go#L31-L32 + switch (client) { + case "Claude Code": + return ( + + ); + case "Codex": + return ( + + ); + case "Kilo Code": + return ( + + ); + case "Roo Code": + return ( + + ); + case "Zed": + return ( + + ); + case "Cursor": + return ( + + ); + case "GitHub Copilot (VS Code)": + case "GitHub Copilot (CLI)": + return ( + + ); + default: + return ( + + ); + } +}; diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeModelIcon.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeModelIcon.tsx similarity index 90% rename from site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeModelIcon.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeModelIcon.tsx index 2a3b4aef29..337510500c 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeModelIcon.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeModelIcon.tsx @@ -33,6 +33,7 @@ function inferModelFamily(model: string): string { export const AIBridgeModelIcon = ({ model, + className, ...props }: { model: string; @@ -44,20 +45,20 @@ export const AIBridgeModelIcon = ({ return ( ); case "openai": return ( ); default: return ( ); diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeProviderIcon.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeProviderIcon.tsx similarity index 79% rename from site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeProviderIcon.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeProviderIcon.tsx index e4cd3f40ef..4208917d36 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/AIBridgeProviderIcon.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeProviderIcon.tsx @@ -5,6 +5,7 @@ import { cn } from "utils/cn"; export const AIBridgeProviderIcon = ({ provider, + className, ...props }: { provider: AIBridgeInterception["provider"]; @@ -15,27 +16,27 @@ export const AIBridgeProviderIcon = ({ return ( ); case "anthropic": return ( ); case "copilot": return ( ); default: return ( ); diff --git a/site/src/theme/externalImages.ts b/site/src/theme/externalImages.ts index e1afd95bc1..dfdd328036 100644 --- a/site/src/theme/externalImages.ts +++ b/site/src/theme/externalImages.ts @@ -145,9 +145,9 @@ export const defaultParametersForBuiltinIcons = new Map([ ["/icon/auggie.svg", "monochrome"], ["/icon/anthropic.svg", "monochrome"], ["/icon/auto-dev-server.svg", "monochrome"], + ["/icon/aws-monochrome.svg", "monochrome"], ["/icon/aws.png", "whiteWithColor&brightness=1.5"], ["/icon/aws.svg", "whiteWithColor&brightness=1.5"], - ["/icon/aws-monochrome.svg", "monochrome"], ["/icon/coder.svg", "monochrome"], ["/icon/container.svg", "monochrome"], ["/icon/copyparty.svg", "blackWithColor"], @@ -155,22 +155,26 @@ export const defaultParametersForBuiltinIcons = new Map([ ["/icon/docker-white.svg", "monochrome"], ["/icon/folder.svg", "monochrome"], ["/icon/gemini-monochrome.svg", "monochrome"], + ["/icon/github-copilot.svg", "whiteWithColor"], ["/icon/github.svg", "monochrome"], ["/icon/image.svg", "monochrome"], ["/icon/jupyter.svg", "blackWithColor"], ["/icon/kasmvnc.svg", "whiteWithColor"], + ["/icon/kilo-code.svg", "blackWithColor"], ["/icon/kiro.svg", "whiteWithColor"], ["/icon/memory.svg", "monochrome"], ["/icon/mux.svg", "monochrome"], ["/icon/nexus-repository.svg", "blackWithColor"], ["/icon/okta.svg", "monochrome"], + ["/icon/openai-codex.svg", "monochrome"], ["/icon/openai.svg", "monochrome"], + ["/icon/openwebui.svg", "monochrome"], + ["/icon/perplexica.svg", "monochrome"], + ["/icon/roo-code.svg", "whiteWithColor"], ["/icon/rust.svg", "monochrome"], + ["/icon/tasks.svg", "monochrome"], ["/icon/terminal.svg", "monochrome"], ["/icon/widgets.svg", "monochrome"], ["/icon/windsurf.svg", "monochrome"], ["/icon/zed.svg", "monochrome"], - ["/icon/tasks.svg", "monochrome"], - ["/icon/openwebui.svg", "monochrome"], - ["/icon/perplexica.svg", "monochrome"], ]); diff --git a/site/src/theme/icons.json b/site/src/theme/icons.json index b10b6d8261..8cb169c2b9 100644 --- a/site/src/theme/icons.json +++ b/site/src/theme/icons.json @@ -61,6 +61,7 @@ "gemini.svg", "git.svg", "gitea.svg", + "github-copilot.svg", "github.svg", "gitlab.svg", "go.svg", @@ -80,6 +81,7 @@ "k8s.svg", "kasmvnc.svg", "keycloak.svg", + "kilo-code.svg", "kiro.svg", "kotlin.svg", "lakefs.svg", @@ -98,6 +100,7 @@ "nomad.svg", "novnc.svg", "okta.svg", + "openai-codex.svg", "openai.svg", "opencode.svg", "openwebui.svg", @@ -114,6 +117,7 @@ "rdp.svg", "rider.svg", "rockylinux.svg", + "roo-code.svg", "rstudio.svg", "ruby.png", "ruby.svg", diff --git a/site/static/icon/github-copilot.svg b/site/static/icon/github-copilot.svg new file mode 100644 index 0000000000..c140638217 --- /dev/null +++ b/site/static/icon/github-copilot.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/site/static/icon/kilo-code.svg b/site/static/icon/kilo-code.svg new file mode 100644 index 0000000000..7f0eecbda6 --- /dev/null +++ b/site/static/icon/kilo-code.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/site/static/icon/openai-codex.svg b/site/static/icon/openai-codex.svg new file mode 100644 index 0000000000..edbd9ba996 --- /dev/null +++ b/site/static/icon/openai-codex.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/site/static/icon/roo-code.svg b/site/static/icon/roo-code.svg new file mode 100644 index 0000000000..b733fe1eb6 --- /dev/null +++ b/site/static/icon/roo-code.svg @@ -0,0 +1,3 @@ + + +