From ebc8ee2638a10500c85f0af862f9d99589429b89 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sat, 20 Dec 2025 23:38:07 -0500 Subject: [PATCH] feat(openrouter): prioritize Exacto models for better agentic performance - Add isExacto field to CategorizedModel for models with :exacto suffix - Add sortModelsByPriority function (Free > Exacto > Regular) - Apply priority sorting within each category in model picker - Add visual "Exacto" badge (green outline) for exacto variants Exacto models are OpenRouter's specialized variants optimized for tool use and agentic behaviors, recommended for Claude Code. --- .../profiles/openrouter-model-picker.tsx | 23 ++++++++++++++++++- ui/src/lib/openrouter-types.ts | 1 + ui/src/lib/openrouter-utils.ts | 22 ++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/ui/src/components/profiles/openrouter-model-picker.tsx b/ui/src/components/profiles/openrouter-model-picker.tsx index 8cb9b45a..a6662c98 100644 --- a/ui/src/components/profiles/openrouter-model-picker.tsx +++ b/ui/src/components/profiles/openrouter-model-picker.tsx @@ -13,6 +13,7 @@ import { Search, RefreshCw, Loader2, Sparkles } from 'lucide-react'; import { useOpenRouterCatalog, useRefreshOpenRouterModels } from '@/hooks/use-openrouter-models'; import { searchModels, + sortModelsByPriority, formatPricingPair, formatContextLength, formatModelAge, @@ -56,7 +57,7 @@ export function OpenRouterModelPicker({ // Determine if we should show presets (no search query and no category filter) const showPresets = !search.trim() && !selectedCategory; - // Group by category + // Group by category and sort each group by priority (Free > Exacto > Regular) const groupedModels = useMemo(() => { const groups: Record = { anthropic: [], @@ -72,6 +73,11 @@ export function OpenRouterModelPicker({ groups[model.category].push(model); }); + // Sort each category by priority + for (const category of Object.keys(groups) as ModelCategory[]) { + groups[category] = sortModelsByPriority(groups[category]); + } + return groups; }, [filteredModels]); @@ -275,6 +281,21 @@ function ModelItem({ > Free + ) : model.isExacto ? ( + <> + + Exacto + + {formatPricingPair(model.pricing)} + ) : ( {formatPricingPair(model.pricing)} )} diff --git a/ui/src/lib/openrouter-types.ts b/ui/src/lib/openrouter-types.ts index b535883d..c3bab395 100644 --- a/ui/src/lib/openrouter-types.ts +++ b/ui/src/lib/openrouter-types.ts @@ -69,4 +69,5 @@ export interface CategorizedModel extends OpenRouterModel { pricePerMillionPrompt: number; pricePerMillionCompletion: number; isFree: boolean; + isExacto: boolean; // Models with :exacto suffix - optimized for tool use } diff --git a/ui/src/lib/openrouter-utils.ts b/ui/src/lib/openrouter-utils.ts index 80b9f5a8..d651b62c 100644 --- a/ui/src/lib/openrouter-utils.ts +++ b/ui/src/lib/openrouter-utils.ts @@ -54,6 +54,7 @@ export function enrichModel(model: OpenRouterModel): CategorizedModel { pricePerMillionPrompt: pricePerMillion(model.pricing.prompt), pricePerMillionCompletion: pricePerMillion(model.pricing.completion), isFree: model.pricing.prompt === '0' && model.pricing.completion === '0', + isExacto: model.id.includes(':exacto'), // Exacto variants - optimized for agentic/tool use }; } @@ -85,6 +86,27 @@ export function searchModels( }); } +/** + * Sort models with priority: Free > Exacto > Regular + * Within each tier, sort by name alphabetically + */ +export function sortModelsByPriority(models: CategorizedModel[]): CategorizedModel[] { + return [...models].sort((a, b) => { + // Priority 1: Free models first + if (a.isFree && !b.isFree) return -1; + if (!a.isFree && b.isFree) return 1; + + // Priority 2: Exacto models second (only if both not free) + if (!a.isFree && !b.isFree) { + if (a.isExacto && !b.isExacto) return -1; + if (!a.isExacto && b.isExacto) return 1; + } + + // Same tier: sort by name + return a.name.localeCompare(b.name); + }); +} + /** Get cached models from localStorage */ export function getCachedModels(): OpenRouterModel[] | null { try {