fix(runtime-provider): clarify opencode model routes ux

This commit is contained in:
777genius 2026-05-24 21:58:18 +03:00
parent 2b3a184bef
commit c04871747c
5 changed files with 249 additions and 88 deletions

View file

@ -55,16 +55,24 @@
"emptyRecommended": "No recommended models found.", "emptyRecommended": "No recommended models found.",
"emptyRecommendedFree": "No recommended free models found.", "emptyRecommendedFree": "No recommended free models found.",
"freeOnly": "Free only", "freeOnly": "Free only",
"launchableDescription": "Routes you can test or use in the team picker: local config, free built-in models, and current default.", "launchableDescription": "Known routes from OpenCode config, free built-in models, and the current default. Local routes need a successful test before they are ready for team launches.",
"launchableTitle": "Launchable OpenCode models", "launchableTitle": "OpenCode model routes",
"loadingRoutes": "Loading OpenCode model routes...", "loadingRoutes": "Loading OpenCode model routes...",
"noRoutesMatch": "No OpenCode model routes match \"{{query}}\".", "noRoutesMatch": "No OpenCode model routes match \"{{query}}\".",
"noneReported": "No launchable OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.", "noneReported": "No OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.",
"recommendedOnly": "Recommended only", "recommendedOnly": "Recommended only",
"searchPlaceholder": "Search models", "searchPlaceholder": "Search models",
"selectProjectBeforeTesting": "Select a project context before testing models.", "selectProjectBeforeTesting": "Select a project context before testing models.",
"selectProjectBeforeTestingDefaults": "Select a project context before testing or saving OpenCode defaults.", "selectProjectBeforeTestingDefaults": "Select a project context before testing or saving OpenCode defaults.",
"useInTeamPicker": "Use in team picker" "testInProgress": "Model test is already running.",
"useInTeamPicker": "Save for team picker",
"validationContextRequired": "Select a validation context above to enable Test and Set default. Saving for team picker only stores the route for new teams.",
"actionsUnavailable": "Actions are temporarily unavailable.",
"defaultSaveInProgress": "OpenCode default is being saved.",
"routeUnavailableAuth": "This provider requires authentication before this model can be used.",
"routeUnavailableFailed": "This model route failed its last execution test.",
"routeUnavailableGeneric": "This model route cannot be used right now.",
"routeUnavailableUnknown": "This model is the current OpenCode default, but it is not available in the live catalog yet."
}, },
"providers": { "providers": {
"catalog": "OpenCode provider catalog", "catalog": "OpenCode provider catalog",
@ -99,10 +107,11 @@
"searchPlaceholder": "Search model routes" "searchPlaceholder": "Search model routes"
}, },
"badges": { "badges": {
"usedInTeamPicker": "Used in team picker", "usedInTeamPicker": "Saved for team picker",
"free": "free", "free": "free",
"local": "local", "local": "local",
"configured": "configured", "configured": "configured",
"knownRoute": "known route",
"connected": "connected", "connected": "connected",
"verified": "verified", "verified": "verified",
"needsTest": "needs test", "needsTest": "needs test",

View file

@ -55,16 +55,24 @@
"emptyRecommended": "Recommended models не найдены.", "emptyRecommended": "Recommended models не найдены.",
"emptyRecommendedFree": "Recommended free models не найдены.", "emptyRecommendedFree": "Recommended free models не найдены.",
"freeOnly": "Только free", "freeOnly": "Только free",
"launchableDescription": "Routes, которые можно тестировать или использовать в team picker: local config, free built-in models и текущий default.", "launchableDescription": "Известные routes из OpenCode config, free built-in models и текущий default. Local routes нужно проверить тестом перед запуском команд.",
"launchableTitle": "Launchable OpenCode models", "launchableTitle": "Маршруты моделей OpenCode",
"loadingRoutes": "Загрузка OpenCode model routes...", "loadingRoutes": "Загрузка OpenCode model routes...",
"noRoutesMatch": "OpenCode model routes не найдены по запросу \"{{query}}\".", "noRoutesMatch": "OpenCode model routes не найдены по запросу \"{{query}}\".",
"noneReported": "Launchable OpenCode model routes пока не получены. Настройте local route в OpenCode или используйте вкладку Providers для просмотра catalog providers.", "noneReported": "OpenCode model routes пока не получены. Настройте local route в OpenCode или используйте вкладку Providers для просмотра catalog providers.",
"recommendedOnly": "Только recommended", "recommendedOnly": "Только recommended",
"searchPlaceholder": "Поиск моделей", "searchPlaceholder": "Поиск моделей",
"selectProjectBeforeTesting": "Выберите project context перед тестированием моделей.", "selectProjectBeforeTesting": "Выберите project context перед тестированием моделей.",
"selectProjectBeforeTestingDefaults": "Выберите project context перед тестированием или сохранением OpenCode defaults.", "selectProjectBeforeTestingDefaults": "Выберите project context перед тестированием или сохранением OpenCode defaults.",
"useInTeamPicker": "Использовать в team picker" "testInProgress": "Тест модели уже выполняется.",
"useInTeamPicker": "Сохранить для team picker",
"validationContextRequired": "Выберите validation context выше, чтобы включить Test и Set default. Сохранение для team picker только запоминает route для новых команд.",
"actionsUnavailable": "Действия временно недоступны.",
"defaultSaveInProgress": "OpenCode default сохраняется.",
"routeUnavailableAuth": "Этому provider нужна авторизация перед использованием модели.",
"routeUnavailableFailed": "Этот model route не прошёл последний execution test.",
"routeUnavailableGeneric": "Этот model route сейчас нельзя использовать.",
"routeUnavailableUnknown": "Эта модель выбрана текущим OpenCode default, но её пока нет в live catalog."
}, },
"providers": { "providers": {
"catalog": "OpenCode provider catalog", "catalog": "OpenCode provider catalog",
@ -99,10 +107,11 @@
"searchPlaceholder": "Поиск маршрутов моделей" "searchPlaceholder": "Поиск маршрутов моделей"
}, },
"badges": { "badges": {
"usedInTeamPicker": "Используется в выборе команды", "usedInTeamPicker": "Сохранено для team picker",
"free": "free", "free": "free",
"local": "local", "local": "local",
"configured": "настроено", "configured": "настроено",
"knownRoute": "известный route",
"connected": "подключено", "connected": "подключено",
"verified": "проверено", "verified": "проверено",
"needsTest": "нужен тест", "needsTest": "нужен тест",

View file

@ -2852,10 +2852,11 @@ export default interface Resources {
default: 'default'; default: 'default';
failed: 'failed'; failed: 'failed';
free: 'free'; free: 'free';
knownRoute: 'known route';
local: 'local'; local: 'local';
needsTest: 'needs test'; needsTest: 'needs test';
unknown: 'unknown'; unknown: 'unknown';
usedInTeamPicker: 'Used in team picker'; usedInTeamPicker: 'Saved for team picker';
verified: 'verified'; verified: 'verified';
}; };
compatibleEndpoint: { compatibleEndpoint: {
@ -2889,22 +2890,30 @@ export default interface Resources {
searchPlaceholder: 'Search model routes'; searchPlaceholder: 'Search model routes';
}; };
models: { models: {
actionsUnavailable: 'Actions are temporarily unavailable.';
alreadyDefault: 'This is already the selected OpenCode default.'; alreadyDefault: 'This is already the selected OpenCode default.';
defaultSaveInProgress: 'OpenCode default is being saved.';
empty: 'No models found.'; empty: 'No models found.';
emptyFree: 'No free models found.'; emptyFree: 'No free models found.';
emptyRecommended: 'No recommended models found.'; emptyRecommended: 'No recommended models found.';
emptyRecommendedFree: 'No recommended free models found.'; emptyRecommendedFree: 'No recommended free models found.';
freeOnly: 'Free only'; freeOnly: 'Free only';
launchableDescription: 'Routes you can test or use in the team picker: local config, free built-in models, and current default.'; launchableDescription: 'Known routes from OpenCode config, free built-in models, and the current default. Local routes need a successful test before they are ready for team launches.';
launchableTitle: 'Launchable OpenCode models'; launchableTitle: 'OpenCode model routes';
loadingRoutes: 'Loading OpenCode model routes...'; loadingRoutes: 'Loading OpenCode model routes...';
noRoutesMatch: 'No OpenCode model routes match "{{query}}".'; noRoutesMatch: 'No OpenCode model routes match "{{query}}".';
noneReported: 'No launchable OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.'; noneReported: 'No OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.';
recommendedOnly: 'Recommended only'; recommendedOnly: 'Recommended only';
routeUnavailableAuth: 'This provider requires authentication before this model can be used.';
routeUnavailableFailed: 'This model route failed its last execution test.';
routeUnavailableGeneric: 'This model route cannot be used right now.';
routeUnavailableUnknown: 'This model is the current OpenCode default, but it is not available in the live catalog yet.';
searchPlaceholder: 'Search models'; searchPlaceholder: 'Search models';
selectProjectBeforeTesting: 'Select a project context before testing models.'; selectProjectBeforeTesting: 'Select a project context before testing models.';
selectProjectBeforeTestingDefaults: 'Select a project context before testing or saving OpenCode defaults.'; selectProjectBeforeTestingDefaults: 'Select a project context before testing or saving OpenCode defaults.';
useInTeamPicker: 'Use in team picker'; testInProgress: 'Model test is already running.';
useInTeamPicker: 'Save for team picker';
validationContextRequired: 'Select a validation context above to enable Test and Set default. Saving for team picker only stores the route for new teams.';
}; };
providers: { providers: {
catalog: 'OpenCode provider catalog'; catalog: 'OpenCode provider catalog';

View file

@ -14,6 +14,12 @@ import {
SelectValue, SelectValue,
} from '@renderer/components/ui/select'; } from '@renderer/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@renderer/components/ui/tooltip';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import { import {
compareOpenCodeTeamModelRecommendations, compareOpenCodeTeamModelRecommendations,
@ -212,6 +218,33 @@ function isDefaultForScope(
return scopedDefault === model.modelId; return scopedDefault === model.modelId;
} }
const DisabledActionTooltip = ({
reason,
children,
}: {
readonly reason: string | undefined;
readonly children: JSX.Element;
}): JSX.Element => {
if (!reason) {
return children;
}
return (
<TooltipProvider delayDuration={150}>
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex" title={reason} aria-label={reason}>
{children}
</span>
</TooltipTrigger>
<TooltipContent className="max-w-72 text-pretty text-xs leading-relaxed">
{reason}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
function directoryEntryMatchesQuery( function directoryEntryMatchesQuery(
provider: RuntimeProviderDirectoryEntryDto, provider: RuntimeProviderDirectoryEntryDto,
query: string query: string
@ -1449,7 +1482,7 @@ function ModelBadges({
{t('runtimeProvider.badges.local')} {t('runtimeProvider.badges.local')}
</Badge> </Badge>
<Badge className="bg-sky-400/15 px-1.5 py-0 text-[10px] text-sky-200"> <Badge className="bg-sky-400/15 px-1.5 py-0 text-[10px] text-sky-200">
{t('runtimeProvider.badges.configured')} {t('runtimeProvider.badges.knownRoute')}
</Badge> </Badge>
</> </>
) : null} ) : null}
@ -1517,17 +1550,47 @@ function canUseOpenCodeModelRoute(model: RuntimeProviderModelDto): boolean {
); );
} }
function getOpenCodeRouteUnavailableTitle(model: RuntimeProviderModelDto): string | undefined { function getOpenCodeRouteUnavailableTitle(
model: RuntimeProviderModelDto,
t: SettingsT
): string | undefined {
if (isUnknownOpenCodeModelRoute(model)) { if (isUnknownOpenCodeModelRoute(model)) {
return 'This model is the current OpenCode default, but it is not available in the live catalog yet.'; return t('runtimeProvider.models.routeUnavailableUnknown');
} }
if (model.accessKind === 'not_authenticated') { if (model.accessKind === 'not_authenticated') {
return ( return model.accessReason ?? t('runtimeProvider.models.routeUnavailableAuth');
model.accessReason ?? 'This provider requires authentication before this model can be used.'
);
} }
if (model.accessKind === 'execution_failed' || model.proofState === 'failed') { if (model.accessKind === 'execution_failed' || model.proofState === 'failed') {
return model.accessReason ?? 'This model route failed its last execution test.'; return model.accessReason ?? t('runtimeProvider.models.routeUnavailableFailed');
}
return undefined;
}
function getDisabledActionReason(input: {
readonly disabled: boolean;
readonly contextRequiredTitle?: string;
readonly unavailableTitle?: string;
readonly busy: boolean;
readonly busyTitle: string;
readonly alreadyDefault?: boolean;
readonly alreadyDefaultTitle?: string;
readonly capabilityAvailable: boolean;
readonly t: SettingsT;
}): string | undefined {
if (input.disabled) {
return input.t('runtimeProvider.models.actionsUnavailable');
}
if (input.contextRequiredTitle) {
return input.contextRequiredTitle;
}
if (input.busy) {
return input.busyTitle;
}
if (input.alreadyDefault) {
return input.alreadyDefaultTitle;
}
if (!input.capabilityAvailable) {
return input.unavailableTitle ?? input.t('runtimeProvider.models.routeUnavailableGeneric');
} }
return undefined; return undefined;
} }
@ -1863,6 +1926,18 @@ function ConfiguredOpenCodeModelsPanel({
</div> </div>
<div className="mt-3 space-y-2"> <div className="mt-3 space-y-2">
{!hasProjectContext ? (
<div
className="rounded-md border px-3 py-2 text-xs leading-5"
style={{
borderColor: 'rgba(251, 191, 36, 0.28)',
backgroundColor: 'rgba(251, 191, 36, 0.08)',
color: '#fde68a',
}}
>
{t('runtimeProvider.models.validationContextRequired')}
</div>
) : null}
{visibleModels.length === 0 ? ( {visibleModels.length === 0 ? (
<div className="rounded-md border border-dashed border-white/10 px-3 py-3 text-sm text-[var(--color-text-muted)]"> <div className="rounded-md border border-dashed border-white/10 px-3 py-3 text-sm text-[var(--color-text-muted)]">
{t('runtimeProvider.models.noRoutesMatch', { query: query.trim() })} {t('runtimeProvider.models.noRoutesMatch', { query: query.trim() })}
@ -1873,20 +1948,55 @@ function ConfiguredOpenCodeModelsPanel({
const testing = state.testingModelIds.includes(model.modelId); const testing = state.testingModelIds.includes(model.modelId);
const savingDefault = state.savingDefaultModelId === model.modelId; const savingDefault = state.savingDefaultModelId === model.modelId;
const result = state.modelResults[model.modelId]; const result = state.modelResults[model.modelId];
const unavailableTitle = getOpenCodeRouteUnavailableTitle(model); const unavailableTitle = getOpenCodeRouteUnavailableTitle(model, t);
const contextRequiredTitle = hasProjectContext const contextRequiredTitle = hasProjectContext
? undefined ? undefined
: t('runtimeProvider.models.selectProjectBeforeTestingDefaults'); : t('runtimeProvider.models.selectProjectBeforeTestingDefaults');
const alreadyDefaultForScope = isDefaultForScope(model, state, defaultScope); const alreadyDefaultForScope = isDefaultForScope(model, state, defaultScope);
const canTest = const testCapabilityAvailable = canTestOpenCodeModelRoute(model);
!disabled && hasProjectContext && !testing && canTestOpenCodeModelRoute(model); const useCapabilityAvailable = canUseOpenCodeModelRoute(model);
const canUse = !disabled && canUseOpenCodeModelRoute(model); const canTest = !disabled && hasProjectContext && !testing && testCapabilityAvailable;
const canUse = !disabled && useCapabilityAvailable;
const canSetDefault = const canSetDefault =
!disabled && !disabled &&
hasProjectContext && hasProjectContext &&
!savingDefault && !savingDefault &&
!alreadyDefaultForScope && !alreadyDefaultForScope &&
canUseOpenCodeModelRoute(model); useCapabilityAvailable;
const testDisabledReason = canTest
? undefined
: getDisabledActionReason({
disabled,
contextRequiredTitle,
unavailableTitle,
busy: testing,
busyTitle: t('runtimeProvider.models.testInProgress'),
capabilityAvailable: testCapabilityAvailable,
t,
});
const useDisabledReason = canUse
? undefined
: getDisabledActionReason({
disabled,
unavailableTitle,
busy: false,
busyTitle: '',
capabilityAvailable: useCapabilityAvailable,
t,
});
const setDefaultDisabledReason = canSetDefault
? undefined
: getDisabledActionReason({
disabled,
contextRequiredTitle,
unavailableTitle,
busy: savingDefault,
busyTitle: t('runtimeProvider.models.defaultSaveInProgress'),
alreadyDefault: alreadyDefaultForScope,
alreadyDefaultTitle: t('runtimeProvider.models.alreadyDefault'),
capabilityAvailable: useCapabilityAvailable,
t,
});
return ( return (
<div <div
key={model.modelId} key={model.modelId}
@ -1912,61 +2022,57 @@ function ConfiguredOpenCodeModelsPanel({
<ModelBadges model={model} usedForNewTeams={selected} /> <ModelBadges model={model} usedForNewTeams={selected} />
</div> </div>
<div className="flex shrink-0 flex-wrap justify-end gap-1.5"> <div className="flex shrink-0 flex-wrap justify-end gap-1.5">
<Button <DisabledActionTooltip reason={testDisabledReason}>
type="button" <Button
size="sm" type="button"
variant="outline" size="sm"
className="h-8" variant="outline"
disabled={!canTest} className="h-8"
title={canTest ? undefined : (contextRequiredTitle ?? unavailableTitle)} disabled={!canTest}
onClick={() => { onClick={() => {
if (!canTest) return; if (!canTest) return;
void actions.testModel(model.providerId, model.modelId); void actions.testModel(model.providerId, model.modelId);
}} }}
> >
{testing ? ( {testing ? (
<Loader2 className="mr-1 size-3.5 animate-spin" /> <Loader2 className="mr-1 size-3.5 animate-spin" />
) : ( ) : (
<CheckCircle2 className="mr-1 size-3.5" /> <CheckCircle2 className="mr-1 size-3.5" />
)} )}
{t('runtimeProvider.actions.test')} {t('runtimeProvider.actions.test')}
</Button> </Button>
<Button </DisabledActionTooltip>
type="button" <DisabledActionTooltip reason={useDisabledReason}>
size="sm" <Button
variant="ghost" type="button"
className="h-8" size="sm"
disabled={!canUse} variant="ghost"
title={canUse ? undefined : unavailableTitle} className="h-8"
onClick={() => { disabled={!canUse}
if (!canUse) return; onClick={() => {
actions.useModelForNewTeams(model.modelId); if (!canUse) return;
}} actions.useModelForNewTeams(model.modelId);
> }}
{t('runtimeProvider.models.useInTeamPicker')} >
</Button> {t('runtimeProvider.models.useInTeamPicker')}
<Button </Button>
type="button" </DisabledActionTooltip>
size="sm" <DisabledActionTooltip reason={setDefaultDisabledReason}>
variant="ghost" <Button
className="h-8" type="button"
disabled={!canSetDefault} size="sm"
title={ variant="ghost"
canSetDefault className="h-8"
? undefined disabled={!canSetDefault}
: (contextRequiredTitle ?? onClick={() => {
(alreadyDefaultForScope if (!canSetDefault) return;
? t('runtimeProvider.models.alreadyDefault') void actions.setDefaultModel(model.providerId, model.modelId, defaultScope);
: unavailableTitle)) }}
} >
onClick={() => { {savingDefault ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : null}
if (!canSetDefault) return; {getDefaultScopeButtonLabel(defaultScope, t)}
void actions.setDefaultModel(model.providerId, model.modelId, defaultScope); </Button>
}} </DisabledActionTooltip>
>
{savingDefault ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : null}
{getDefaultScopeButtonLabel(defaultScope, t)}
</Button>
</div> </div>
</div> </div>
<ModelResult result={result} /> <ModelResult result={result} />

View file

@ -653,9 +653,10 @@ describe('RuntimeProviderManagementPanelView', () => {
const row = host.querySelector<HTMLElement>( const row = host.querySelector<HTMLElement>(
'[data-testid="configured-opencode-model-row-llama.cpp/qwen-test:0.5b"]' '[data-testid="configured-opencode-model-row-llama.cpp/qwen-test:0.5b"]'
); );
expect(host.textContent).toContain('Launchable OpenCode models'); expect(host.textContent).toContain('OpenCode model routes');
expect(host.textContent).toContain('Known routes from OpenCode config');
expect(row?.textContent).toContain('local'); expect(row?.textContent).toContain('local');
expect(row?.textContent).toContain('configured'); expect(row?.textContent).toContain('known route');
expect(row?.textContent).toContain('needs test'); expect(row?.textContent).toContain('needs test');
const buttons = Array.from(row?.querySelectorAll('button') ?? []); const buttons = Array.from(row?.querySelectorAll('button') ?? []);
@ -664,7 +665,7 @@ describe('RuntimeProviderManagementPanelView', () => {
await Promise.resolve(); await Promise.resolve();
}); });
await act(async () => { await act(async () => {
buttons.find((button) => button.textContent?.includes('Use in team picker'))?.click(); buttons.find((button) => button.textContent?.includes('Save for team picker'))?.click();
await Promise.resolve(); await Promise.resolve();
}); });
await act(async () => { await act(async () => {
@ -847,10 +848,30 @@ describe('RuntimeProviderManagementPanelView', () => {
await Promise.resolve(); await Promise.resolve();
}); });
expect(host.textContent).toContain('Launchable OpenCode models'); expect(host.textContent).toContain('OpenCode model routes');
expect(host.textContent).toContain('llama.cpp/qwen-test:0.5b'); expect(host.textContent).toContain('llama.cpp/qwen-test:0.5b');
expect(host.textContent).toContain(
'Select a validation context above to enable Test and Set default'
);
expect(host.textContent).toContain('Providers'); expect(host.textContent).toContain('Providers');
expect(host.querySelector('[data-testid="runtime-provider-row-openrouter"]')).toBeNull(); expect(host.querySelector('[data-testid="runtime-provider-row-openrouter"]')).toBeNull();
const row = host.querySelector<HTMLElement>(
'[data-testid="configured-opencode-model-row-llama.cpp/qwen-test:0.5b"]'
);
const buttons = Array.from(row?.querySelectorAll('button') ?? []);
expect(buttons.map((button) => [button.textContent?.trim(), button.disabled])).toEqual([
['Test', true],
['Save for team picker', false],
['Set all-projects default', true],
]);
expect(
Array.from(row?.querySelectorAll('[title]') ?? []).some(
(element) =>
element.getAttribute('title') ===
'Select a project context before testing or saving OpenCode defaults.'
)
).toBe(true);
}); });
it('shows unknown OpenCode defaults without enabling launch actions', async () => { it('shows unknown OpenCode defaults without enabling launch actions', async () => {
@ -897,6 +918,13 @@ describe('RuntimeProviderManagementPanelView', () => {
const buttons = Array.from(row?.querySelectorAll('button') ?? []); const buttons = Array.from(row?.querySelectorAll('button') ?? []);
expect(buttons.map((button) => button.disabled)).toEqual([true, true, true]); expect(buttons.map((button) => button.disabled)).toEqual([true, true, true]);
expect(
Array.from(row?.querySelectorAll('[title]') ?? []).some(
(element) =>
element.getAttribute('title') ===
'This model is the current OpenCode default, but it is not available in the live catalog yet.'
)
).toBe(true);
await act(async () => { await act(async () => {
buttons.forEach((button) => button.click()); buttons.forEach((button) => button.click());
await Promise.resolve(); await Promise.resolve();
@ -1807,7 +1835,7 @@ 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 in team picker'); expect(host.textContent).toContain('Saved for team picker');
expect(host.textContent).toContain('Model probe passed'); expect(host.textContent).toContain('Model probe passed');
expect(host.textContent).toContain('Recommended'); expect(host.textContent).toContain('Recommended');
expect(host.textContent).toContain('Not recommended'); expect(host.textContent).toContain('Not recommended');
@ -1818,7 +1846,7 @@ describe('RuntimeProviderManagementPanelView', () => {
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(
(button) => button.textContent?.trim() === 'Use in team picker' (button) => button.textContent?.trim() === 'Save for team picker'
) )
).toBe(false); ).toBe(false);
expect( expect(