diff --git a/coderd/x/chatd/chatprovider/chatprovider.go b/coderd/x/chatd/chatprovider/chatprovider.go index 5862138b0c..a973848a18 100644 --- a/coderd/x/chatd/chatprovider/chatprovider.go +++ b/coderd/x/chatd/chatprovider/chatprovider.go @@ -655,6 +655,9 @@ func ResolveModelWithProviderHint(modelName, providerHint string) (provider stri if isChatModelForProvider(fantasyopenai.Name, normalized) { return fantasyopenai.Name, modelName, nil } + if isChatModelForProvider(fantasygoogle.Name, normalized) { + return fantasygoogle.Name, modelName, nil + } return "", "", xerrors.Errorf("unknown model %q", modelName) } diff --git a/coderd/x/chatd/chatprovider/chatprovider_test.go b/coderd/x/chatd/chatprovider/chatprovider_test.go index fea015db73..9b808d9820 100644 --- a/coderd/x/chatd/chatprovider/chatprovider_test.go +++ b/coderd/x/chatd/chatprovider/chatprovider_test.go @@ -11,6 +11,7 @@ import ( "charm.land/fantasy" fantasyanthropic "charm.land/fantasy/providers/anthropic" fantasybedrock "charm.land/fantasy/providers/bedrock" + fantasygoogle "charm.land/fantasy/providers/google" fantasyopenai "charm.land/fantasy/providers/openai" fantasyopenaicompat "charm.land/fantasy/providers/openaicompat" fantasyopenrouter "charm.land/fantasy/providers/openrouter" @@ -1622,6 +1623,34 @@ func TestResolveModelWithProviderHint(t *testing.T) { wantProvider: fantasyvercel.Name, wantModel: "claude-4-5-sonnet", }, + { + name: "BareGeminiModelResolvesToGoogle", + modelName: "gemini-3.5-flash", + providerHint: "", + wantProvider: fantasygoogle.Name, + wantModel: "gemini-3.5-flash", + }, + { + name: "BareGemmaModelResolvesToGoogle", + modelName: "gemma-3-27b", + providerHint: "", + wantProvider: fantasygoogle.Name, + wantModel: "gemma-3-27b", + }, + { + name: "GoogleHintWithGeminiModel", + modelName: "gemini-2.5-pro", + providerHint: fantasygoogle.Name, + wantProvider: fantasygoogle.Name, + wantModel: "gemini-2.5-pro", + }, + { + name: "CanonicalGoogleRefResolvesToGoogle", + modelName: "google/gemini-3.5-flash", + providerHint: "", + wantProvider: fantasygoogle.Name, + wantModel: "gemini-3.5-flash", + }, } for _, tt := range tests { diff --git a/site/src/pages/AIBridgePage/icons/AIBridgeModelIcon.tsx b/site/src/pages/AIBridgePage/icons/AIBridgeModelIcon.tsx index ea17396309..121ecda7da 100644 --- a/site/src/pages/AIBridgePage/icons/AIBridgeModelIcon.tsx +++ b/site/src/pages/AIBridgePage/icons/AIBridgeModelIcon.tsx @@ -6,6 +6,7 @@ import { cn } from "#/utils/cn"; // See official model naming docs: // - Anthropic: https://docs.anthropic.com/en/docs/about-claude/models/all-models // - OpenAI: https://platform.openai.com/docs/models +// - Google: https://ai.google.dev/gemini-api/docs/models function inferModelFamily(model: string): string { const modelFamily = model.toLowerCase(); // Anthropic model families @@ -28,6 +29,10 @@ function inferModelFamily(model: string): string { ) { return "openai"; } + // Google model families (gemini-*, gemma-*) + if (modelFamily.includes("gemini") || modelFamily.includes("gemma")) { + return "gemini"; + } return "unknown"; } @@ -55,6 +60,13 @@ export const AIBridgeModelIcon = ({ className={cn(iconClassName, className)} /> ); + case "gemini": + return ( + + ); default: return ( ); + case "google": + return ( + + ); + case "azure": + return ( + + ); + case "bedrock": + return ( + + ); case "copilot": return ( ); + case "vercel": + return ( + + ); default: return ( { + it.each([ + ["anthropic", "Anthropic"], + ["openai", "OpenAI"], + ["google", "Google"], + ["azure", "Azure OpenAI"], + ["bedrock", "AWS Bedrock"], + ["copilot", "GitHub Copilot"], + ["openai-compat", "OpenAI-compatible"], + ["openrouter", "OpenRouter"], + ["vercel", "Vercel"], + ])("maps known provider %s to %s", (input, expected) => { + expect(getProviderDisplayName(input)).toBe(expected); + }); + + it("capitalizes unknown provider names", () => { + expect(getProviderDisplayName("custom")).toBe("Custom"); + expect(getProviderDisplayName("some-provider")).toBe("Some-provider"); + }); + + it("returns Unknown for empty string", () => { + expect(getProviderDisplayName("")).toBe("Unknown"); + }); +}); diff --git a/site/src/pages/AIBridgePage/utils.ts b/site/src/pages/AIBridgePage/utils.ts index 3585d4468e..08424f0341 100644 --- a/site/src/pages/AIBridgePage/utils.ts +++ b/site/src/pages/AIBridgePage/utils.ts @@ -18,9 +18,25 @@ export const getProviderDisplayName = (provider: string) => { return "Anthropic"; case "openai": return "OpenAI"; + case "google": + return "Google"; + case "azure": + return "Azure OpenAI"; + case "bedrock": + return "AWS Bedrock"; case "copilot": - return "Github"; - default: - return "Unknown"; + return "GitHub Copilot"; + case "openai-compat": + return "OpenAI-compatible"; + case "openrouter": + return "OpenRouter"; + case "vercel": + return "Vercel"; + default: { + if (!provider) { + return "Unknown"; + } + return provider.charAt(0).toUpperCase() + provider.slice(1); + } } };