diff --git a/crates/agent-ui/src/components/ProviderBrandIcon.tsx b/crates/agent-ui/src/components/ProviderBrandIcon.tsx
new file mode 100644
index 000000000..3a6255c0d
--- /dev/null
+++ b/crates/agent-ui/src/components/ProviderBrandIcon.tsx
@@ -0,0 +1,36 @@
+import {
+ ClaudeIcon,
+ DeepseekIcon,
+ GeminiIcon,
+ GrokIcon,
+ OpenaiChatgptIcon,
+} from "@liveagent/ui/components/IconSet";
+import type { ProviderId } from "@liveagent/ui/lib/settings/types";
+import { cn } from "@liveagent/ui/lib/shared/utils";
+
+const KNOWN_PROVIDER_IDS: readonly ProviderId[] = [
+ "codex",
+ "claude_code",
+ "gemini",
+ "xai",
+ "deepseek",
+];
+
+// Sidebar rows carry providerId as a wide string: legacy rows persist "",
+// GUI optimistic rows may fall back to "pending", and web optimistic rows
+// store a custom provider *instance* id. Only enum hits may reach the icon —
+// anything else would render as the OpenAI fallback below.
+export function isKnownProviderId(value: string | undefined): value is ProviderId {
+ return (KNOWN_PROVIDER_IDS as readonly string[]).includes(value ?? "");
+}
+
+// Unmatched types (including "codex") fall through to the OpenAI icon; callers
+// that need "unknown renders nothing" must guard with isKnownProviderId first.
+export function ProviderBrandIcon({ type, className }: { type?: ProviderId; className?: string }) {
+ const cls = cn("h-4 w-4 shrink-0", className);
+ if (type === "claude_code") return ;
+ if (type === "gemini") return ;
+ if (type === "xai") return ;
+ if (type === "deepseek") return ;
+ return ;
+}
diff --git a/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx b/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx
index 183af4813..91198c9a1 100644
--- a/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx
+++ b/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx
@@ -56,6 +56,7 @@ import {
} from "react";
import type { SidebarConversation } from "../../lib/sidebar/types";
import type { WorkspaceProjectGroup } from "../../lib/workspaceProjectTypes";
+import { isKnownProviderId, ProviderBrandIcon } from "../ProviderBrandIcon";
export type WorkspaceProjectRemoveOptions = {
deleteWorktree?: boolean;
@@ -126,6 +127,8 @@ function areRenderedHistoryItemsEqual(previous: SidebarConversation, next: Sideb
return (
previous.id === next.id &&
previous.title === next.title &&
+ previous.providerId === next.providerId &&
+ previous.model === next.model &&
previous.cwd === next.cwd &&
previous.isPinned === next.isPinned &&
previous.isShared === next.isShared &&
@@ -700,6 +703,15 @@ export const HistoryRow = memo(function HistoryRow(props: HistoryRowProps) {
{isSelected ? : null}
) : null}
+ {!isSelectionMode && isKnownProviderId(item.providerId) ? (
+
+
+
+ ) : null}
{item.title}
diff --git a/crates/agent-ui/src/components/chat/ComposerModelControls.tsx b/crates/agent-ui/src/components/chat/ComposerModelControls.tsx
index f4c2d45c1..c57a376b8 100644
--- a/crates/agent-ui/src/components/chat/ComposerModelControls.tsx
+++ b/crates/agent-ui/src/components/chat/ComposerModelControls.tsx
@@ -12,20 +12,16 @@ import {
ArrowDownAZ,
Check,
ChevronDown,
- ClaudeIcon,
- DeepseekIcon,
- GeminiIcon,
Globe,
GlobeOff,
- GrokIcon,
Layers,
Lightbulb,
LightbulbOff,
- OpenaiChatgptIcon,
Pencil,
Search,
Sparkle,
} from "@liveagent/ui/components/IconSet";
+import { ProviderBrandIcon } from "@liveagent/ui/components/ProviderBrandIcon";
import { Button } from "@liveagent/ui/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@liveagent/ui/components/ui/popover";
import { useLocale } from "@liveagent/ui/i18n/index";
@@ -66,15 +62,6 @@ const REASONING_COMPACT_I18N_KEYS: Record = {
max: "chat.runtime.reasoningCompact.max",
};
-function ProviderBrandIcon({ type, className }: { type: ProviderId; className?: string }) {
- const cls = cn("h-4 w-4 shrink-0", className);
- if (type === "claude_code") return ;
- if (type === "gemini") return ;
- if (type === "xai") return ;
- if (type === "deepseek") return ;
- return ;
-}
-
function RuntimeToggleChip(props: {
pressed: boolean;
disabled?: boolean;
diff --git a/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx b/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx
index b28a11d0a..8d41f2e46 100644
--- a/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx
+++ b/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx
@@ -118,6 +118,8 @@ export function getProviderLabel(type: ProviderId) {
return PROVIDER_LABELS[type];
}
+// TODO: converge with components/ProviderBrandIcon.tsx — this variant keeps the
+// settings-page sizing contract (height="1em" scales with surrounding text).
export function ProviderBrandIcon({ type }: { type: ProviderId }) {
if (type === "claude_code") return ;
if (type === "gemini") return ;
diff --git a/crates/agent-ui/src/pages/settings/modelPicker.tsx b/crates/agent-ui/src/pages/settings/modelPicker.tsx
index 0b3331325..936ad979a 100644
--- a/crates/agent-ui/src/pages/settings/modelPicker.tsx
+++ b/crates/agent-ui/src/pages/settings/modelPicker.tsx
@@ -1,15 +1,6 @@
import type { ProviderId } from "@liveagent/app/lib/settings/index";
-import {
- Check,
- ChevronDown,
- ClaudeIcon,
- DeepseekIcon,
- GeminiIcon,
- GrokIcon,
- OpenaiChatgptIcon,
- Search,
- Sparkles,
-} from "@liveagent/ui/components/IconSet";
+import { Check, ChevronDown, Search, Sparkles } from "@liveagent/ui/components/IconSet";
+import { ProviderBrandIcon } from "@liveagent/ui/components/ProviderBrandIcon";
import {
DropdownMenu,
DropdownMenuContent,
@@ -34,15 +25,6 @@ export type ModelPickerOption = {
providerType?: ProviderId;
};
-function ProviderBrandIcon({ type, className }: { type?: ProviderId; className?: string }) {
- const cls = cn("h-4 w-4 shrink-0", className);
- if (type === "claude_code") return ;
- if (type === "gemini") return ;
- if (type === "xai") return ;
- if (type === "deepseek") return ;
- return ;
-}
-
type ModelGroup = {
id: string;
name: string;