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.
This commit is contained in:
kaitranntt
2025-12-20 23:38:07 -05:00
parent 7d4961e7a9
commit ebc8ee2638
3 changed files with 45 additions and 1 deletions
@@ -13,6 +13,7 @@ import { Search, RefreshCw, Loader2, Sparkles } from 'lucide-react';
import { useOpenRouterCatalog, useRefreshOpenRouterModels } from '@/hooks/use-openrouter-models'; import { useOpenRouterCatalog, useRefreshOpenRouterModels } from '@/hooks/use-openrouter-models';
import { import {
searchModels, searchModels,
sortModelsByPriority,
formatPricingPair, formatPricingPair,
formatContextLength, formatContextLength,
formatModelAge, formatModelAge,
@@ -56,7 +57,7 @@ export function OpenRouterModelPicker({
// Determine if we should show presets (no search query and no category filter) // Determine if we should show presets (no search query and no category filter)
const showPresets = !search.trim() && !selectedCategory; const showPresets = !search.trim() && !selectedCategory;
// Group by category // Group by category and sort each group by priority (Free > Exacto > Regular)
const groupedModels = useMemo(() => { const groupedModels = useMemo(() => {
const groups: Record<ModelCategory, CategorizedModel[]> = { const groups: Record<ModelCategory, CategorizedModel[]> = {
anthropic: [], anthropic: [],
@@ -72,6 +73,11 @@ export function OpenRouterModelPicker({
groups[model.category].push(model); 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; return groups;
}, [filteredModels]); }, [filteredModels]);
@@ -275,6 +281,21 @@ function ModelItem({
> >
Free Free
</Badge> </Badge>
) : model.isExacto ? (
<>
<Badge
variant="outline"
className={cn(
'text-[10px] px-1 border-emerald-500/50 text-emerald-600',
isSelected
? 'border-accent-foreground/30 text-accent-foreground/80'
: 'group-hover:border-accent-foreground/30 group-hover:text-accent-foreground/80'
)}
>
Exacto
</Badge>
<span className="tabular-nums">{formatPricingPair(model.pricing)}</span>
</>
) : ( ) : (
<span className="tabular-nums">{formatPricingPair(model.pricing)}</span> <span className="tabular-nums">{formatPricingPair(model.pricing)}</span>
)} )}
+1
View File
@@ -69,4 +69,5 @@ export interface CategorizedModel extends OpenRouterModel {
pricePerMillionPrompt: number; pricePerMillionPrompt: number;
pricePerMillionCompletion: number; pricePerMillionCompletion: number;
isFree: boolean; isFree: boolean;
isExacto: boolean; // Models with :exacto suffix - optimized for tool use
} }
+22
View File
@@ -54,6 +54,7 @@ export function enrichModel(model: OpenRouterModel): CategorizedModel {
pricePerMillionPrompt: pricePerMillion(model.pricing.prompt), pricePerMillionPrompt: pricePerMillion(model.pricing.prompt),
pricePerMillionCompletion: pricePerMillion(model.pricing.completion), pricePerMillionCompletion: pricePerMillion(model.pricing.completion),
isFree: model.pricing.prompt === '0' && model.pricing.completion === '0', 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 */ /** Get cached models from localStorage */
export function getCachedModels(): OpenRouterModel[] | null { export function getCachedModels(): OpenRouterModel[] | null {
try { try {