nexu-io/open-design · error · Error

Unsupported protocol

Error message

Unsupported protocol: ${protocol}

What it means

Thrown by providerModelsUrl() when the protocol is not one of aihubmix/openai/senseaudio/anthropic/google. The ConnectionTestProtocol union also contains ollama, azure and bedrock; azure and bedrock are short-circuited earlier in listProviderModels (returning unsupported_protocol / a synthetic success), so in practice ollama (and any future protocol) reaches this throw because model discovery has no URL builder for it.

Solutions

  1. If you need Ollama model discovery, add an ollama branch to providerModelsUrl (and extractModels) — typically GET {baseUrl}/api/tags.
  2. At the call site, skip listProviderModels for protocols without discovery (ollama/azure/bedrock) and let the user type the model id.
  3. Guard the input: validate protocol against the supported discovery set before calling.

Example fix

// before
if (protocol === 'google') return googleProviderModelsUrl(baseUrl, apiKey);
throw new Error(`Unsupported protocol: ${protocol}`);

// after
if (protocol === 'google') return googleProviderModelsUrl(baseUrl, apiKey);
if (protocol === 'ollama') return new URL('/api/tags', baseUrl).toString();
throw new Error(`Unsupported protocol: ${protocol}`);
Defensive patterns

Strategy: validation

Validate before calling

import type { ConnectionTestProtocol } from '@open-design/contracts';

const DISCOVERY_SUPPORTED: ReadonlySet<ConnectionTestProtocol> = new Set([
  'openai', 'senseaudio', 'anthropic', 'google', 'aihubmix',
]);

function supportsModelDiscovery(protocol: ConnectionTestProtocol): boolean {
  return DISCOVERY_SUPPORTED.has(protocol);
}

// usage
if (!supportsModelDiscovery(input.protocol)) {
  return { models: [], reason: 'no_discovery' };
}

Type guard

function isDiscoverableProtocol(p: string): p is 'openai' | 'senseaudio' | 'anthropic' | 'google' | 'aihubmix' {
  return p === 'openai' || p === 'senseaudio' || p === 'anthropic' || p === 'google' || p === 'aihubmix';
}

Try / catch

try {
  return await listProviderModels(input);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Unsupported protocol:')) {
    return { models: [], reason: 'no_discovery' };
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling listProviderModels({ protocol: 'ollama', ... }) (ollama is in the union but has no models-URL branch), or passing a protocol value the function does not recognise. azure/bedrock do NOT reach this throw because listProviderModels returns before calling providerModelsUrl.

Common situations: User adds an Ollama provider and the model picker tries to enumerate models; a new protocol is added to ConnectionTestProtocol without a corresponding branch here; a caller forwards an unvalidated protocol string.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/d7c6b4c8b92f9021. Report an issue: GitHub.

Appendix: source

Thrown at apps/daemon/src/integrations/provider-models.ts:255

function providerModelsUrl(protocol: ConnectionTestProtocol, baseUrl: string, apiKey: string): string {
  if (protocol === 'aihubmix') {
    // AIHubMix exposes its chat catalogue on a dedicated endpoint
    // (GET /api/v1/models?type=llm), not the OpenAI /v1/models route.
    return aihubmixCatalogUrl(baseUrl, 'llm');
  }
  if (protocol === 'openai' || protocol === 'senseaudio') {
    return appendVersionedApiPath(baseUrl, '/models');
  }
  if (protocol === 'anthropic') {
    const url = new URL(appendVersionedApiPath(baseUrl, '/models'));
    url.searchParams.set('limit', '1000');
    return url.toString();
  }
  if (protocol === 'google') {
    return googleProviderModelsUrl(baseUrl, apiKey);
  }
  throw new Error(`Unsupported protocol: ${protocol}`);
}

function providerModelsHeaders(
  protocol: ConnectionTestProtocol,
  apiKey: string,
): Record<string, string> {
  if (protocol === 'openai' || protocol === 'senseaudio') {
    return { authorization: `Bearer ${apiKey}` };
  }
  if (protocol === 'aihubmix') {
    // The catalogue is public — only attach Bearer auth (+ APP-Code) when the
    // user actually supplied a key. An empty `Bearer ` would be rejected by
    // some gateways, so send no headers when the key is blank.
    return apiKey.trim() ? aihubmixHeaders(apiKey) : {};
  }
  if (protocol === 'anthropic') {
    return {
      'x-api-key': apiKey,

View on GitHub (pinned to 5be4028344)