feat: improve opencode model verification ux

This commit is contained in:
777genius 2026-04-26 10:21:18 +03:00
parent a0f1c92d18
commit 49982a1db8
12 changed files with 5182 additions and 234 deletions

View file

@ -188,7 +188,7 @@ For feature architecture and implementation guidance:
| **Per-task code review** | ✅ Accept / reject / comment | ⚠️ PR-level only | ⚠️ File-level only | ✅ BugBot on PRs | ❌ | | **Per-task code review** | ✅ Accept / reject / comment | ⚠️ PR-level only | ⚠️ File-level only | ✅ BugBot on PRs | ❌ |
| **Flexible autonomy** | ✅ Granular settings, per-action approval, notifications | ❌ | ⚠️ Plan approval only | ✅ | ✅ | | **Flexible autonomy** | ✅ Granular settings, per-action approval, notifications | ❌ | ⚠️ Plan approval only | ✅ | ✅ |
| **Git worktree isolation** | ✅ Optional | ⚠️ Mandatory | ⚠️ Mandatory | ✅ | ✅ | | **Git worktree isolation** | ✅ Optional | ⚠️ Mandatory | ⚠️ Mandatory | ✅ | ✅ |
| **Multi-agent backend** | ✅ Claude, Codex, more coming soon | ✅ 6+ agents | ✅ 11 providers | ✅ Multi-model | — | | **Multi-agent backend** | ✅ Codex, Claude, and 75+ providers | ✅ 6+ agents | ✅ 11 providers | ✅ Multi-model | — |
| **Price** | **Free** | Free / $30 user/mo | Free | $0$200/mo | Provider subscription | | **Price** | **Free** | Free / $30 user/mo | Free | $0$200/mo | Provider subscription |
--- ---

File diff suppressed because it is too large Load diff

View file

@ -1126,15 +1126,22 @@ function ModelBadges({
? 'bg-emerald-400/15 px-1.5 py-0 text-[10px] text-emerald-200' ? 'bg-emerald-400/15 px-1.5 py-0 text-[10px] text-emerald-200'
: modelRecommendation.level === 'recommended-with-limits' : modelRecommendation.level === 'recommended-with-limits'
? 'bg-amber-400/15 px-1.5 py-0 text-[10px] text-amber-200' ? 'bg-amber-400/15 px-1.5 py-0 text-[10px] text-amber-200'
: modelRecommendation.level === 'unavailable-in-opencode' : modelRecommendation.level === 'tested'
? 'bg-slate-400/15 px-1.5 py-0 text-[10px] text-slate-200' ? 'bg-sky-400/15 px-1.5 py-0 text-[10px] text-sky-200'
: 'bg-red-400/15 px-1.5 py-0 text-[10px] text-red-200' : modelRecommendation.level === 'tested-with-limits'
? 'bg-cyan-400/15 px-1.5 py-0 text-[10px] text-cyan-200'
: modelRecommendation.level === 'unavailable-in-opencode'
? 'bg-slate-400/15 px-1.5 py-0 text-[10px] text-slate-200'
: 'bg-red-400/15 px-1.5 py-0 text-[10px] text-red-200'
} }
title={modelRecommendation.reason} title={modelRecommendation.reason}
> >
{modelRecommendation.level === 'not-recommended' || {modelRecommendation.level === 'not-recommended' ||
modelRecommendation.level === 'unavailable-in-opencode' ? ( modelRecommendation.level === 'unavailable-in-opencode' ? (
<AlertTriangle className="mr-1 size-3" /> <AlertTriangle className="mr-1 size-3" />
) : modelRecommendation.level === 'tested' ||
modelRecommendation.level === 'tested-with-limits' ? (
<CheckCircle2 className="mr-1 size-3" />
) : ( ) : (
<Star className="mr-1 size-3 fill-current" /> <Star className="mr-1 size-3 fill-current" />
)} )}

View file

