From 4b0ae1d95f47c69c73f9310e49902914f0867f53 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 8 Jan 2026 17:01:24 +0200 Subject: [PATCH] feat: add variant support across model selection and multi-run components --- CHANGELOG.md | 3 + .../components/chat/message/MessageHeader.tsx | 10 ++- .../components/multirun/ModelMultiSelect.tsx | 84 +++++++++++++++---- .../components/multirun/MultiRunLauncher.tsx | 5 ++ packages/ui/src/components/ui/select.tsx | 4 +- .../agent-manager/AgentManagerEmptyState.tsx | 8 +- packages/ui/src/stores/useMultiRunStore.ts | 23 ++--- packages/ui/src/types/multirun.ts | 1 + 8 files changed, 106 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263d8e18..4122a0a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Added support for model variants (thinking effort) + + ## [1.4.4] - 2026-01-08 - Agent Manager / Multi Run: select agent per worktree session (thanks to @wienans). diff --git a/packages/ui/src/components/chat/message/MessageHeader.tsx b/packages/ui/src/components/chat/message/MessageHeader.tsx index e871b677..10cb2a70 100644 --- a/packages/ui/src/components/chat/message/MessageHeader.tsx +++ b/packages/ui/src/components/chat/message/MessageHeader.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { RiBrainAi3Line, RiUser3Line } from '@remixicon/react'; +import { RiAiAgentLine, RiBrainAi3Line, RiUser3Line } from '@remixicon/react'; import { cn } from '@/lib/utils'; import { getAgentColor } from '@/lib/agentColors'; import { FadeInOnReveal } from './FadeInOnReveal'; @@ -60,19 +60,22 @@ const MessageHeader: React.FC = ({ isUser, providerID, agent {!isUser && agentName && (
+ {agentName}
)} {!isUser && variant && (
= ({ isUser, providerID, agent : undefined } > + {variant.length > 0 ? variant[0].toLowerCase() + variant.slice(1) : variant}
)} diff --git a/packages/ui/src/components/multirun/ModelMultiSelect.tsx b/packages/ui/src/components/multirun/ModelMultiSelect.tsx index eabdaec5..37059ebb 100644 --- a/packages/ui/src/components/multirun/ModelMultiSelect.tsx +++ b/packages/ui/src/components/multirun/ModelMultiSelect.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { RiAddLine, RiCloseLine, RiSearchLine, RiStarFill, RiTimeLine } from '@remixicon/react'; +import { RiAddLine, RiBrainAi3Line, RiCloseLine, RiSearchLine, RiStarFill, RiTimeLine } from '@remixicon/react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { ProviderLogo } from '@/components/ui/ProviderLogo'; import { cn } from '@/lib/utils'; @@ -18,6 +19,7 @@ export interface ModelSelectionWithId { providerID: string; modelID: string; displayName?: string; + variant?: string; instanceId: string; } @@ -26,6 +28,7 @@ export interface ModelSelection { providerID: string; modelID: string; displayName?: string; + variant?: string; } // eslint-disable-next-line react-refresh/only-export-components -- Utility is tightly coupled with ModelMultiSelect @@ -85,11 +88,12 @@ export interface ModelMultiSelectProps { selectedModels: ModelSelectionWithId[]; onAdd: (model: ModelSelectionWithId) => void; onRemove: (index: number) => void; + onUpdate?: (index: number, model: ModelSelectionWithId) => void; /** Minimum models required (shows validation hint) */ minModels?: number; /** Label for the add button */ addButtonLabel?: string; - /** Whether to show the selected chips inline */ + /** Whether to show the selected chips */ showChips?: boolean; /** Maximum models allowed */ maxModels?: number; @@ -102,6 +106,7 @@ export const ModelMultiSelect: React.FC = ({ selectedModels, onAdd, onRemove, + onUpdate, minModels, addButtonLabel = 'Add model', showChips = true, @@ -462,20 +467,67 @@ export const ModelMultiSelect: React.FC = ({ {/* Selected models */} - {showChips && selectedModels.map((model, index) => { - const key = `${model.providerID}:${model.modelID}`; - const totalSameModel = modelCounts.get(key) || 1; - const instanceIndex = getInstanceIndex(model); - return ( - onRemove(index)} - /> - ); - })} + {showChips && selectedModels.length > 0 && ( +
+ {selectedModels.map((model, index) => { + const key = `${model.providerID}:${model.modelID}`; + const totalSameModel = modelCounts.get(key) || 1; + const instanceIndex = getInstanceIndex(model); + + const provider = providers.find((p) => p.id === model.providerID); + const providerModel = provider?.models.find((m: Record) => (m as { id?: string }).id === model.modelID) as + | { variants?: Record } + | undefined; + const variantKeys = providerModel?.variants ? Object.keys(providerModel.variants) : []; + const hasVariants = variantKeys.length > 0; + + const DEFAULT_VARIANT_VALUE = '__default__'; + const variantValue = model.variant ?? DEFAULT_VARIANT_VALUE; + + return ( +
+ onRemove(index)} + /> + + {hasVariants && ( + + )} +
+ ); + })} +
+ )} {/* Validation hint */} diff --git a/packages/ui/src/components/multirun/MultiRunLauncher.tsx b/packages/ui/src/components/multirun/MultiRunLauncher.tsx index a9a0b189..b833d968 100644 --- a/packages/ui/src/components/multirun/MultiRunLauncher.tsx +++ b/packages/ui/src/components/multirun/MultiRunLauncher.tsx @@ -174,6 +174,10 @@ export const MultiRunLauncher: React.FC = ({ clearError(); }; + const handleUpdateModel = React.useCallback((index: number, model: ModelSelectionWithId) => { + setSelectedModels((prev) => prev.map((item, i) => (i === index ? model : item))); + }, []); + const handleFileSelect = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files) return; @@ -533,6 +537,7 @@ export const MultiRunLauncher: React.FC = ({ selectedModels={selectedModels} onAdd={handleAddModel} onRemove={handleRemoveModel} + onUpdate={handleUpdateModel} minModels={2} maxModels={MAX_MODELS} /> diff --git a/packages/ui/src/components/ui/select.tsx b/packages/ui/src/components/ui/select.tsx index ce6a4e14..14bbf324 100644 --- a/packages/ui/src/components/ui/select.tsx +++ b/packages/ui/src/components/ui/select.tsx @@ -31,14 +31,14 @@ function SelectTrigger({ children, ...props }: React.ComponentProps & { - size?: "sm" | "default" | "lg" + size?: "sm" | "default" | "lg" | "chip" }) { return ( = ({ setSelectedModels((prev) => prev.filter((_, i) => i !== index)); }, []); + const handleUpdateModel = React.useCallback((index: number, model: ModelSelectionWithId) => { + setSelectedModels((prev) => prev.map((item, i) => (i === index ? model : item))); + }, []); + const handleFileSelect = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files) return; @@ -214,10 +218,11 @@ export const AgentManagerEmptyState: React.FC = ({ setIsSubmitting(true); try { - const models = selectedModels.map(({ providerID, modelID, displayName }) => ({ + const models = selectedModels.map(({ providerID, modelID, displayName, variant }) => ({ providerID, modelID, displayName, + variant, })); const files: MultiRunFileAttachment[] | undefined = attachedFiles.length > 0 @@ -394,6 +399,7 @@ export const AgentManagerEmptyState: React.FC = ({ selectedModels={selectedModels} onAdd={handleAddModel} onRemove={handleRemoveModel} + onUpdate={handleUpdateModel} minModels={1} addButtonLabel="Add model" maxModels={5} diff --git a/packages/ui/src/stores/useMultiRunStore.ts b/packages/ui/src/stores/useMultiRunStore.ts index 1aee1f3a..978d7c4c 100644 --- a/packages/ui/src/stores/useMultiRunStore.ts +++ b/packages/ui/src/stores/useMultiRunStore.ts @@ -150,6 +150,7 @@ export const useMultiRunStore = create()( worktreePath: string; providerID: string; modelID: string; + variant?: string; }> = []; const commandsToRun = setupCommands?.filter((cmd) => cmd.trim().length > 0) ?? []; @@ -214,6 +215,7 @@ export const useMultiRunStore = create()( worktreePath: worktreeMetadata.path, providerID: model.providerID, modelID: model.modelID, + variant: model.variant, }); } catch (error) { @@ -276,16 +278,17 @@ export const useMultiRunStore = create()( await Promise.allSettled( createdRuns.map(async (run) => { try { - await opencodeClient.withDirectory(run.worktreePath, () => - opencodeClient.sendMessage({ - id: run.sessionId, - providerID: run.providerID, - modelID: run.modelID, - text: prompt, - agent, - files: filesForMessage, - }) - ); + await opencodeClient.withDirectory(run.worktreePath, () => + opencodeClient.sendMessage({ + id: run.sessionId, + providerID: run.providerID, + modelID: run.modelID, + variant: run.variant, + text: prompt, + agent, + files: filesForMessage, + }) + ); } catch (error) { console.warn('[MultiRun] Failed to start run:', error); } diff --git a/packages/ui/src/types/multirun.ts b/packages/ui/src/types/multirun.ts index 1d1a9e4c..5ba23c39 100644 --- a/packages/ui/src/types/multirun.ts +++ b/packages/ui/src/types/multirun.ts @@ -9,6 +9,7 @@ export interface MultiRunModelSelection { providerID: string; modelID: string; displayName?: string; + variant?: string; } export interface MultiRunFileAttachment {