{"record":{"id":"d7c6b4c8b92f9021","repo":"nexu-io/open-design","slug":"unsupported-protocol-protocol","errorCode":null,"errorMessage":"Unsupported protocol: ${protocol}","messagePattern":"Unsupported protocol: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/integrations/provider-models.ts","lineNumber":255,"sourceCode":"\nfunction providerModelsUrl(protocol: ConnectionTestProtocol, baseUrl: string, apiKey: string): string {\n  if (protocol === 'aihubmix') {\n    // AIHubMix exposes its chat catalogue on a dedicated endpoint\n    // (GET /api/v1/models?type=llm), not the OpenAI /v1/models route.\n    return aihubmixCatalogUrl(baseUrl, 'llm');\n  }\n  if (protocol === 'openai' || protocol === 'senseaudio') {\n    return appendVersionedApiPath(baseUrl, '/models');\n  }\n  if (protocol === 'anthropic') {\n    const url = new URL(appendVersionedApiPath(baseUrl, '/models'));\n    url.searchParams.set('limit', '1000');\n    return url.toString();\n  }\n  if (protocol === 'google') {\n    return googleProviderModelsUrl(baseUrl, apiKey);\n  }\n  throw new Error(`Unsupported protocol: ${protocol}`);\n}\n\nfunction providerModelsHeaders(\n  protocol: ConnectionTestProtocol,\n  apiKey: string,\n): Record<string, string> {\n  if (protocol === 'openai' || protocol === 'senseaudio') {\n    return { authorization: `Bearer ${apiKey}` };\n  }\n  if (protocol === 'aihubmix') {\n    // The catalogue is public — only attach Bearer auth (+ APP-Code) when the\n    // user actually supplied a key. An empty `Bearer ` would be rejected by\n    // some gateways, so send no headers when the key is blank.\n    return apiKey.trim() ? aihubmixHeaders(apiKey) : {};\n  }\n  if (protocol === 'anthropic') {\n    return {\n      'x-api-key': apiKey,","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/integrations/provider-models.ts#L237-L273","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If you need Ollama model discovery, add an ollama branch to providerModelsUrl (and extractModels) — typically GET {baseUrl}/api/tags.","At the call site, skip listProviderModels for protocols without discovery (ollama/azure/bedrock) and let the user type the model id.","Guard the input: validate protocol against the supported discovery set before calling."],"exampleFix":"// before\nif (protocol === 'google') return googleProviderModelsUrl(baseUrl, apiKey);\nthrow new Error(`Unsupported protocol: ${protocol}`);\n\n// after\nif (protocol === 'google') return googleProviderModelsUrl(baseUrl, apiKey);\nif (protocol === 'ollama') return new URL('/api/tags', baseUrl).toString();\nthrow new Error(`Unsupported protocol: ${protocol}`);","handlingStrategy":"validation","validationCode":"import type { ConnectionTestProtocol } from '@open-design/contracts';\n\nconst DISCOVERY_SUPPORTED: ReadonlySet<ConnectionTestProtocol> = new Set([\n  'openai', 'senseaudio', 'anthropic', 'google', 'aihubmix',\n]);\n\nfunction supportsModelDiscovery(protocol: ConnectionTestProtocol): boolean {\n  return DISCOVERY_SUPPORTED.has(protocol);\n}\n\n// usage\nif (!supportsModelDiscovery(input.protocol)) {\n  return { models: [], reason: 'no_discovery' };\n}","typeGuard":"function isDiscoverableProtocol(p: string): p is 'openai' | 'senseaudio' | 'anthropic' | 'google' | 'aihubmix' {\n  return p === 'openai' || p === 'senseaudio' || p === 'anthropic' || p === 'google' || p === 'aihubmix';\n}","tryCatchPattern":"try {\n  return await listProviderModels(input);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Unsupported protocol:')) {\n    return { models: [], reason: 'no_discovery' };\n  }\n  throw err;\n}","preventionTips":["Skip listProviderModels for ollama/azure/bedrock — let the user type the model id.","When adding a protocol to ConnectionTestProtocol, add the matching providerModelsUrl branch the same change.","Validate protocol at the trust boundary (route input) against the discovery-capable set."],"tags":["validation","integration","protocol","provider-models"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}