agent-ecosystem/test/renderer/features/runtime-provider-management/useRuntimeProviderManagement.test.ts
2026-06-02 09:46:43 +03:00

2208 lines
63 KiB
TypeScript

import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
type RuntimeProviderManagementActions,
type RuntimeProviderManagementState,
useRuntimeProviderManagement,
} from '../../../../src/features/runtime-provider-management/renderer/hooks/useRuntimeProviderManagement';
import {
getStoredCreateTeamModel,
getStoredCreateTeamProvider,
} from '../../../../src/renderer/services/createTeamPreferences';
import type {
RuntimeProviderConnectionDto,
RuntimeProviderDirectoryEntryDto,
RuntimeProviderManagementDirectoryResponse,
RuntimeProviderManagementModelTestResponse,
RuntimeProviderManagementProviderResponse,
RuntimeProviderManagementSetupFormResponse,
RuntimeProviderManagementViewDto,
RuntimeProviderManagementViewResponse,
} from '../../../../src/features/runtime-provider-management/contracts';
import type { ElectronAPI } from '../../../../src/shared/types/api';
function installRuntimeProviderManagementApi(
response: RuntimeProviderManagementModelTestResponse
): void {
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
testModel: vi.fn(() => Promise.resolve(response)),
},
} as unknown as ElectronAPI,
});
}
function createRuntimeView(
providers: readonly RuntimeProviderConnectionDto[] = []
): RuntimeProviderManagementViewDto {
return {
runtimeId: 'opencode',
title: 'OpenCode',
runtime: {
state: 'ready',
cliPath: '/opt/homebrew/bin/opencode',
version: '1.0.0',
managedProfile: 'active',
localAuth: 'synced',
},
providers,
defaultModel: null,
fallbackModel: null,
diagnostics: [],
};
}
function createOpenAiLocalProvider(): RuntimeProviderConnectionDto {
return {
providerId: 'openai',
displayName: 'OpenAI',
state: 'connected',
ownership: ['local'],
recommended: true,
modelCount: 12,
defaultModelId: null,
authMethods: ['oauth'],
actions: [],
detail: 'Connected via local OpenCode credential',
};
}
function createOpenAiLocalDirectoryEntry(): RuntimeProviderDirectoryEntryDto {
return {
...createOpenAiLocalProvider(),
setupKind: 'connected',
sources: ['opencode-provider'],
sourceLabel: 'OpenCode catalog',
providerSource: 'models.dev',
metadata: {
hasKnownModels: true,
requiresManualConfig: false,
supportedInlineAuth: false,
configuredAuthless: false,
},
};
}
describe('useRuntimeProviderManagement', () => {
let host: HTMLDivElement;
let state: RuntimeProviderManagementState | null = null;
let actions: RuntimeProviderManagementActions | null = null;
function Harness(): React.ReactElement {
const hook = useRuntimeProviderManagement({
runtimeId: 'opencode',
enabled: false,
});
state = hook[0];
actions = hook[1];
return React.createElement('div');
}
function EnabledHarness(props: { projectPath?: string | null }): React.ReactElement {
const hook = useRuntimeProviderManagement({
runtimeId: 'opencode',
enabled: true,
projectPath: props.projectPath,
});
state = hook[0];
actions = hook[1];
return React.createElement('div');
}
function ConfigurableHarness(props: {
enabled: boolean;
projectPath?: string | null;
}): React.ReactElement {
const hook = useRuntimeProviderManagement({
runtimeId: 'opencode',
enabled: props.enabled,
projectPath: props.projectPath,
});
state = hook[0];
actions = hook[1];
return React.createElement('div');
}
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
host = document.createElement('div');
document.body.appendChild(host);
window.localStorage.clear();
state = null;
actions = null;
});
afterEach(() => {
Reflect.deleteProperty(window, 'electronAPI');
document.body.innerHTML = '';
vi.unstubAllGlobals();
});
it('uses a clicked model as the app default for new teams without a global success banner', async () => {
const modelId = 'openrouter/openai/gpt-oss-20b:free';
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
actions?.useModelForNewTeams(modelId);
});
expect(state?.selectedModelId).toBe(modelId);
expect(state?.successMessage).toBeNull();
expect(getStoredCreateTeamProvider()).toBe('opencode');
expect(getStoredCreateTeamModel('opencode')).toBe(modelId);
});
it('passes projectPath to the runtime provider management API', async () => {
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: createRuntimeView(),
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
expect(loadView).toHaveBeenCalledWith({
runtimeId: 'opencode',
projectPath: '/tmp/project-a',
});
});
it('clears structured errors and stale provider state when disabled', async () => {
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'runtime-misconfigured',
message: 'OpenCode provider settings are using the wrong runtime binary.',
recoverable: true,
diagnostics: {
summary: 'OpenCode provider settings are using the wrong runtime binary.',
likelyCause:
'The app resolved the OpenCode CLI itself as the Agent Teams runtime binary.',
binaryPath: '/opt/homebrew/bin/opencode',
command:
'/opt/homebrew/bin/opencode runtime providers view --runtime opencode --json --compact',
projectPath: null,
exitCode: null,
stderrPreview: null,
stdoutPreview: null,
hints: ['Those environment variables must not point to opencode.'],
},
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(ConfigurableHarness, { enabled: true }));
await Promise.resolve();
});
await act(async () => {
await vi.waitFor(() => {
expect(state?.error ?? '').toContain('wrong runtime binary');
});
});
expect(state?.errorDiagnostics?.binaryPath).toBe('/opt/homebrew/bin/opencode');
await act(async () => {
root.render(React.createElement(ConfigurableHarness, { enabled: false }));
await Promise.resolve();
});
expect(state?.view).toBeNull();
expect(state?.selectedProviderId).toBeNull();
expect(state?.error).toBeNull();
expect(state?.errorDiagnostics).toBeNull();
expect(state?.loading).toBe(false);
});
it('ignores pending directory and setup-form responses after being disabled', async () => {
let resolveDirectory:
| ((response: RuntimeProviderManagementDirectoryResponse) => void)
| null = null;
let resolveSetupForm:
| ((response: RuntimeProviderManagementSetupFormResponse) => void)
| null = null;
const directoryResponse = new Promise<RuntimeProviderManagementDirectoryResponse>(
(resolve) => {
resolveDirectory = resolve;
}
);
const setupFormResponse = new Promise<RuntimeProviderManagementSetupFormResponse>(
(resolve) => {
resolveSetupForm = resolve;
}
);
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: createRuntimeView(),
})
);
const loadProviderDirectory = vi.fn(() => directoryResponse);
const loadSetupForm = vi.fn(() => setupFormResponse);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
loadSetupForm,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(ConfigurableHarness, { enabled: true }));
await Promise.resolve();
});
await act(async () => {
await vi.waitFor(() => {
expect(loadProviderDirectory).toHaveBeenCalled();
});
actions?.startConnect('openrouter');
});
await act(async () => {
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
await act(async () => {
root.render(React.createElement(ConfigurableHarness, { enabled: false }));
await Promise.resolve();
});
await act(async () => {
resolveDirectory?.({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
entries: [createOpenAiLocalDirectoryEntry()],
diagnostics: [],
fetchedAt: '2026-05-22T00:00:00.000Z',
},
});
resolveSetupForm?.({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openrouter',
displayName: 'OpenRouter',
method: 'api',
supported: true,
title: 'Connect OpenRouter',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
});
await Promise.resolve();
await Promise.resolve();
});
expect(state?.directoryEntries).toEqual([]);
expect(state?.directoryLoaded).toBe(false);
expect(state?.setupForm).toBeNull();
expect(state?.activeFormProviderId).toBeNull();
expect(state?.setupFormLoading).toBe(false);
});
it('ignores stale provider views after project context changes', async () => {
let resolveProjectA:
| ((response: {
schemaVersion: 1;
runtimeId: 'opencode';
view: RuntimeProviderManagementViewDto;
}) => void)
| null = null;
const projectAResponse = new Promise<{
schemaVersion: 1;
runtimeId: 'opencode';
view: RuntimeProviderManagementViewDto;
}>((resolve) => {
resolveProjectA = resolve;
});
const loadView = vi.fn((input: { projectPath?: string | null }) => {
if (input.projectPath === '/tmp/project-a') {
return projectAResponse;
}
return Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: '/tmp/project-b',
defaultModel: 'opencode/project-b',
},
});
});
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-b' }));
await Promise.resolve();
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
await act(async () => {
resolveProjectA?.({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: '/tmp/project-a',
defaultModel: 'opencode/project-a',
},
});
await Promise.resolve();
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
expect(state?.view?.defaultModel).toBe('opencode/project-b');
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('restarts provider directory loading when project context changes while loading', async () => {
let resolveProjectADirectory:
| ((response: RuntimeProviderManagementDirectoryResponse) => void)
| null = null;
let resolveProjectBDirectory:
| ((response: RuntimeProviderManagementDirectoryResponse) => void)
| null = null;
const projectBEntry: RuntimeProviderDirectoryEntryDto = {
...createOpenAiLocalDirectoryEntry(),
providerId: 'project-b-provider',
displayName: 'Project B Provider',
};
const loadView = vi.fn((input: { projectPath?: string | null }) =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: input.projectPath ?? null,
},
})
);
const loadProviderDirectory = vi.fn((input: { projectPath?: string | null }) => {
if (input.projectPath === '/tmp/project-a') {
return new Promise<RuntimeProviderManagementDirectoryResponse>((resolve) => {
resolveProjectADirectory = resolve;
});
}
return new Promise<RuntimeProviderManagementDirectoryResponse>((resolve) => {
resolveProjectBDirectory = resolve;
});
});
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 10));
await vi.waitFor(() => {
expect(loadProviderDirectory).toHaveBeenCalledWith({
runtimeId: 'opencode',
projectPath: '/tmp/project-a',
query: null,
filter: 'all',
limit: 50,
cursor: null,
refresh: false,
});
});
});
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-b' }));
await Promise.resolve();
});
await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 10));
await vi.waitFor(() => {
expect(loadProviderDirectory).toHaveBeenCalledWith({
runtimeId: 'opencode',
projectPath: '/tmp/project-b',
query: null,
filter: 'all',
limit: 50,
cursor: null,
refresh: false,
});
});
});
await act(async () => {
resolveProjectBDirectory?.({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-05-22T00:00:00.000Z',
entries: [projectBEntry],
diagnostics: [],
},
});
await Promise.resolve();
});
expect(state?.directoryEntries.map((entry) => entry.providerId)).toEqual([
'project-b-provider',
]);
await act(async () => {
resolveProjectADirectory?.({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-05-22T00:00:00.000Z',
entries: [createOpenAiLocalDirectoryEntry()],
diagnostics: [],
},
});
await Promise.resolve();
});
expect(state?.directoryEntries.map((entry) => entry.providerId)).toEqual([
'project-b-provider',
]);
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('drops stale model probe results after project context changes', async () => {
const modelId = 'llama.cpp/qwen-test:0.5b';
let resolveProbe: ((value: RuntimeProviderManagementModelTestResponse) => void) | null = null;
const loadView = vi.fn((input: { projectPath?: string | null }) =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: input.projectPath ?? null,
defaultModel: input.projectPath === '/tmp/project-b' ? 'opencode/project-b' : null,
},
})
);
const testModel = vi.fn(
() =>
new Promise<RuntimeProviderManagementModelTestResponse>((resolve) => {
resolveProbe = resolve;
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
testModel,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
let probe: Promise<void> | null = null;
await act(async () => {
probe = actions?.testModel('llama.cpp', modelId) ?? null;
await Promise.resolve();
});
expect(testModel).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'llama.cpp',
modelId,
projectPath: '/tmp/project-a',
});
expect(state?.testingModelIds).toEqual([modelId]);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-b' }));
await Promise.resolve();
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
expect(state?.testingModelIds).toEqual([]);
await act(async () => {
resolveProbe?.({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'llama.cpp',
modelId,
ok: true,
availability: 'available',
message: 'Stale project A probe passed',
diagnostics: [],
},
});
await probe;
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
expect(state?.modelResults[modelId]).toBeUndefined();
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('drops stale set-default responses after project context changes', async () => {
const projectAModelId = 'llama.cpp/project-a:0.5b';
let resolveSetDefault: ((value: RuntimeProviderManagementViewResponse) => void) | null = null;
const loadView = vi.fn((input: { projectPath?: string | null }) =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: input.projectPath ?? null,
defaultModel: input.projectPath === '/tmp/project-b' ? 'opencode/project-b' : null,
},
})
);
const setDefaultModel = vi.fn(
() =>
new Promise<RuntimeProviderManagementViewResponse>((resolve) => {
resolveSetDefault = resolve;
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
setDefaultModel,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
let setDefault: Promise<void> | null = null;
await act(async () => {
setDefault = actions?.setDefaultModel('llama.cpp', projectAModelId, 'project') ?? null;
await Promise.resolve();
});
expect(setDefaultModel).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'llama.cpp',
modelId: projectAModelId,
probe: true,
scope: 'project',
projectPath: '/tmp/project-a',
});
expect(state?.savingDefaultModelId).toBe(projectAModelId);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-b' }));
await Promise.resolve();
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
expect(state?.savingDefaultModelId).toBeNull();
await act(async () => {
resolveSetDefault?.({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: '/tmp/project-a',
defaultModel: projectAModelId,
},
});
await setDefault;
});
expect(state?.view?.projectPath).toBe('/tmp/project-b');
expect(state?.view?.defaultModel).toBe('opencode/project-b');
expect(state?.selectedModelId).toBeNull();
expect(state?.successMessage).toBeNull();
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('clears pending provider save state after project context changes', async () => {
const connectedProvider: RuntimeProviderConnectionDto = {
...createOpenAiLocalProvider(),
ownership: ['managed'],
detail: 'Connected via managed OpenCode credential',
};
let resolveConnect: ((value: RuntimeProviderManagementProviderResponse) => void) | null =
null;
const loadView = vi.fn((input: { projectPath?: string | null }) =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
projectPath: input.projectPath ?? null,
defaultModel: input.projectPath === '/tmp/project-b' ? 'opencode/project-b' : null,
},
})
);
const loadProviderDirectory = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [createOpenAiLocalDirectoryEntry()],
diagnostics: [],
},
})
);
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openai',
displayName: 'OpenAI',
method: 'api',
supported: true,
title: 'Connect OpenAI',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
})
);
const connectProvider = vi.fn(
() =>
new Promise<RuntimeProviderManagementProviderResponse>((resolve) => {
resolveConnect = resolve;
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
loadSetupForm,
connectProvider,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
actions?.startConnect('openai');
actions?.setApiKeyValue('sk-project-a');
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
let submitPromise: Promise<void> | null = null;
await act(async () => {
submitPromise = actions?.submitConnect('openai') ?? null;
await vi.waitFor(() => {
expect(connectProvider).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openai',
method: 'api',
apiKey: 'sk-project-a',
metadata: {},
projectPath: '/tmp/project-a',
});
});
await Promise.resolve();
});
expect(state?.savingProviderId).toBe('openai');
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-b' }));
await Promise.resolve();
await Promise.resolve();
});
await vi.waitFor(() => {
expect(loadView).toHaveBeenCalledWith({
runtimeId: 'opencode',
projectPath: '/tmp/project-b',
});
});
expect(state?.savingProviderId).toBeNull();
expect(state?.activeFormProviderId).toBeNull();
await act(async () => {
resolveConnect?.({
schemaVersion: 1,
runtimeId: 'opencode',
provider: connectedProvider,
});
await submitPromise;
});
expect(state?.view?.providers).toEqual([]);
expect(state?.savingProviderId).toBeNull();
expect(state?.setupSubmitError).toBeNull();
await act(async () => {
root.unmount();
await Promise.resolve();
});
});
it('refreshes view and catalog after forgetting managed auth while local auth remains', async () => {
const localProvider = createOpenAiLocalProvider();
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: createRuntimeView([localProvider]),
})
);
const loadProviderDirectory = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [createOpenAiLocalDirectoryEntry()],
diagnostics: [],
},
})
);
const forgetCredential = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
provider: localProvider,
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
forgetCredential,
loadModels: vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
models: {
runtimeId: 'opencode',
providerId: 'openai',
models: [],
defaultModelId: null,
diagnostics: [],
},
})
),
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await vi.waitFor(() => {
expect(loadView).toHaveBeenCalledTimes(1);
});
await act(async () => {
await actions?.forgetProvider('openai');
});
expect(forgetCredential).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openai',
projectPath: '/tmp/project-a',
});
expect(loadView).toHaveBeenCalledTimes(2);
const refreshDirectoryArgs = {
runtimeId: 'opencode',
projectPath: '/tmp/project-a',
query: null,
filter: 'all',
limit: 50,
cursor: null,
refresh: true,
};
expect(loadProviderDirectory).toHaveBeenCalledWith(refreshDirectoryArgs);
expect(state?.successMessage).toBe(
'Managed credential removed. Provider remains connected through local OpenCode credentials.'
);
await act(async () => {
await actions?.refreshDirectory();
});
expect(loadView).toHaveBeenCalledTimes(3);
expect(
loadProviderDirectory.mock.calls.filter((call) => {
const input = (call as unknown[])[0] as { refresh?: boolean } | undefined;
return input?.refresh === true;
})
).toHaveLength(2);
expect(state?.successMessage).toBeNull();
await act(async () => {
root.unmount();
});
});
it('keeps connect action busy until the post-connect refresh finishes', async () => {
const disconnectedProvider: RuntimeProviderConnectionDto = {
...createOpenAiLocalProvider(),
state: 'not-connected',
ownership: [],
modelCount: 0,
actions: [
{
id: 'connect',
label: 'Connect',
enabled: true,
disabledReason: null,
requiresSecret: true,
ownershipScope: 'managed',
},
],
detail: null,
};
const connectedProvider = createOpenAiLocalProvider();
const initialViewResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
view: createRuntimeView([disconnectedProvider]),
};
const refreshedViewResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
view: createRuntimeView([connectedProvider]),
};
const directoryResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
directory: {
runtimeId: 'opencode' as const,
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all' as const,
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [createOpenAiLocalDirectoryEntry()],
diagnostics: [],
},
};
let resolveRefreshView: (() => void) | null = null;
let resolveRefreshDirectory: (() => void) | null = null;
const loadView = vi
.fn()
.mockResolvedValueOnce(initialViewResponse)
.mockImplementation(
() =>
new Promise<typeof refreshedViewResponse>((resolve) => {
resolveRefreshView = () => resolve(refreshedViewResponse);
})
);
const loadProviderDirectory = vi
.fn()
.mockResolvedValueOnce(directoryResponse)
.mockImplementation(
() =>
new Promise<typeof directoryResponse>((resolve) => {
resolveRefreshDirectory = () => resolve(directoryResponse);
})
);
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openai',
displayName: 'OpenAI',
method: 'api',
supported: true,
title: 'Connect OpenAI',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
})
);
const connectProvider = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
provider: connectedProvider,
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
loadSetupForm,
connectProvider,
loadModels: vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
models: {
runtimeId: 'opencode',
providerId: 'openai',
models: [],
defaultModelId: null,
diagnostics: [],
},
})
),
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
actions?.startConnect('openai');
actions?.setApiKeyValue('sk-good-value');
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
let submitPromise: Promise<void> | null = null;
await act(async () => {
submitPromise = actions?.submitConnect('openai') ?? null;
await vi.waitFor(() => {
expect(connectProvider).toHaveBeenCalled();
});
await Promise.resolve();
});
expect(state?.savingProviderId).toBe('openai');
expect(state?.activeFormProviderId).toBeNull();
await act(async () => {
resolveRefreshView?.();
resolveRefreshDirectory?.();
await submitPromise;
});
expect(loadView).toHaveBeenCalledTimes(2);
expect(
loadProviderDirectory.mock.calls.filter((call) => {
const input = (call as unknown[])[0] as { refresh?: boolean } | undefined;
return input?.refresh === true;
})
).toHaveLength(1);
expect(state?.savingProviderId).toBeNull();
await act(async () => {
root.unmount();
});
});
it('keeps provider data visible during catalog refresh', async () => {
const localProvider = { ...createOpenAiLocalProvider(), modelCount: 0 };
const localDirectoryEntry = { ...createOpenAiLocalDirectoryEntry(), modelCount: 0 };
const viewResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
view: createRuntimeView([localProvider]),
};
const directoryResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
directory: {
runtimeId: 'opencode' as const,
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all' as const,
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [localDirectoryEntry],
diagnostics: [],
},
};
let resolveRefreshView: (() => void) | null = null;
let resolveRefreshDirectory: (() => void) | null = null;
const loadView = vi
.fn()
.mockResolvedValueOnce(viewResponse)
.mockImplementation(
() =>
new Promise<typeof viewResponse>((resolve) => {
resolveRefreshView = () => resolve(viewResponse);
})
);
const loadProviderDirectory = vi
.fn()
.mockResolvedValueOnce(directoryResponse)
.mockImplementation(
() =>
new Promise<typeof directoryResponse>((resolve) => {
resolveRefreshDirectory = () => resolve(directoryResponse);
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
loadModels: vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
models: {
runtimeId: 'opencode',
providerId: 'openai',
models: [],
defaultModelId: null,
diagnostics: [],
},
})
),
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 10));
});
await act(async () => {
await vi.waitFor(() => {
expect(state?.providers).toHaveLength(1);
expect(state?.directoryEntries).toHaveLength(1);
});
});
let refreshPromise: Promise<void> | null = null;
await act(async () => {
refreshPromise = actions?.refreshDirectory() ?? null;
await Promise.resolve();
});
expect(state?.loading).toBe(false);
expect(state?.directoryRefreshing).toBe(true);
expect(state?.providers).toHaveLength(1);
expect(state?.directoryEntries).toHaveLength(1);
await act(async () => {
resolveRefreshView?.();
resolveRefreshDirectory?.();
await refreshPromise;
});
expect(state?.loading).toBe(false);
expect(state?.directoryRefreshing).toBe(false);
await act(async () => {
root.unmount();
});
});
it('lazy-loads provider directory and ignores stale search responses', async () => {
let resolveFirst: ((value: unknown) => void) | null = null;
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
runtimeId: 'opencode',
title: 'OpenCode',
runtime: {
state: 'ready',
cliPath: '/opt/homebrew/bin/opencode',
version: '1.0.0',
managedProfile: 'active',
localAuth: 'synced',
},
providers: [],
defaultModel: null,
fallbackModel: null,
diagnostics: [],
},
})
);
const deepseekDirectoryResponse = {
schemaVersion: 1 as const,
runtimeId: 'opencode' as const,
directory: {
runtimeId: 'opencode' as const,
totalCount: 1,
returnedCount: 1,
query: 'deep',
filter: 'all' as const,
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [
{
providerId: 'deepseek',
displayName: 'DeepSeek',
state: 'available' as const,
setupKind: 'available-readonly' as const,
ownership: [],
recommended: false,
modelCount: 62,
authMethods: [],
defaultModelId: null,
sources: ['opencode-provider'] as const,
sourceLabel: 'OpenCode catalog',
providerSource: 'models.dev',
detail: null,
actions: [],
metadata: {
hasKnownModels: true,
requiresManualConfig: false,
supportedInlineAuth: false,
configuredAuthless: false,
},
},
],
diagnostics: [],
},
};
const loadProviderDirectory = vi.fn().mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveFirst = resolve;
})
);
loadProviderDirectory.mockResolvedValue(deepseekDirectoryResponse);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 10));
});
await act(async () => {
await vi.waitFor(() => {
expect(loadProviderDirectory).toHaveBeenCalled();
});
});
const callCountBeforeSearch = loadProviderDirectory.mock.calls.length;
act(() => {
actions?.setProviderQuery('deep');
});
await act(async () => {
await new Promise((resolve) => window.setTimeout(resolve, 300));
await vi.waitFor(() => {
expect(loadProviderDirectory.mock.calls.length).toBeGreaterThan(callCountBeforeSearch);
});
});
await act(async () => {
resolveFirst?.({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 1,
returnedCount: 1,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [
{
providerId: 'openrouter',
displayName: 'OpenRouter',
state: 'connected',
setupKind: 'connected',
ownership: ['managed'],
recommended: true,
modelCount: 174,
authMethods: ['api'],
defaultModelId: null,
sources: ['opencode-provider'],
sourceLabel: 'OpenCode catalog',
providerSource: 'models.dev',
detail: null,
actions: [],
metadata: {
hasKnownModels: true,
requiresManualConfig: false,
supportedInlineAuth: true,
configuredAuthless: false,
},
},
],
diagnostics: [],
},
});
await Promise.resolve();
});
expect(loadProviderDirectory).toHaveBeenLastCalledWith({
runtimeId: 'opencode',
projectPath: '/tmp/project-a',
query: 'deep',
filter: 'all',
limit: 50,
cursor: null,
refresh: false,
});
expect(state?.directoryEntries.map((entry) => entry.providerId)).toEqual(['deepseek']);
});
it('keeps the API key draft when provider connect fails', async () => {
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openrouter',
displayName: 'OpenRouter',
method: 'api',
supported: true,
title: 'Connect OpenRouter',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
})
);
const connectProvider = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'auth-failed',
message: 'Invalid API key',
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadSetupForm,
connectProvider,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
actions?.startConnect('openrouter');
actions?.setApiKeyValue('sk-bad-value');
});
await act(async () => {
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
await act(async () => {
await actions?.submitConnect('openrouter');
});
expect(connectProvider).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openrouter',
method: 'api',
apiKey: 'sk-bad-value',
metadata: {},
projectPath: null,
});
expect(state?.error).toBeNull();
expect(state?.setupSubmitError).toBe('Invalid API key');
expect(state?.apiKeyValue).toBe('sk-bad-value');
});
it('keeps setup form diagnostics available when submit is attempted after form load failure', async () => {
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'runtime-misconfigured',
message: 'OpenCode provider settings are using the wrong runtime binary.',
recoverable: true,
diagnostics: {
summary: 'OpenCode provider settings are using the wrong runtime binary.',
likelyCause: 'The app resolved the OpenCode CLI itself as the runtime binary.',
binaryPath: '/opt/homebrew/bin/opencode',
command: '/opt/homebrew/bin/opencode runtime providers setup-form',
projectPath: null,
exitCode: null,
stderrPreview: null,
stdoutPreview: null,
hints: ['Those environment variables must not point to opencode.'],
},
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadSetupForm,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
actions?.startConnect('openrouter');
});
await act(async () => {
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
expect(state?.setupFormError).toBe(
'OpenCode provider settings are using the wrong runtime binary.'
);
expect(state?.setupFormErrorDiagnostics?.binaryPath).toBe('/opt/homebrew/bin/opencode');
await act(async () => {
await actions?.submitConnect('openrouter');
});
expect(state?.setupSubmitError).toBe(
'OpenCode provider settings are using the wrong runtime binary.'
);
expect(state?.setupSubmitErrorDiagnostics?.binaryPath).toBe('/opt/homebrew/bin/opencode');
});
it('submits a supported setup form without a secret as a null API key', async () => {
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openai',
displayName: 'OpenAI',
method: 'oauth',
supported: true,
title: 'Connect OpenAI',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'oauth',
secret: null,
prompts: [],
},
})
);
const connectProvider = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
provider: createOpenAiLocalProvider(),
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadSetupForm,
connectProvider,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
actions?.startConnect('openai');
});
await act(async () => {
await vi.waitFor(() => {
expect(loadSetupForm).toHaveBeenCalled();
});
});
await act(async () => {
await actions?.submitConnect('openai');
});
expect(connectProvider).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'openai',
method: 'oauth',
apiKey: null,
metadata: {},
projectPath: null,
});
expect(state?.setupSubmitError).toBeNull();
await act(async () => {
root.unmount();
});
});
it('clears model loading when switching from model picker to setup form', async () => {
const localProvider = createOpenAiLocalProvider();
let resolveModels: ((value: unknown) => void) | null = null;
const loadView = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: createRuntimeView([localProvider]),
})
);
const loadProviderDirectory = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
directory: {
runtimeId: 'opencode',
totalCount: 0,
returnedCount: 0,
query: null,
filter: 'all',
limit: 50,
cursor: null,
nextCursor: null,
fetchedAt: '2026-04-25T00:00:00.000Z',
entries: [],
diagnostics: [],
},
})
);
const loadModels = vi.fn(
() =>
new Promise((resolve) => {
resolveModels = resolve;
})
);
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openrouter',
displayName: 'OpenRouter',
method: 'api',
supported: true,
title: 'Connect OpenRouter',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
loadView,
loadProviderDirectory,
loadModels,
loadSetupForm,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(EnabledHarness, { projectPath: '/tmp/project-a' }));
await Promise.resolve();
});
await act(async () => {
await vi.waitFor(() => {
expect(loadModels).toHaveBeenCalled();
expect(state?.modelsLoading).toBe(true);
});
});
await act(async () => {
actions?.startConnect('openrouter');
await Promise.resolve();
});
expect(state?.modelPickerProviderId).toBeNull();
expect(state?.activeFormProviderId).toBe('openrouter');
expect(state?.modelsLoading).toBe(false);
await act(async () => {
resolveModels?.({
schemaVersion: 1,
runtimeId: 'opencode',
models: {
runtimeId: 'opencode',
providerId: 'openai',
models: [
{
modelId: 'openai/stale-model',
providerId: 'openai',
displayName: 'Stale model',
sourceLabel: 'OpenCode catalog',
free: false,
default: false,
availability: 'available',
},
],
defaultModelId: null,
diagnostics: [],
},
});
await Promise.resolve();
});
expect(state?.modelsLoading).toBe(false);
expect(state?.models).toEqual([]);
await act(async () => {
root.unmount();
});
});
it('tracks concurrent model probes independently', async () => {
const firstModelId = 'openrouter/anthropic/claude-3.5-haiku';
const secondModelId = 'openrouter/openai/gpt-oss-20b:free';
const resolvers = new Map<string, (value: unknown) => void>();
const testModel = vi.fn(
(input: { modelId: string }) =>
new Promise((resolve) => {
resolvers.set(input.modelId, resolve);
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
testModel,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
let firstProbe: Promise<void> | null = null;
let secondProbe: Promise<void> | null = null;
await act(async () => {
firstProbe = actions?.testModel('openrouter', firstModelId) ?? null;
secondProbe = actions?.testModel('openrouter', secondModelId) ?? null;
await Promise.resolve();
});
expect(state?.testingModelIds).toEqual([firstModelId, secondModelId]);
await act(async () => {
resolvers.get(firstModelId)?.({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId: firstModelId,
ok: true,
availability: 'available',
message: 'First passed',
diagnostics: [],
},
});
await firstProbe;
});
expect(state?.testingModelIds).toEqual([secondModelId]);
await act(async () => {
resolvers.get(secondModelId)?.({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId: secondModelId,
ok: true,
availability: 'available',
message: 'Second passed',
diagnostics: [],
},
});
await secondProbe;
});
expect(state?.testingModelIds).toEqual([]);
expect(state?.modelResults[firstModelId]?.message).toBe('First passed');
expect(state?.modelResults[secondModelId]?.message).toBe('Second passed');
await act(async () => {
root.unmount();
});
});
it('drops stale model probe results after leaving the model picker', async () => {
const modelId = 'openrouter/anthropic/claude-3.5-haiku';
let resolveProbe: ((value: RuntimeProviderManagementModelTestResponse) => void) | null = null;
const testModel = vi.fn(
() =>
new Promise<RuntimeProviderManagementModelTestResponse>((resolve) => {
resolveProbe = resolve;
})
);
const loadSetupForm = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
setupForm: {
runtimeId: 'opencode',
providerId: 'openai',
displayName: 'OpenAI',
method: 'api',
supported: true,
title: 'Connect OpenAI',
description: null,
submitLabel: 'Connect',
disabledReason: null,
source: 'curated',
secret: {
key: 'key',
label: 'API key',
placeholder: 'Paste API key',
required: true,
},
prompts: [],
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
testModel,
loadSetupForm,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
act(() => {
actions?.openModelPicker('openrouter', 'use');
});
let probe: Promise<void> | null = null;
await act(async () => {
probe = actions?.testModel('openrouter', modelId) ?? null;
await Promise.resolve();
});
expect(state?.testingModelIds).toEqual([modelId]);
await act(async () => {
actions?.startConnect('openai');
await Promise.resolve();
});
expect(state?.modelPickerProviderId).toBeNull();
expect(state?.testingModelIds).toEqual([]);
await act(async () => {
resolveProbe?.({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId,
ok: true,
availability: 'available',
message: 'Stale probe passed',
diagnostics: [],
},
});
await probe;
});
expect(state?.modelResults[modelId]).toBeUndefined();
expect(state?.testingModelIds).toEqual([]);
await act(async () => {
root.unmount();
});
});
it('keeps failed model probes scoped to the model result instead of a global success banner', async () => {
const modelId = 'openrouter/anthropic/claude-3.5-haiku';
const message =
'This request requires more credits, or fewer max_tokens. You requested up to 8192 tokens, but can only afford 381.';
installRuntimeProviderManagementApi({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId,
ok: false,
availability: 'unavailable',
message,
diagnostics: [],
},
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await actions?.testModel('openrouter', modelId);
});
expect(state?.successMessage).toBeNull();
expect(state?.error).toBeNull();
expect(state?.modelResults[modelId]?.ok).toBe(false);
expect(state?.modelResults[modelId]?.message).toBe(message);
});
it('promotes structured model probe failures to the global diagnostics alert state', async () => {
const modelId = 'openrouter/anthropic/claude-3.5-haiku';
installRuntimeProviderManagementApi({
schemaVersion: 1,
runtimeId: 'opencode',
error: {
code: 'runtime-misconfigured',
message: 'OpenCode provider settings are using the wrong runtime binary.',
recoverable: true,
diagnostics: {
summary: 'OpenCode provider settings are using the wrong runtime binary.',
likelyCause: 'The app resolved the OpenCode CLI itself as the runtime binary.',
binaryPath: '/opt/homebrew/bin/opencode',
command: '/opt/homebrew/bin/opencode runtime providers test-model',
projectPath: null,
exitCode: null,
stderrPreview: null,
stdoutPreview: null,
hints: ['Those environment variables must not point to opencode.'],
},
},
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await actions?.testModel('openrouter', modelId);
});
expect(state?.error).toBe('OpenCode provider settings are using the wrong runtime binary.');
expect(state?.errorDiagnostics?.binaryPath).toBe('/opt/homebrew/bin/opencode');
expect(state?.modelResults[modelId]).toMatchObject({
ok: false,
message: 'OpenCode provider settings are using the wrong runtime binary.',
});
});
it('keeps successful model probes scoped to the model card instead of a global success banner', async () => {
const modelId = 'openrouter/openai/gpt-oss-20b:free';
installRuntimeProviderManagementApi({
schemaVersion: 1,
runtimeId: 'opencode',
result: {
providerId: 'openrouter',
modelId,
ok: true,
availability: 'available',
message: 'Model probe passed',
diagnostics: [],
},
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await actions?.testModel('openrouter', modelId);
});
expect(state?.successMessage).toBeNull();
expect(state?.error).toBeNull();
expect(state?.modelResults[modelId]?.ok).toBe(true);
expect(state?.modelResults[modelId]?.message).toBe('Model probe passed');
});
it('keeps a successful set-default probe visible as verified model state', async () => {
const modelId = 'llama.cpp/qwen-test:0.5b';
const setDefaultModel = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
defaultModel: modelId,
configuredModels: [
{
providerId: 'llama.cpp',
modelId,
displayName: 'qwen-test:0.5b',
sourceLabel: 'llama.cpp',
free: false,
default: true,
availability: 'untested',
accessKind: 'configured_authless',
routeKind: 'configured_local',
proofState: 'needs_probe',
requiresExecutionProof: true,
accessReason: 'Execution proof required',
},
],
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
setDefaultModel,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await actions?.setDefaultModel('llama.cpp', modelId);
await Promise.resolve();
});
expect(setDefaultModel).toHaveBeenCalledWith({
runtimeId: 'opencode',
providerId: 'llama.cpp',
modelId,
probe: true,
scope: 'project',
projectPath: null,
});
expect(state?.view?.configuredModels?.[0]).toMatchObject({
modelId,
default: true,
availability: 'available',
accessKind: 'verified',
proofState: 'verified',
requiresExecutionProof: false,
});
expect(state?.modelResults[modelId]).toMatchObject({
ok: true,
availability: 'available',
message: 'Model probe passed',
});
});
it('keeps the effective project default selected when an all-projects default is shadowed', async () => {
const allProjectsModelId = 'llama.cpp/qwen-test:0.5b';
const projectModelId = 'llama.cpp/project-test:1b';
const setDefaultModel = vi.fn(() =>
Promise.resolve({
schemaVersion: 1,
runtimeId: 'opencode',
view: {
...createRuntimeView(),
defaultModel: projectModelId,
projectDefaultModel: projectModelId,
allProjectsDefaultModel: allProjectsModelId,
defaultModelSource: 'project',
configuredModels: [
{
providerId: 'llama.cpp',
modelId: allProjectsModelId,
displayName: 'qwen-test:0.5b',
sourceLabel: 'llama.cpp',
free: false,
default: false,
availability: 'untested',
accessKind: 'configured_authless',
routeKind: 'configured_local',
proofState: 'needs_probe',
requiresExecutionProof: true,
accessReason: 'Execution proof required',
},
{
providerId: 'llama.cpp',
modelId: projectModelId,
displayName: 'project-test:1b',
sourceLabel: 'llama.cpp',
free: false,
default: true,
availability: 'available',
accessKind: 'verified',
routeKind: 'configured_local',
proofState: 'verified',
requiresExecutionProof: false,
accessReason: null,
},
],
},
})
);
Object.defineProperty(window, 'electronAPI', {
configurable: true,
value: {
runtimeProviderManagement: {
setDefaultModel,
},
} as unknown as ElectronAPI,
});
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(Harness));
await Promise.resolve();
});
await act(async () => {
await actions?.setDefaultModel('llama.cpp', allProjectsModelId, 'all_projects');
await Promise.resolve();
});
expect(state?.selectedModelId).toBe(projectModelId);
expect(state?.view?.defaultModel).toBe(projectModelId);
expect(state?.view?.defaultModelSource).toBe('project');
expect(
state?.view?.configuredModels?.find((model) => model.modelId === allProjectsModelId)
).toMatchObject({
default: false,
availability: 'available',
accessKind: 'verified',
proofState: 'verified',
});
expect(
state?.view?.configuredModels?.find((model) => model.modelId === projectModelId)
).toMatchObject({
default: true,
accessKind: 'verified',
});
});
});