From 47a8c9572f579913209edddfddd6c71c5546781b Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 8 Jun 2026 06:26:30 +0200 Subject: [PATCH] feat: add OpenCode AI Bridge client support (#26098) Adds OpenCode to AI Bridge client detection so requests with user agents like `opencode/1.16.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14` show up as a first-class client instead of `Unknown`. This also wires the existing OpenCode frontend asset into the AIBridge UI, adds a Storybook story for the client icon, and updates the monitoring docs list of supported client values.
Coder Agents generated This pull request was generated by Coder Agents.
--- aibridge/client.go | 3 ++ aibridge/client_test.go | 5 +++ .../integrationtest/bridge_internal_test.go | 11 +++++++ docs/ai-coder/ai-gateway/monitoring.md | 1 + .../icons/AIBridgeClientIcon.stories.tsx | 31 +++++++++++++++++++ .../icons/AIBridgeClientIcon.tsx | 7 +++++ 6 files changed, 58 insertions(+) create mode 100644 site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.stories.tsx diff --git a/aibridge/client.go b/aibridge/client.go index 68caffdd30..0d0a492338 100644 --- a/aibridge/client.go +++ b/aibridge/client.go @@ -21,6 +21,7 @@ const ( ClientMux Client = "Mux" ClientRoo Client = "Roo Code" ClientCursor Client = "Cursor" + ClientOpenCode Client = "OpenCode" ClientUnknown Client = "Unknown" ) @@ -55,6 +56,8 @@ func GuessClient(r *http.Request) Client { return ClientCrush case r.Header.Get("x-cursor-client-version") != "": return ClientCursor + case strings.HasPrefix(userAgent, "opencode/"): + return ClientOpenCode } return ClientUnknown } diff --git a/aibridge/client_test.go b/aibridge/client_test.go index 985c254a26..253a374a69 100644 --- a/aibridge/client_test.go +++ b/aibridge/client_test.go @@ -99,6 +99,11 @@ func TestGuessClient(t *testing.T) { headers: map[string]string{"x-cursor-client-version": "abc123"}, wantClient: aibridge.ClientCursor, }, + { + name: "opencode", + userAgent: "opencode/1.16.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14", + wantClient: aibridge.ClientOpenCode, + }, { name: "unknown_client", userAgent: "ccclaude-cli/calude-with-wrong-prefix", diff --git a/aibridge/internal/integrationtest/bridge_internal_test.go b/aibridge/internal/integrationtest/bridge_internal_test.go index 9c75108685..7a8ae1116f 100644 --- a/aibridge/internal/integrationtest/bridge_internal_test.go +++ b/aibridge/internal/integrationtest/bridge_internal_test.go @@ -905,6 +905,17 @@ func TestSimple(t *testing.T) { userAgent: "codex_cli_rs/0.87.0 (Mac OS 26.2.0; arm64)", expectedClient: aibridge.ClientCodex, }, + { + name: config.ProviderOpenAI + "_opencode", + fixture: fixtures.OaiChatSimple, + basePath: "", + expectedPath: "/chat/completions", + getResponseIDFunc: getOpenAIResponseID, + path: pathOpenAIChatCompletions, + expectedMsgID: "chatcmpl-BwoiPTGRbKkY5rncfaM0s9KtWrq5N", + userAgent: "opencode/1.16.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14", + expectedClient: aibridge.ClientOpenCode, + }, { name: config.ProviderAnthropic + "_baseURL_path", fixture: fixtures.AntSimple, diff --git a/docs/ai-coder/ai-gateway/monitoring.md b/docs/ai-coder/ai-gateway/monitoring.md index 7b9e680905..9b982fb0af 100644 --- a/docs/ai-coder/ai-gateway/monitoring.md +++ b/docs/ai-coder/ai-gateway/monitoring.md @@ -94,6 +94,7 @@ Available query filters: - `Coder Agents` - `Mux` - `Cursor` + - `OpenCode` - `Unknown`
diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.stories.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.stories.tsx new file mode 100644 index 0000000000..f94c75c205 --- /dev/null +++ b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.stories.tsx @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { AIBridgeClientIcon } from "./AIBridgeClientIcon"; + +const meta: Meta = { + title: "pages/AIBridgePage/AIBridgeClientIcon", + component: AIBridgeClientIcon, + args: { + className: "size-8", + }, +}; + +export default meta; +type Story = StoryObj; + +export const OpenCode: Story = { + args: { + client: "OpenCode", + }, +}; + +export const ClaudeCode: Story = { + args: { + client: "Claude Code", + }, +}; + +export const Unknown: Story = { + args: { + client: "Unknown", + }, +}; diff --git a/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx index 120a494979..780a2fa8b0 100644 --- a/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx @@ -71,6 +71,13 @@ export const AIBridgeClientIcon = ({ className={cn(iconClassName, className)} /> ); + case "OpenCode": + return ( + + ); case "Charm Crush": return (