@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo'; import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
import { Checkbox } from '@renderer/components/ui/checkbox'; import { Checkbox } from '@renderer/components/ui/checkbox';
import { Input } from '@renderer/components/ui/input';
import { Label } from '@renderer/components/ui/label'; import { Label } from '@renderer/components/ui/label';
import { Tabs, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { Tabs, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { import {
@ -43,7 +44,7 @@ import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext
import { resolveAnthropicLaunchModel } from '@shared/utils/anthropicLaunchModel'; import { resolveAnthropicLaunchModel } from '@shared/utils/anthropicLaunchModel';
import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults'; import { getAnthropicDefaultTeamModel } from '@shared/utils/anthropicModelDefaults';
import { isTeamProviderId } from '@shared/utils/teamProvider'; import { isTeamProviderId } from '@shared/utils/teamProvider';
import { AlertTriangle, Info, Star } from 'lucide-react'; import { AlertTriangle, CheckCircle2, Info, Search, Star } from 'lucide-react';
import type { CliProviderStatus, TeamProviderId } from '@shared/types'; import type { CliProviderStatus, TeamProviderId } from '@shared/types';
@ -169,6 +170,7 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
}) => { }) => {
const multimodelEnabled = useStore((s) => s.appConfig?.general?.multimodelEnabled ?? true); const multimodelEnabled = useStore((s) => s.appConfig?.general?.multimodelEnabled ?? true);
const [recommendedOnly, setRecommendedOnly] = useState(false); const [recommendedOnly, setRecommendedOnly] = useState(false);
const [modelQuery, setModelQuery] = useState('');
const effectiveProviderId = const effectiveProviderId =
disableGeminiOption && isGeminiUiFrozen() && providerId === 'gemini' ? 'anthropic' : providerId; disableGeminiOption && isGeminiUiFrozen() && providerId === 'gemini' ? 'anthropic' : providerId;
@ -322,15 +324,41 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
} }
}, [effectiveProviderId, hasRecommendedOpenCodeModels]); }, [effectiveProviderId, hasRecommendedOpenCodeModels]);
useEffect(() => {
setModelQuery('');
}, [effectiveProviderId]);
const visibleModelOptions = useMemo(() => { const visibleModelOptions = useMemo(() => {
const normalizedModelQuery = modelQuery.trim().toLowerCase();
const matchesModelQuery = (option: (typeof modelOptions)[number]): boolean => {
if (!normalizedModelQuery) {
return true;
}
const modelRecommendation =
effectiveProviderId === 'opencode'
? getOpenCodeTeamModelRecommendation(option.value)
: null;
return [
option.value,
option.label,
option.badgeLabel ?? '',
modelRecommendation?.label ?? '',
modelRecommendation?.reason ?? '',
]
.join(' ')
.toLowerCase()
.includes(normalizedModelQuery);
};
if (effectiveProviderId !== 'opencode') { if (effectiveProviderId !== 'opencode') {
return modelOptions; return modelOptions.filter(matchesModelQuery);
} }
const concreteOptions = modelOptions const concreteOptions = modelOptions
.filter((option) => option.value.trim().length > 0) .filter((option) => option.value.trim().length > 0)
.map((option, index) => ({ option, index })) .map((option, index) => ({ option, index }))
.filter(({ option }) => !recommendedOnly || isOpenCodeTeamModelRecommended(option.value)) .filter(({ option }) => !recommendedOnly || isOpenCodeTeamModelRecommended(option.value))
.filter(({ option }) => matchesModelQuery(option))
.sort((left, right) => { .sort((left, right) => {
const recommendationOrder = compareOpenCodeTeamModelRecommendations( const recommendationOrder = compareOpenCodeTeamModelRecommendations(
left.option.value, left.option.value,
@ -347,8 +375,11 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
return [ return [
...modelOptions.filter((option) => option.value.trim().length === 0), ...modelOptions.filter((option) => option.value.trim().length === 0),
...concreteOptions, ...concreteOptions,
]; ].filter(matchesModelQuery);
}, [effectiveProviderId, modelOptions, recommendedOnly]); }, [effectiveProviderId, modelOptions, modelQuery, recommendedOnly]);
const concreteModelOptionCount = modelOptions.filter((option) => option.value.trim()).length;
const shouldShowModelSearch = concreteModelOptionCount > 8;
const trimmedModelQuery = modelQuery.trim();
const shouldConstrainModelListHeight = visibleModelOptions.length > 8; const shouldConstrainModelListHeight = visibleModelOptions.length > 8;
return ( return (
@ -433,6 +464,20 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
list is syncing. list is syncing.
</p> </p>
) : null} ) : null}
{shouldShowModelSearch ? (
<div className="relative mb-2">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--color-text-muted)]" />
<Input
data-testid="team-model-selector-model-search"
value={modelQuery}
onChange={(event) => setModelQuery(event.target.value)}
placeholder="Search models"
aria-label="Search models"
className="h-9 pr-3 text-sm"
style={{ paddingLeft: 40 }}
/>
</div>
) : null}
{hasRecommendedOpenCodeModels ? ( {hasRecommendedOpenCodeModels ? (
<div className="mb-2 flex w-fit items-center gap-2"> <div className="mb-2 flex w-fit items-center gap-2">
<Checkbox <Checkbox
@ -540,15 +585,22 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
? 'bg-emerald-300/12 border-emerald-300/35 text-emerald-200' ? 'bg-emerald-300/12 border-emerald-300/35 text-emerald-200'
: modelRecommendation.level === 'recommended-with-limits' : modelRecommendation.level === 'recommended-with-limits'
? 'bg-amber-300/12 border-amber-300/35 text-amber-200' ? 'bg-amber-300/12 border-amber-300/35 text-amber-200'
: modelRecommendation.level === 'unavailable-in-opencode' : modelRecommendation.level === 'tested'
? 'border-slate-300/30 bg-slate-400/10 text-slate-200' ? 'bg-sky-300/12 border-sky-300/35 text-sky-200'
: 'border-red-300/35 bg-red-400/10 text-red-200' : modelRecommendation.level === 'tested-with-limits'
? 'border-cyan-300/30 bg-cyan-400/10 text-cyan-200'
: modelRecommendation.level === 'unavailable-in-opencode'
? 'border-slate-300/30 bg-slate-400/10 text-slate-200'
: 'border-red-300/35 bg-red-400/10 text-red-200'
)} )}
title={modelRecommendation.reason} title={modelRecommendation.reason}
> >
{modelRecommendation.level === 'not-recommended' || {modelRecommendation.level === 'not-recommended' ||
modelRecommendation.level === 'unavailable-in-opencode' ? ( modelRecommendation.level === 'unavailable-in-opencode' ? (
<AlertTriangle className="size-3 shrink-0" /> <AlertTriangle className="size-3 shrink-0" />
) : modelRecommendation.level === 'tested' ||
modelRecommendation.level === 'tested-with-limits' ? (
<CheckCircle2 className="size-3 shrink-0" />
) : ( ) : (
<Star className="size-3 shrink-0 fill-current" /> <Star className="size-3 shrink-0 fill-current" />
)} )}
@ -626,9 +678,13 @@ export const TeamModelSelector: React.FC<TeamModelSelectorProps> = ({
})() })()
)} )}
</div> </div>
{effectiveProviderId === 'opencode' && visibleModelOptions.length === 0 ? ( {visibleModelOptions.length === 0 ? (
<div className="rounded-md border border-white/10 px-3 py-2 text-xs text-[var(--color-text-muted)]"> <div className="rounded-md border border-white/10 px-3 py-2 text-xs text-[var(--color-text-muted)]">
No recommended OpenCode models are available in the current runtime list. {trimmedModelQuery
? 'No models match this search.'
: effectiveProviderId === 'opencode' && recommendedOnly
? 'No recommended OpenCode models are available in the current runtime list.'
: 'No models are available in the current runtime list.'}
</div> </div>
) : null} ) : null}
</div> </div>

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,7 @@ liveDescribe('OpenCode semantic messaging live e2e', () => {
const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({ const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({
tempDir, tempDir,
selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL, selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL,
projectPath: PROJECT_PATH,
}); });
const teamName = `opencode-semantic-message-${Date.now()}`; const teamName = `opencode-semantic-message-${Date.now()}`;
@ -163,6 +164,7 @@ liveDescribe('OpenCode semantic messaging live e2e', () => {
const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({ const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({
tempDir, tempDir,
selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL, selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL,
projectPath: PROJECT_PATH,
}); });
const teamName = `opencode-peer-message-${Date.now()}`; const teamName = `opencode-peer-message-${Date.now()}`;

File diff suppressed because it is too large Load diff

View file

@ -106,6 +106,7 @@ async function runModelScenario(input: {
harness = await createOpenCodeLiveHarness({ harness = await createOpenCodeLiveHarness({
tempDir, tempDir,
selectedModel: input.model, selectedModel: input.model,
projectPath,
}); });
const progressEvents: TeamProvisioningProgress[] = []; const progressEvents: TeamProvisioningProgress[] = [];

View file

@ -54,6 +54,7 @@ export interface OpenCodeLiveHarness {
export async function createOpenCodeLiveHarness(input: { export async function createOpenCodeLiveHarness(input: {
tempDir: string; tempDir: string;
selectedModel: string; selectedModel: string;
projectPath?: string;
}): Promise<OpenCodeLiveHarness> { }): Promise<OpenCodeLiveHarness> {
const orchestratorCli = const orchestratorCli =
process.env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim() || DEFAULT_ORCHESTRATOR_CLI; process.env.CLAUDE_AGENT_TEAMS_ORCHESTRATOR_CLI_PATH?.trim() || DEFAULT_ORCHESTRATOR_CLI;
@ -104,6 +105,17 @@ export async function createOpenCodeLiveHarness(input: {
svc, svc,
dispose: async () => { dispose: async () => {
svc.setControlApiBaseUrlResolver(null); svc.setControlApiBaseUrlResolver(null);
if (input.projectPath?.trim()) {
await readinessBridge
.cleanupOpenCodeHosts({
reason: 'test-harness-dispose',
mode: 'force',
projectPath: input.projectPath,
staleAgeMs: null,
leaseStaleAgeMs: null,
})
.catch(() => undefined);
}
await controlApi.close(); await controlApi.close();
}, },
}; };

View file

@ -253,8 +253,10 @@ describe('TeamModelSelector disabled Codex models', () => {
'openrouter/openai/gpt-oss-20b:free', 'openrouter/openai/gpt-oss-20b:free',
'openrouter/qwen/qwen3-coder-plus', 'openrouter/qwen/qwen3-coder-plus',
'opencode/big-pickle', 'opencode/big-pickle',
'opencode/minimax-m2.5-free',
'openrouter/openai/gpt-oss-120b:free', 'openrouter/openai/gpt-oss-120b:free',
'openrouter/qwen/qwen3-coder-flash', 'openrouter/mistralai/codestral-2508',
'openrouter/anthropic/claude-sonnet-4.6',
], ],
modelVerificationState: 'idle', modelVerificationState: 'idle',
modelAvailability: [], modelAvailability: [],
@ -278,11 +280,13 @@ describe('TeamModelSelector disabled Codex models', () => {
await Promise.resolve(); await Promise.resolve();
}); });
expect(host.textContent).toContain('Recommended only'); expect(host.textContent).toContain('anthropic/claude-sonnet-4.6');
expect(host.textContent).toContain('qwen/qwen3-coder-flash'); expect(host.textContent).toContain('Tested');
expect(host.textContent).toContain('Recommended'); expect(host.textContent).toContain('mistralai/codestral-2508');
expect(host.textContent).toContain('Tested');
expect(host.textContent).toContain('minimax-m2.5-free');
expect(host.textContent).toContain('Tested with limits');
expect(host.textContent).toContain('openai/gpt-oss-120b:free'); expect(host.textContent).toContain('openai/gpt-oss-120b:free');
expect(host.textContent).toContain('Recommended with limits');
expect(host.textContent).toContain('big-pickle'); expect(host.textContent).toContain('big-pickle');
expect(host.textContent).toContain('qwen/qwen3-coder-plus'); expect(host.textContent).toContain('qwen/qwen3-coder-plus');
expect(host.textContent).toContain('Unavailable in OpenCode'); expect(host.textContent).toContain('Unavailable in OpenCode');
@ -292,38 +296,26 @@ describe('TeamModelSelector disabled Codex models', () => {
const buttonTexts = Array.from(host.querySelectorAll('button')).map( const buttonTexts = Array.from(host.querySelectorAll('button')).map(
(button) => button.textContent ?? '' (button) => button.textContent ?? ''
); );
const recommendedIndex = buttonTexts.findIndex((text) => const sonnetIndex = buttonTexts.findIndex((text) =>
text.includes('qwen/qwen3-coder-flash') text.includes('anthropic/claude-sonnet-4.6')
); );
const testedIndex = buttonTexts.findIndex((text) => text.includes('mistralai/codestral-2508'));
const neutralIndex = buttonTexts.findIndex((text) => text.includes('big-pickle')); const neutralIndex = buttonTexts.findIndex((text) => text.includes('big-pickle'));
const limitedIndex = buttonTexts.findIndex((text) => const limitedIndex = buttonTexts.findIndex((text) => text.includes('minimax-m2.5-free'));
text.includes('openai/gpt-oss-120b:free')
);
const notRecommendedIndex = buttonTexts.findIndex((text) => const notRecommendedIndex = buttonTexts.findIndex((text) =>
text.includes('openai/gpt-oss-20b:free') text.includes('openai/gpt-oss-20b:free')
); );
const unavailableIndex = buttonTexts.findIndex((text) => const unavailableIndex = buttonTexts.findIndex((text) =>
text.includes('qwen/qwen3-coder-plus') text.includes('qwen/qwen3-coder-plus')
); );
expect(recommendedIndex).toBeGreaterThanOrEqual(0); expect(sonnetIndex).toBeGreaterThanOrEqual(0);
expect(limitedIndex).toBeGreaterThan(recommendedIndex); expect(testedIndex).toBeGreaterThanOrEqual(0);
expect(limitedIndex).toBeGreaterThan(testedIndex);
expect(neutralIndex).toBeGreaterThan(limitedIndex); expect(neutralIndex).toBeGreaterThan(limitedIndex);
expect(unavailableIndex).toBeGreaterThan(neutralIndex); expect(unavailableIndex).toBeGreaterThan(neutralIndex);
expect(notRecommendedIndex).toBeGreaterThan(unavailableIndex); expect(notRecommendedIndex).toBeGreaterThan(unavailableIndex);
await act(async () => { expect(host.textContent).not.toContain('Recommended only');
const checkbox = Array.from(host.querySelectorAll('button')).find(
(button) => button.getAttribute('role') === 'checkbox'
);
checkbox?.click();
await Promise.resolve();
});
expect(host.textContent).toContain('qwen/qwen3-coder-flash');
expect(host.textContent).toContain('openai/gpt-oss-120b:free');
expect(host.textContent).not.toContain('big-pickle');
expect(host.textContent).not.toContain('qwen/qwen3-coder-plus');
expect(host.textContent).not.toContain('openai/gpt-oss-20b:free');
await act(async () => { await act(async () => {
root.unmount(); root.unmount();
@ -375,6 +367,23 @@ describe('TeamModelSelector disabled Codex models', () => {
expect(modelGrid).toBeTruthy(); expect(modelGrid).toBeTruthy();
expect(modelGrid?.style.maxHeight).toBe('400px'); expect(modelGrid?.style.maxHeight).toBe('400px');
expect(modelGrid?.className).toContain('overflow-y-auto'); expect(modelGrid?.className).toContain('overflow-y-auto');
const searchInput = host.querySelector(
'[data-testid="team-model-selector-model-search"]'
) as HTMLInputElement | null;
expect(searchInput).toBeTruthy();
await act(async () => {
const setValue = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
)?.set;
setValue?.call(searchInput, '5.3');
searchInput?.dispatchEvent(new Event('input', { bubbles: true }));
await Promise.resolve();
});
expect(host.textContent).toContain('5.3 Codex');
expect(host.textContent).not.toContain('5.4 Mini');
await act(async () => { await act(async () => {
root.unmount(); root.unmount();
@ -413,6 +422,7 @@ describe('TeamModelSelector disabled Codex models', () => {
expect(host.textContent).toContain('5.4'); expect(host.textContent).toContain('5.4');
expect(host.textContent).toContain('5.3 Codex'); expect(host.textContent).toContain('5.3 Codex');
expect(host.textContent).not.toContain('Explicit models load from the current runtime'); expect(host.textContent).not.toContain('Explicit models load from the current runtime');
expect(host.querySelector('[data-testid="team-model-selector-model-search"]')).toBeNull();
await act(async () => { await act(async () => {
root.unmount(); root.unmount();

View file

@ -586,8 +586,26 @@ describe('RuntimeProviderManagementPanelView', () => {
}, },
{ {
providerId: 'openrouter', providerId: 'openrouter',
modelId: 'openrouter/qwen/qwen3-coder-flash', modelId: 'opencode/minimax-m2.5-free',
displayName: 'qwen/qwen3-coder-flash', displayName: 'minimax-m2.5-free',
sourceLabel: 'OpenCode',
free: true,
default: false,
availability: 'untested',
},
{
providerId: 'openrouter',
modelId: 'openrouter/mistralai/codestral-2508',
displayName: 'mistralai/codestral-2508',
sourceLabel: 'OpenRouter',
free: false,
default: false,
availability: 'untested',
},
{
providerId: 'openrouter',
modelId: 'openrouter/anthropic/claude-sonnet-4.6',
displayName: 'anthropic/claude-sonnet-4.6',
sourceLabel: 'OpenRouter', sourceLabel: 'OpenRouter',
free: false, free: false,
default: false, default: false,
@ -621,11 +639,12 @@ describe('RuntimeProviderManagementPanelView', () => {
expect(host.textContent).toContain('openrouter/openai/gpt-oss-20b:free'); expect(host.textContent).toContain('openrouter/openai/gpt-oss-20b:free');
expect(host.textContent).toContain('Used for new teams'); expect(host.textContent).toContain('Used for new teams');
expect(host.textContent).toContain('Model probe passed'); expect(host.textContent).toContain('Model probe passed');
expect(host.textContent).toContain('Recommended');
expect(host.textContent).toContain('Not recommended'); expect(host.textContent).toContain('Not recommended');
expect(host.textContent).toContain('Unavailable in OpenCode'); expect(host.textContent).toContain('Unavailable in OpenCode');
expect(host.textContent).toContain('Recommended only'); expect(host.textContent).toContain('Tested');
expect(host.textContent).toContain('Recommended'); expect(host.textContent).toContain('Tested with limits');
expect(host.textContent).toContain('Recommended with limits'); expect(host.textContent).not.toContain('Recommended only');
expect(host.textContent).not.toContain('Set OpenCode default'); expect(host.textContent).not.toContain('Set OpenCode default');
expect( expect(
Array.from(host.querySelectorAll('button')).some( Array.from(host.querySelectorAll('button')).some(
@ -640,11 +659,8 @@ describe('RuntimeProviderManagementPanelView', () => {
) as HTMLElement | undefined; ) as HTMLElement | undefined;
expect(connectedBadge?.style.color).toBeTruthy(); expect(connectedBadge?.style.color).toBeTruthy();
expect( expect(
( (host.querySelector('[data-testid="runtime-provider-model-search"]') as HTMLElement | null)
host.querySelector( ?.style.paddingLeft
'[data-testid="runtime-provider-model-search"]'
) as HTMLElement | null
)?.style.paddingLeft
).toBe('42px'); ).toBe('42px');
expect( expect(
(host.querySelector('[data-testid="runtime-provider-model-list"]') as HTMLElement | null) (host.querySelector('[data-testid="runtime-provider-model-list"]') as HTMLElement | null)
@ -659,10 +675,13 @@ describe('RuntimeProviderManagementPanelView', () => {
'[data-testid="runtime-provider-model-result-openrouter/openai/gpt-oss-20b:free"]' '[data-testid="runtime-provider-model-result-openrouter/openai/gpt-oss-20b:free"]'
) as HTMLElement | null; ) as HTMLElement | null;
expect(modelResult?.style.color).toBe('#86efac'); expect(modelResult?.style.color).toBe('#86efac');
expect((host.textContent ?? '').indexOf('qwen/qwen3-coder-flash')).toBeLessThan( expect((host.textContent ?? '').indexOf('mistralai/codestral-2508')).toBeLessThan(
(host.textContent ?? '').indexOf('openai/gpt-oss-120b:free') (host.textContent ?? '').indexOf('anthropic/claude-sonnet-4.6')
); );
expect((host.textContent ?? '').indexOf('openai/gpt-oss-120b:free')).toBeLessThan( expect((host.textContent ?? '').indexOf('anthropic/claude-sonnet-4.6')).toBeLessThan(
(host.textContent ?? '').indexOf('minimax-m2.5-free')
);
expect((host.textContent ?? '').indexOf('minimax-m2.5-free')).toBeLessThan(
(host.textContent ?? '').indexOf('opencode/big-pickle') (host.textContent ?? '').indexOf('opencode/big-pickle')
); );
expect((host.textContent ?? '').indexOf('opencode/big-pickle')).toBeLessThan( expect((host.textContent ?? '').indexOf('opencode/big-pickle')).toBeLessThan(
@ -671,29 +690,6 @@ describe('RuntimeProviderManagementPanelView', () => {
expect((host.textContent ?? '').indexOf('qwen/qwen3-coder-plus')).toBeLessThan( expect((host.textContent ?? '').indexOf('qwen/qwen3-coder-plus')).toBeLessThan(
(host.textContent ?? '').indexOf('openrouter/openai/gpt-oss-20b:free') (host.textContent ?? '').indexOf('openrouter/openai/gpt-oss-20b:free')
); );
await act(async () => {
const checkbox = Array.from(host.querySelectorAll('button')).find(
(button) => button.getAttribute('role') === 'checkbox'
);
checkbox?.click();
await Promise.resolve();
});
expect(host.textContent).toContain('qwen/qwen3-coder-flash');
expect(host.textContent).toContain('openai/gpt-oss-120b:free');
expect(host.textContent).not.toContain('opencode/big-pickle');
expect(host.textContent).not.toContain('qwen/qwen3-coder-plus');
expect(host.textContent).not.toContain('openrouter/openai/gpt-oss-20b:free');
await act(async () => {
const checkbox = Array.from(host.querySelectorAll('button')).find(
(button) => button.getAttribute('role') === 'checkbox'
);
checkbox?.click();
await Promise.resolve();
});
await act(async () => { await act(async () => {
host host
.querySelector( .querySelector(
@ -854,14 +850,10 @@ describe('RuntimeProviderManagementPanelView', () => {
expect(logo).not.toBeNull(); expect(logo).not.toBeNull();
expect(logo?.className).toContain('runtime-provider-brand-icon'); expect(logo?.className).toContain('runtime-provider-brand-icon');
expect(logo?.querySelector('svg,img')).not.toBeNull(); expect(logo?.querySelector('svg,img')).not.toBeNull();
expect(logo?.getAttribute('style')).toContain( expect(logo?.getAttribute('style')).toContain('--runtime-provider-brand-fallback-background');
'--runtime-provider-brand-fallback-background'
);
expect(logo?.getAttribute('style')).toContain('--runtime-provider-brand-fallback-border'); expect(logo?.getAttribute('style')).toContain('--runtime-provider-brand-fallback-border');
if (logo?.querySelector('svg')) { if (logo?.querySelector('svg')) {
expect(logo.getAttribute('style')).toContain( expect(logo.getAttribute('style')).toContain('--runtime-provider-brand-fallback-color');
'--runtime-provider-brand-fallback-color'
);
} }
} }
}); });

View file

@ -7,104 +7,94 @@ import {
} from '@renderer/utils/openCodeModelRecommendations'; } from '@renderer/utils/openCodeModelRecommendations';
describe('getOpenCodeTeamModelRecommendation', () => { describe('getOpenCodeTeamModelRecommendation', () => {
it('marks models that passed real OpenCode Agent Teams E2E as recommended', () => { it('keeps Claude Sonnet 4.6 as tested while recommendations are disabled', () => {
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-coder-flash')).toMatchObject({ expect(
level: 'recommended', getOpenCodeTeamModelRecommendation('openrouter/anthropic/claude-sonnet-4.6')
label: 'Recommended', ).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(isOpenCodeTeamModelRecommended('openrouter/anthropic/claude-sonnet-4.6')).toBe(false);
});
it('marks models that passed real OpenCode Agent Teams smoke E2E as tested', () => {
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/codestral-2508')).toMatchObject({
level: 'tested',
label: 'Tested',
}); });
expect( expect(
getOpenCodeTeamModelRecommendation(' OPENROUTER/GOOGLE/GEMINI-3-FLASH-PREVIEW ') getOpenCodeTeamModelRecommendation(' OPENROUTER/GOOGLE/GEMINI-3-FLASH-PREVIEW ')
).toMatchObject({ ).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/moonshotai/kimi-k2.6')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/moonshotai/kimi-k2.6')).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-5.1')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-5.1')).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-5')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2.7')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-3.1-pro-preview')
).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/anthropic/claude-sonnet-4.6')
).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/anthropic/claude-opus-4.6')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/anthropic/claude-opus-4.7')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/devstral-2512')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.4')).toMatchObject({
level: 'recommended',
label: 'Recommended',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.3-codex')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.3-codex')).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4-fast')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5-nano')).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.1-fast')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2')).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/xiaomi/mimo-v2-pro')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.1-codex')).toMatchObject({
level: 'recommended',
label: 'Recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-max')).toMatchObject({
level: 'recommended',
label: 'Recommended',
}); });
expect( expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-medium-3.1') getOpenCodeTeamModelRecommendation('openrouter/google/gemma-4-26b-a4b-it')
).toMatchObject({ ).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.1-fast')).toMatchObject({
level: 'tested',
label: 'Tested',
}); });
expect( expect(
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-3.1-flash-lite-preview') getOpenCodeTeamModelRecommendation('openrouter/google/gemini-3.1-flash-lite-preview')
).toMatchObject({ ).toMatchObject({
level: 'recommended', level: 'tested',
label: 'Recommended', label: 'Tested',
}); });
expect(isOpenCodeTeamModelRecommended('openrouter/qwen/qwen3-coder-flash')).toBe(true); expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-4.7-flash')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-4.7')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/stepfun/step-3.5-flash')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2.1')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2.7')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/xiaomi/mimo-v2-pro')).toMatchObject({
level: 'tested',
label: 'Tested',
});
expect(isOpenCodeTeamModelRecommended('openrouter/mistralai/codestral-2508')).toBe(false);
}); });
it('keeps similarly named models distinct when real E2E disagreed', () => { it('keeps similarly named models distinct when real E2E disagreed', () => {
expect(getOpenCodeTeamModelRecommendation('opencode/minimax-m2.5-free')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('opencode/minimax-m2.5-free')).toMatchObject({
level: 'recommended-with-limits', level: 'tested-with-limits',
label: 'Recommended with limits', label: 'Tested with limits',
}); });
expect( expect(
getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2.5:free') getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2.5:free')
@ -113,53 +103,50 @@ describe('getOpenCodeTeamModelRecommendation', () => {
}); });
}); });
it('marks passing free routes as recommended with limits', () => { it('marks models with real launch or messaging failures as not recommended', () => {
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-oss-120b:free')).toMatchObject( for (const modelId of [
{ 'openrouter/openai/gpt-oss-20b:free',
level: 'recommended-with-limits', 'openrouter/openai/gpt-oss-120b:free',
label: 'Recommended with limits', 'openrouter/google/gemini-3-pro-preview',
} 'openrouter/google/gemini-2.5-flash-lite',
); 'openrouter/deepseek/deepseek-v3.2',
expect(isOpenCodeTeamModelRecommended('openrouter/openai/gpt-oss-120b:free')).toBe(true); 'openrouter/x-ai/grok-code-fast-1',
'openrouter/openai/gpt-5.1',
'openrouter/openai/gpt-5.4',
'openrouter/z-ai/glm-5-turbo',
'openrouter/qwen/qwen3.6-plus',
'openrouter/qwen/qwen3-coder-flash',
'openrouter/qwen/qwen3-coder',
'openrouter/google/gemini-3.1-pro-preview',
'openrouter/anthropic/claude-opus-4.6',
'openrouter/mistralai/mistral-medium-3',
'openrouter/nvidia/nemotron-nano-9b-v2',
'openrouter/liquid/lfm-2.5-1.2b-thinking:free',
'openrouter/deepseek/deepseek-r1-distill-llama-70b',
]) {
expect(getOpenCodeTeamModelRecommendation(modelId)).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
}
}); });
it('marks models with real launch or messaging failures as not recommended', () => { it('does not mark provider allowance or depleted-credit failures as model verdicts', () => {
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-oss-20b:free')).toMatchObject({ for (const modelId of [
level: 'not-recommended', 'openrouter/openai/gpt-5.1-codex-max',
label: 'Not recommended', 'openrouter/openai/gpt-5.2-chat',
}); 'openrouter/x-ai/grok-3',
expect( 'openrouter/anthropic/claude-sonnet-4',
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-3-pro-preview') 'openrouter/anthropic/claude-opus-4.1',
).toMatchObject({ 'openrouter/openai/gpt-5-pro',
level: 'not-recommended', 'openrouter/openai/gpt-5.2-pro',
label: 'Not recommended', 'openrouter/openai/gpt-5.4-pro',
}); 'openrouter/moonshotai/kimi-k2.5',
expect( 'openrouter/openai/gpt-5-mini',
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-2.5-flash-lite') 'openrouter/google/gemini-2.5-flash-lite-preview-09-2025',
).toMatchObject({ ]) {
level: 'not-recommended', expect(getOpenCodeTeamModelRecommendation(modelId)).toBeNull();
label: 'Not recommended', }
});
expect(getOpenCodeTeamModelRecommendation('openrouter/deepseek/deepseek-v3.2')).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-code-fast-1')).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.2-codex')).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/moonshotai/kimi-k2-thinking')).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.1-chat')).toMatchObject({
level: 'not-recommended',
label: 'Not recommended',
});
}); });
it('marks OpenRouter routes missing from the OpenCode catalog as unavailable, not bad', () => { it('marks OpenRouter routes missing from the OpenCode catalog as unavailable, not bad', () => {
@ -171,6 +158,124 @@ describe('getOpenCodeTeamModelRecommendation', () => {
level: 'unavailable-in-opencode', level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode', label: 'Unavailable in OpenCode',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-coder:free')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-next-80b-a3b-instruct:free')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-2.0-flash-lite-001')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-4.1-nano')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-4o-mini-2024-07-18')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-4o-mini-search-preview')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-plus')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-turbo')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-235b-a22b-2507')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/deepseek/deepseek-v3.2-exp')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-32b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-14b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-8b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwq-32b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/deepseek/deepseek-chat')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-nemo')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-small-24b-instruct-2501')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/cohere/command-r7b-12-2024')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/cohere/command-r-08-2024')).toMatchObject(
{
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
}
);
expect(getOpenCodeTeamModelRecommendation('openrouter/rekaai/reka-flash-3')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/rekaai/reka-edge')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/nvidia/nemotron-3-nano-30b-a3b')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-01')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/nvidia/llama-3.3-nemotron-super-49b-v1.5')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-max-thinking')).toMatchObject({ expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-max-thinking')).toMatchObject({
level: 'unavailable-in-opencode', level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode', label: 'Unavailable in OpenCode',
@ -181,35 +286,528 @@ describe('getOpenCodeTeamModelRecommendation', () => {
level: 'unavailable-in-opencode', level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode', label: 'Unavailable in OpenCode',
}); });
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/devstral-medium')).toMatchObject( expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/devstral-medium')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/devstral-small')).toMatchObject(
{ {
level: 'unavailable-in-opencode', level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode', label: 'Unavailable in OpenCode',
} }
); );
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/ministral-14b-2512')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/ministral-8b-2512')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/ministral-3b-2512')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/minimax/minimax-m2-her')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/xiaomi/mimo-v2.5')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/xiaomi/mimo-v2.5-pro')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.20')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-3.1-flash-image-preview')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-5v-turbo')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.20-multi-agent')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-small-creative')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5.3-chat')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/voxtral-small-24b-2507')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-5-chat')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-2.5-72b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/tngtech/deepseek-r1t2-chimera')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/google/gemini-2.5-pro-preview')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-saba')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-large-2411')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-30b-a3b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/inclusionai/ling-2.6-1t:free')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/inclusionai/ling-2.6-flash:free')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/meta-llama/llama-3.1-8b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-2.5-7b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/amazon/nova-lite-v1')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-4-32b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/bytedance-seed/seed-1.6-flash')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/meta-llama/llama-4-scout')).toMatchObject(
{
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
}
);
expect(
getOpenCodeTeamModelRecommendation('openrouter/meta-llama/llama-3.3-70b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/bytedance-seed/seed-2.0-mini')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-32b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/alibaba/tongyi-deepresearch-30b-a3b')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/arcee-ai/trinity-large-preview')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/amazon/nova-micro-v1')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/arcee-ai/trinity-mini')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3.5-9b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/essentialai/rnj-1-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/upstage/solar-pro-3')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/allenai/olmo-3.1-32b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-plus-2025-07-28')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/tencent/hy3-preview:free')).toMatchObject(
{
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
}
);
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-8b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/nex-agi/deepseek-v3.1-nex-n1')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/baidu/ernie-4.5-vl-28b-a3b')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/thedrummer/rocinante-12b')).toMatchObject(
{
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
}
);
expect(
getOpenCodeTeamModelRecommendation('openrouter/meta-llama/llama-3.1-70b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-plus-2025-07-28:thinking')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/z-ai/glm-4.6v')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/anthropic/claude-3-haiku')).toMatchObject(
{
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
}
);
expect(
getOpenCodeTeamModelRecommendation('openrouter/bytedance-seed/seed-2.0-lite')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-235b-a22b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3.5-122b-a10b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/deepseek/deepseek-r1-0528')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/amazon/nova-2-lite-v1')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/o3-mini')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mistral-large-2407')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/thedrummer/unslopnemo-12b')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-235b-a22b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-8b-thinking')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/kwaipilot/kat-coder-pro-v2')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/o4-mini-high')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/o3-mini-high')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-4o')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/cohere/command-r-plus-08-2024')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-30b-a3b-thinking')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/sao10k/l3.1-euryale-70b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3.5-27b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/arcee-ai/virtuoso-large')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-3.5-turbo')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/bytedance-seed/seed-1.6')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/nvidia/llama-3.1-nemotron-70b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-vl-max')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-vl-235b-a22b-thinking')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/openai/gpt-audio-mini')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/amazon/nova-pro-v1')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/relace/relace-search')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen-max')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/pixtral-large-2411')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/mistralai/mixtral-8x22b-instruct')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3.5-35b-a3b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/qwen/qwen3-30b-a3b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(getOpenCodeTeamModelRecommendation('openrouter/baidu/ernie-4.5-21b-a3b')).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
expect(
getOpenCodeTeamModelRecommendation('openrouter/nousresearch/hermes-4-70b')
).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
for (const modelId of [
'openrouter/openai/gpt-3.5-turbo-16k',
'openrouter/mistralai/mistral-large',
'openrouter/openai/o4-mini-deep-research',
'openrouter/ai21/jamba-large-1.7',
'openrouter/openai/o3',
'openrouter/openai/gpt-audio',
'openrouter/openai/gpt-4o-audio-preview',
'openrouter/openai/gpt-4o-2024-11-20',
'openrouter/openai/gpt-4o-2024-08-06',
'openrouter/amazon/nova-premier-v1',
'openrouter/anthropic/claude-3.7-sonnet:thinking',
'openrouter/openai/gpt-4o-2024-05-13',
'openrouter/~anthropic/claude-opus-latest',
'openrouter/openai/gpt-4-turbo',
'openrouter/openai/gpt-4-turbo-preview',
'openrouter/openai/gpt-4-1106-preview',
'openrouter/openai/o3-deep-research',
'openrouter/openai/o1',
'openrouter/openai/o3-pro',
'openrouter/anthropic/claude-opus-4.6-fast',
'openrouter/openai/gpt-5.5-pro',
'openrouter/openrouter/auto',
'openrouter/openrouter/pareto-code',
'openrouter/openrouter/bodybuilder',
'openrouter/baidu/qianfan-ocr-fast:free',
'openrouter/google/lyria-3-pro-preview',
'openrouter/ibm-granite/granite-4.0-h-micro',
'openrouter/microsoft/phi-4',
'openrouter/meta-llama/llama-guard-4-12b',
'openrouter/qwen/qwen-vl-plus',
'openrouter/deepseek/deepseek-r1-distill-qwen-32b',
'openrouter/google/lyria-3-clip-preview',
'openrouter/liquid/lfm-2-24b-a2b',
'openrouter/meta-llama/llama-3.2-1b-instruct',
'openrouter/bytedance/ui-tars-1.5-7b',
'openrouter/baidu/ernie-4.5-21b-a3b-thinking',
'openrouter/arcee-ai/spotlight',
'openrouter/meta-llama/llama-3.2-3b-instruct',
'openrouter/meta-llama/llama-guard-3-8b',
'openrouter/nousresearch/hermes-3-llama-3.1-70b',
'openrouter/allenai/olmo-3-32b-think',
'openrouter/tencent/hunyuan-a13b-instruct',
'openrouter/meta-llama/llama-4-maverick',
'openrouter/nvidia/nemotron-nano-12b-v2-vl',
'openrouter/thedrummer/cydonia-24b-v4.1',
'openrouter/microsoft/wizardlm-2-8x22b',
'openrouter/arcee-ai/coder-large',
'openrouter/thedrummer/skyfall-36b-v2',
'openrouter/baidu/ernie-4.5-300b-a47b',
'openrouter/sao10k/l3.3-euryale-70b',
]) {
expect(getOpenCodeTeamModelRecommendation(modelId)).toMatchObject({
level: 'unavailable-in-opencode',
label: 'Unavailable in OpenCode',
});
}
expect(isOpenCodeTeamModelRecommended('openrouter/qwen/qwen3-coder-plus')).toBe(false); expect(isOpenCodeTeamModelRecommended('openrouter/qwen/qwen3-coder-plus')).toBe(false);
}); });
it('does not label noisy or unproven models as good or bad', () => { it('does not label noisy or unproven models as good or bad', () => {
expect(getOpenCodeTeamModelRecommendation('opencode/big-pickle')).toBeNull(); expect(getOpenCodeTeamModelRecommendation('opencode/big-pickle')).toBeNull();
expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.20')).toBeNull(); expect(getOpenCodeTeamModelRecommendation('openrouter/x-ai/grok-4.20-unknown')).toBeNull();
expect(getOpenCodeTeamModelRecommendation('')).toBeNull(); expect(getOpenCodeTeamModelRecommendation('')).toBeNull();
}); });
it('sorts recommended, limited, neutral, unavailable, and not-recommended routes by status', () => { it('sorts tested, tested-with-limits, neutral, unavailable, and not-recommended routes by status', () => {
const models = [ const models = [
'openrouter/openai/gpt-oss-20b:free', 'openrouter/openai/gpt-oss-20b:free',
'openrouter/qwen/qwen3-coder-plus', 'openrouter/qwen/qwen3-coder-plus',
'opencode/big-pickle', 'opencode/big-pickle',
'openrouter/openai/gpt-oss-120b:free', 'opencode/minimax-m2.5-free',
'openrouter/qwen/qwen3-coder-flash', 'openrouter/mistralai/codestral-2508',
'openrouter/anthropic/claude-sonnet-4.6',
]; ];
expect( expect(
[...models].sort((left, right) => compareOpenCodeTeamModelRecommendations(left, right)) [...models].sort((left, right) => compareOpenCodeTeamModelRecommendations(left, right))
).toEqual([ ).toEqual([
'openrouter/qwen/qwen3-coder-flash', 'openrouter/mistralai/codestral-2508',
'openrouter/openai/gpt-oss-120b:free', 'openrouter/anthropic/claude-sonnet-4.6',
'opencode/minimax-m2.5-free',
'opencode/big-pickle', 'opencode/big-pickle',
'openrouter/qwen/qwen3-coder-plus', 'openrouter/qwen/qwen3-coder-plus',
'openrouter/openai/gpt-oss-20b:free', 'openrouter/openai/gpt-oss-20b:free',