feat: implement AI Bridge client table column (#22228)

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 | <img width="1332" height="67" alt="image 11"
src="https://github.com/user-attachments/assets/0b06ea42-aaf9-431b-9f9f-3a0146d3eb44"
/> |
| Claude | <img width="1332" height="327" alt="PREVIEW_CLAUDE"
src="https://github.com/user-attachments/assets/7e1afcbc-b94b-4017-bbdc-f40e0ca237d8"
/> |
| Codex CLI | <img width="1332" height="67" alt="PREVIEW_CODEX"
src="https://github.com/user-attachments/assets/2a9ffde1-2516-4d81-baf0-6e689d8a37bf"
/> |
| Cursor | <img width="1332" height="67" alt="PREVIEW_CURSOR"
src="https://github.com/user-attachments/assets/2c4883e8-35cd-4b08-8463-82ba7c95d96d"
/> |
| KiloCode | <img width="1332" height="132" alt="PREVIEW_KILO_CODE"
src="https://github.com/user-attachments/assets/e8bc2854-6fdb-47e0-a304-fb138ac0e2fe"
/> |
| Roo Code | <img width="1332" height="262" alt="PREVIEW_ROO_CODE"
src="https://github.com/user-attachments/assets/d2977090-525b-44ee-9ab6-e6019e559bbd"
/> |
| Zed | <img width="1332" height="67" alt="PREVIEW_ZED"
src="https://github.com/user-attachments/assets/1d982ae0-1d08-4b85-8b4a-5c13fb7754f1"
/> |
This commit is contained in:
Jake Howell
2026-02-25 02:38:57 +11:00
committed by GitHub
parent 1e274063d4
commit adcdbfd562
12 changed files with 182 additions and 22 deletions
@@ -50,6 +50,7 @@ export const RequestLogsPageView: FC<RequestLogsPageViewProps> = ({
<TableHead>Timestamp</TableHead>
<TableHead>Initiator</TableHead>
<TableHead>Tokens</TableHead>
<TableHead>Client</TableHead>
<TableHead>Model</TableHead>
<TableHead>Tool Calls</TableHead>
</TableRow>
@@ -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<RequestLogsRowProps> = ({ interception }) => {
</div>
</TableCell>
<TableCell className="w-40 max-w-40">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="min-w-0 overflow-hidden">
<div className="min-w-0 overflow-hidden">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge className="gap-1.5 max-w-full">
<div className="flex-shrink-0 flex items-center">
<AIBridgeClientIcon
client={interception.client}
className="size-icon-xs"
/>
</div>
<span className="truncate min-w-0">
{interception.client ?? "Unknown"}
</span>
</Badge>
</TooltipTrigger>
<TooltipContent>{interception.client}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</TableCell>
<TableCell className="w-40 max-w-40">
<div className="min-w-0 overflow-hidden">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge className="gap-1.5 max-w-full">
<div className="flex-shrink-0 flex items-center">
<AIBridgeModelIcon
@@ -206,11 +229,11 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
{interception.model}
</span>
</Badge>
</div>
</TooltipTrigger>
<TooltipContent>{interception.model}</TooltipContent>
</Tooltip>
</TooltipProvider>
</TooltipTrigger>
<TooltipContent>{interception.model}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</TableCell>
<TableCell className="w-32 text-center">{toolCalls}</TableCell>
</TableRow>
@@ -268,6 +291,21 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
</span>
</dd>
<dt>Client:</dt>
<dd data-chromatic="ignore">
<Badge className="gap-2">
<div className="flex-shrink-0 flex items-center">
<AIBridgeClientIcon
client={interception.client}
className="size-icon-xs"
/>
</div>
<span className="truncate min-w-0 w-full text-2xs">
{interception.client ?? "Unknown"}
</span>
</Badge>
</dd>
<dt>Model:</dt>
<dd data-chromatic="ignore">
<Badge className="gap-2">
@@ -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[] = [
{
@@ -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 (
<ExternalImage
src="/icon/claude.svg"
className={cn(iconClassName, className)}
/>
);
case "Codex":
return (
<ExternalImage
src="/icon/openai-codex.svg"
className={cn(iconClassName, className)}
/>
);
case "Kilo Code":
return (
<ExternalImage
src="/icon/kilo-code.svg"
className={cn(iconClassName, className)}
/>
);
case "Roo Code":
return (
<ExternalImage
src="/icon/roo-code.svg"
className={cn(iconClassName, className)}
/>
);
case "Zed":
return (
<ExternalImage
src="/icon/zed.svg"
className={cn(iconClassName, className)}
/>
);
case "Cursor":
return (
<ExternalImage
src="/icon/cursor.svg"
className={cn(iconClassName, className)}
/>
);
case "GitHub Copilot (VS Code)":
case "GitHub Copilot (CLI)":
return (
<ExternalImage
src="/icon/github-copilot.svg"
className={cn(iconClassName, className)}
/>
);
default:
return (
<CircleQuestionMarkIcon
className={cn(iconClassName, className)}
{...props}
/>
);
}
};
@@ -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 (
<ExternalImage
src="/icon/claude.svg"
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
/>
);
case "openai":
return (
<ExternalImage
src="/icon/openai.svg"
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
/>
);
default:
return (
<CircleQuestionMarkIcon
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
{...props}
/>
);
@@ -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 (
<ExternalImage
src="/icon/openai.svg"
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
/>
);
case "anthropic":
return (
<ExternalImage
src="/icon/claude.svg"
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
/>
);
case "copilot":
return (
<ExternalImage
src="/icon/github.svg"
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
/>
);
default:
return (
<CircleQuestionMarkIcon
className={cn(iconClassName, props.className)}
className={cn(iconClassName, className)}
{...props}
/>
);
+8 -4
View File
@@ -145,9 +145,9 @@ export const defaultParametersForBuiltinIcons = new Map<string, string>([
["/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<string, string>([
["/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"],
]);
+4
View File
@@ -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",
+12
View File
@@ -0,0 +1,12 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_80_10)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M255.118 180.6C245.94 196.419 192.649 233.743 128.002 233.743C63.3548 233.743 10.0628 196.419 0.88631 180.6C0.215186 179.444 -0.00140381 178.123 -0.00140381 176.788V148.629C-0.00140381 147.461 0.180177 146.301 0.6155 145.217C4.58422 135.33 14.978 120.967 28.3917 117.113C30.1701 112.583 32.8037 105.962 35.2612 101.074C34.8506 97.3342 34.7053 93.4765 34.7053 89.5791C34.7053 75.5 37.7138 63.149 46.7771 53.9523C51.0104 49.6573 56.2628 46.3617 62.4923 43.8857C77.4117 31.8598 98.6561 21.7429 127.768 21.7429C156.88 21.7429 178.585 31.8598 193.505 43.8857C199.734 46.3617 204.987 49.6573 209.22 53.9523C218.284 63.149 221.292 75.5 221.292 89.5791C221.292 93.4765 221.147 97.3342 220.736 101.074C223.193 105.962 225.827 112.583 227.605 117.113C241.02 120.967 251.413 135.33 255.382 145.217C255.817 146.301 255.999 147.461 255.999 148.629V176.788C255.999 178.123 255.788 179.444 255.118 180.6ZM136.671 86.1665C136.217 82.6591 136.001 79.5184 135.996 76.7124V76.4908C136.009 68.3507 137.803 63.0542 140.668 59.8012C144.305 55.6697 151.824 52.5048 167.671 54.2068C183.725 55.931 192.699 59.8845 197.786 65.0464C202.712 70.0449 205.3 77.5223 205.3 89.5791C205.3 102.39 203.439 109.958 199.348 114.563C195.458 118.941 187.796 122.503 171.018 122.503C158.119 122.503 150.743 118.34 146.029 112.583C140.966 106.402 138.117 97.3458 136.671 86.1665ZM119.33 86.1665C119.783 82.6591 119.999 79.5184 120.004 76.7124V76.4908C119.991 68.3507 118.197 63.0542 115.332 59.8012C111.695 55.6697 104.176 52.5048 88.3294 54.2068C72.2749 55.931 63.3012 59.8845 58.2136 65.0464C53.288 70.0449 50.6998 77.5223 50.6998 89.5791C50.6998 102.39 52.5605 109.958 56.6524 114.563C60.5424 118.941 68.2035 122.503 84.9825 122.503C97.8813 122.503 105.257 118.34 109.971 112.583C115.034 106.401 117.883 97.3458 119.33 86.1665ZM129.836 117.209C130.449 117.209 129.224 117.212 129.836 117.209C129.225 117.209 126.776 117.212 126.164 117.209C125.035 119.084 123.78 120.885 122.383 122.591C114.177 132.611 101.926 138.371 84.9815 138.371C66.5905 138.371 53.1124 134.573 44.6556 125.057C44.1749 124.516 43.7448 123.949 43.7448 123.949L42.7033 125.057V194.709C58.001 202.958 90.8366 217.761 128 217.761C165.163 217.761 197.999 202.958 213.297 194.709V125.057L212.255 123.949C212.255 123.949 211.903 124.428 211.344 125.057C202.888 134.573 189.409 138.371 171.018 138.371C154.074 138.371 141.823 132.611 133.617 122.591C132.221 120.885 130.965 119.084 129.836 117.209Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.999 148.743C107.969 148.743 111.999 152.888 111.999 158.001V180.484C111.999 185.598 107.969 189.743 102.999 189.743C98.0282 189.743 93.9986 185.598 93.9986 180.484V158.001C93.9986 152.888 98.0282 148.743 102.999 148.743Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M152.999 148.743C157.969 148.743 161.999 152.888 161.999 158.001V180.484C161.999 185.598 157.969 189.743 152.999 189.743C148.028 189.743 143.999 185.598 143.999 180.484V158.001C143.999 152.888 148.028 148.743 152.999 148.743Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_80_10">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_14_12)">
<path d="M0 0V256H256V0H0ZM237.037 237.037H18.963V18.963H237.037V237.037ZM156.444 184.089H180.148V203.052H150.349L137.481 190.184V160.385H156.444V184.089ZM199.111 184.089H180.148V160.385H156.444V141.422H186.243L199.111 154.29V184.089ZM118.519 156.445H99.5556V137.482H118.519V156.445ZM56.8889 137.482H75.8518V180.149H118.519V199.112H69.7566L56.8889 186.244V137.482ZM199.111 99.5556V118.519H137.481V99.5556H158.674V75.8518H137.481V56.8889H164.769L177.637 69.7566V99.5556H199.111ZM75.8518 78.2222H99.5556L118.519 97.1852V118.519H99.5556V97.1852H75.8518V118.519H56.8889V56.8889H75.8518V78.2222ZM118.519 78.2222H99.5556V56.8889H118.519V78.2222Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_14_12">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 903 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_118_8)">
<path d="M140.59 167.519H185.355C190.601 167.519 195.147 162.973 195.147 157.377C195.147 152.131 190.601 147.584 185.355 147.584H140.59C134.995 147.584 130.448 152.131 130.448 157.377C130.448 162.973 134.995 167.519 140.59 167.519ZM81.1361 171.016C84.6338 171.016 87.7811 168.918 89.5306 166.12L108.416 133.596C110.864 129.049 110.864 125.552 108.416 121.005L89.5306 88.4807C87.7811 85.6825 84.6338 83.9342 81.1361 83.9342C75.5409 83.9342 70.9945 88.4807 70.9945 93.7266C70.9945 96.1744 72.0432 98.2731 73.0932 100.021L89.1802 127.301L72.3936 155.977C71.3437 157.727 70.9945 159.126 70.9945 160.874C70.9945 166.469 75.5409 171.016 81.1361 171.016ZM127.999 235.716C68.5467 235.716 20.2843 187.453 20.2843 127.999C20.2843 68.5467 68.5467 20.2843 127.999 20.2843C187.453 20.2843 235.716 68.5467 235.716 127.999C235.716 187.453 187.453 235.716 127.999 235.716ZM127.999 256C198.645 256 256 198.645 256 127.999C256 57.3552 198.645 0 127.999 0C57.3552 0 0 57.3552 0 127.999C0 198.645 57.3552 256 127.999 256Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_118_8">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M213.7 51.4245L209.003 68.3546C208.755 69.2509 207.817 69.7688 206.923 69.503L128.149 46.0693C127.625 45.9135 127.058 46.024 126.632 46.365L48.6177 108.742C48.3899 108.924 48.1181 109.043 47.8296 109.088L1.4115 116.22C0.552004 116.352 -0.0607743 117.121 0.00480471 117.986L0.2065 120.645C0.270918 121.495 0.969693 122.158 1.82377 122.18L55.7403 123.569L56.3552 123.586L96.1945 102.369C96.7517 102.071 97.4307 102.115 97.9454 102.481L126.17 122.541C126.612 122.856 126.873 123.366 126.868 123.908L126.63 150.525C126.627 150.87 126.732 151.206 126.929 151.489L166.632 208.289C166.943 208.735 167.453 209 167.998 209H180.567C181.821 209 182.625 207.67 182.039 206.565L153.741 153.116C153.464 152.593 153.487 151.962 153.801 151.462L168.555 127.896C168.716 127.639 168.944 127.432 169.214 127.294L221.963 100.606C222.499 100.335 223.14 100.373 223.64 100.705L238.713 110.723C238.987 110.905 239.308 111.001 239.637 111.001H253.332C254.659 111.001 255.452 109.531 254.722 108.427L216.694 50.9528C215.907 49.7629 214.081 50.0505 213.7 51.4245Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB