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('/')) {
|
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 {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
reason: `Selected model ${trimmedModelId} was not found in the live provider catalog.`,
|
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(
|
private findEquivalentOpenRouterModelIds(
|
||||||
requestedModelId: string,
|
requestedModelId: string,
|
||||||
availableModels: readonly string[]
|
availableModels: readonly string[]
|
||||||
|
|
|
||||||
|
|
@ -836,6 +836,65 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
expect(prepare).toHaveBeenCalledTimes(1);
|
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 () => {
|
it('treats retryable OpenCode compatibility failures as blocking selected-model diagnostics', async () => {
|
||||||
const prepare = vi.fn(async () => ({
|
const prepare = vi.fn(async () => ({
|
||||||
ok: false as const,
|
ok: false as const,
|
||||||
|
|
|
||||||
|
|
@ -352,11 +352,16 @@ 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"]')?.style.paddingLeft
|
(
|
||||||
|
host.querySelector(
|
||||||
|
'[data-testid="runtime-provider-model-search"]'
|
||||||
|
) as HTMLElement | null
|
||||||
|
)?.style.paddingLeft
|
||||||
).toBe('42px');
|
).toBe('42px');
|
||||||
expect(host.querySelector('[data-testid="runtime-provider-model-list"]')?.style.maxHeight).toBe(
|
expect(
|
||||||
'300px'
|
(host.querySelector('[data-testid="runtime-provider-model-list"]') as HTMLElement | null)
|
||||||
);
|
?.style.maxHeight
|
||||||
|
).toBe('300px');
|
||||||
expect(host.textContent).not.toContain('OpenRouterfree');
|
expect(host.textContent).not.toContain('OpenRouterfree');
|
||||||
const firstTestButton = Array.from(host.querySelectorAll('button')).find(
|
const firstTestButton = Array.from(host.querySelectorAll('button')).find(
|
||||||
(button) => button.textContent?.trim() === 'Test'
|
(button) => button.textContent?.trim() === 'Test'
|
||||||
|
|
@ -364,7 +369,7 @@ describe('RuntimeProviderManagementPanelView', () => {
|
||||||
expect(firstTestButton?.className).toContain('border');
|
expect(firstTestButton?.className).toContain('border');
|
||||||
const modelResult = host.querySelector(
|
const modelResult = host.querySelector(
|
||||||
'[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;
|
||||||
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('qwen/qwen3-coder-flash')).toBeLessThan(
|
||||||
(host.textContent ?? '').indexOf('opencode/big-pickle')
|
(host.textContent ?? '').indexOf('opencode/big-pickle')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue