fix(opencode): explain missing openrouter catalog provider
This commit is contained in:
parent
661f308ab4
commit
351ae4f4ed
3 changed files with 105 additions and 5 deletions
|
|
@ -10133,6 +10133,24 @@ export class TeamProvisioningService {
|
|||
}
|
||||
|
||||
if (trimmedModelId.includes('/')) {
|
||||
const requestedProviderId = this.extractOpenCodeCatalogProviderId(trimmedModelId);
|
||||
const availableProviderIds = this.getOpenCodeCatalogProviderIds(availableModels);
|
||||
if (
|
||||
requestedProviderId === 'openrouter' &&
|
||||
!availableProviderIds.includes(requestedProviderId)
|
||||
) {
|
||||
const availableProviderList =
|
||||
availableProviderIds.length > 0 ? availableProviderIds.join(', ') : 'none';
|
||||
return {
|
||||
ok: false,
|
||||
reason:
|
||||
`OpenCode provider "openrouter" for selected model "${trimmedModelId}" ` +
|
||||
'is not available in the current runtime catalog for this project/profile. ' +
|
||||
`Live catalog providers: ${availableProviderList}. ` +
|
||||
'Connect OpenRouter in OpenCode provider management or choose one of the listed OpenCode models.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
reason: `Selected model ${trimmedModelId} was not found in the live provider catalog.`,
|
||||
|
|
@ -10163,6 +10181,24 @@ export class TeamProvisioningService {
|
|||
};
|
||||
}
|
||||
|
||||
private extractOpenCodeCatalogProviderId(modelId: string): string | null {
|
||||
const separatorIndex = modelId.indexOf('/');
|
||||
if (separatorIndex <= 0) {
|
||||
return null;
|
||||
}
|
||||
return modelId.slice(0, separatorIndex).trim().toLowerCase() || null;
|
||||
}
|
||||
|
||||
private getOpenCodeCatalogProviderIds(availableModels: readonly string[]): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
availableModels
|
||||
.map((modelId) => this.extractOpenCodeCatalogProviderId(modelId.trim()))
|
||||
.filter((providerId): providerId is string => Boolean(providerId))
|
||||
)
|
||||
).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
private findEquivalentOpenRouterModelIds(
|
||||
requestedModelId: string,
|
||||
availableModels: readonly string[]
|
||||
|
|
|
|||
|
|
@ -836,6 +836,65 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
|||
expect(prepare).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('explains OpenRouter selected-model failures when the current OpenCode catalog has no OpenRouter provider', async () => {
|
||||
const prepare = vi.fn(async (input: { model?: string; runtimeOnly?: boolean }) => ({
|
||||
ok: true as const,
|
||||
providerId: 'opencode' as const,
|
||||
modelId: input.model ?? null,
|
||||
diagnostics: [],
|
||||
warnings: [],
|
||||
}));
|
||||
const registry = new TeamRuntimeAdapterRegistry([
|
||||
{
|
||||
providerId: 'opencode',
|
||||
prepare,
|
||||
getLastOpenCodeTeamLaunchReadiness: vi.fn(() => ({
|
||||
state: 'ready',
|
||||
launchAllowed: true,
|
||||
modelId: 'opencode/minimax-m2.5-free',
|
||||
availableModels: ['opencode/minimax-m2.5-free', 'openai/gpt-5.4'],
|
||||
opencodeVersion: '1.0.0',
|
||||
installMethod: 'unknown',
|
||||
binaryPath: 'opencode',
|
||||
hostHealthy: true,
|
||||
appMcpConnected: true,
|
||||
requiredToolsPresent: true,
|
||||
permissionBridgeReady: true,
|
||||
runtimeStoresReady: true,
|
||||
supportLevel: 'production_supported',
|
||||
missing: [],
|
||||
diagnostics: [],
|
||||
evidence: {
|
||||
capabilitiesReady: true,
|
||||
mcpToolProofRoute: 'mcp:tools/list',
|
||||
observedMcpTools: [],
|
||||
runtimeStoreReadinessReason: 'runtime_store_manifest_valid',
|
||||
},
|
||||
})),
|
||||
launch: vi.fn(),
|
||||
reconcile: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
} as any,
|
||||
]);
|
||||
const svc = new TeamProvisioningService();
|
||||
svc.setRuntimeAdapterRegistry(registry);
|
||||
|
||||
const result = await svc.prepareForProvisioning(tempRoot, {
|
||||
providerId: 'opencode',
|
||||
forceFresh: true,
|
||||
modelIds: ['openrouter/qwen/qwen3-coder'],
|
||||
modelVerificationMode: 'compatibility',
|
||||
});
|
||||
|
||||
expect(result.ready).toBe(false);
|
||||
expect(result.message).toContain(
|
||||
'OpenCode provider "openrouter" for selected model "openrouter/qwen/qwen3-coder" is not available'
|
||||
);
|
||||
expect(result.message).toContain('Live catalog providers: openai, opencode.');
|
||||
expect(result.message).toContain('Connect OpenRouter in OpenCode provider management');
|
||||
expect(prepare).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('treats retryable OpenCode compatibility failures as blocking selected-model diagnostics', async () => {
|
||||
const prepare = vi.fn(async () => ({
|
||||
ok: false as const,
|
||||
|
|
|
|||
|
|
@ -352,11 +352,16 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
) as HTMLElement | undefined;
|
||||
expect(connectedBadge?.style.color).toBeTruthy();
|
||||
expect(
|
||||
host.querySelector('[data-testid="runtime-provider-model-search"]')?.style.paddingLeft
|
||||
(
|
||||
host.querySelector(
|
||||
'[data-testid="runtime-provider-model-search"]'
|
||||
) as HTMLElement | null
|
||||
)?.style.paddingLeft
|
||||
).toBe('42px');
|
||||
expect(host.querySelector('[data-testid="runtime-provider-model-list"]')?.style.maxHeight).toBe(
|
||||
'300px'
|
||||
);
|
||||
expect(
|
||||
(host.querySelector('[data-testid="runtime-provider-model-list"]') as HTMLElement | null)
|
||||
?.style.maxHeight
|
||||
).toBe('300px');
|
||||
expect(host.textContent).not.toContain('OpenRouterfree');
|
||||
const firstTestButton = Array.from(host.querySelectorAll('button')).find(
|
||||
(button) => button.textContent?.trim() === 'Test'
|
||||
|
|
@ -364,7 +369,7 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
expect(firstTestButton?.className).toContain('border');
|
||||
const modelResult = host.querySelector(
|
||||
'[data-testid="runtime-provider-model-result-openrouter/openai/gpt-oss-20b:free"]'
|
||||
);
|
||||
) as HTMLElement | null;
|
||||
expect(modelResult?.style.color).toBe('#86efac');
|
||||
expect((host.textContent ?? '').indexOf('qwen/qwen3-coder-flash')).toBeLessThan(
|
||||
(host.textContent ?? '').indexOf('opencode/big-pickle')
|
||||
|
|
|
|||
Loading…
Reference in a new issue