Skip to content

fix(llm): normalize and validate apiBase with descriptive errors - #12593

Closed
rodboev wants to merge 1 commit into
continuedev:mainfrom
rodboev:pr/validate-api-base
Closed

rodboev wants to merge 1 commit into
continuedev:mainfrom
rodboev:pr/validate-api-base

Conversation

@rodboev

@rodboev rodboev commented Jun 10, 2026 •

Copy link
Copy Markdown

Fixes #11872

Summary

A bare apiBase like localhost:11434 (no protocol) or an unresolved template like ${{ secrets.MY_KEY }} surfaces as an opaque TypeError: Invalid URL deep inside provider code at request time, with no hint of which model or value is at fault. This PR normalizes protocol-less values and validates apiBase at construction with an error naming the provider and the bad value. It covers two of the three root causes RomneyDa identified in the issue; the third (the getMaxStopWords() non-null assertion) is already addressed by #12518 and is deliberately not duplicated here.

Root cause

BaseLLM's constructor (core/llm/index.ts) stores apiBase with only trailing-slash normalization; nothing validates the value, so new URL(...) fails later wherever a provider first builds a request. Separately, the JSON config path constructs models inside uncaught Promise.all calls in core/config/load.ts (chat models, tab-autocomplete, embeddings, reranker), so any single throwing constructor used to reject the entire config load, unlike the YAML path which catches per model (core/config/yaml/loadYaml.ts).

Changes

  • core/llm/index.ts: after trailing-slash normalization, prepend http:// when apiBase has no protocol, then validate with new URL(); on failure throw Invalid apiBase for provider "<name>": "<value>"... including a hint about unresolved secret references
  • core/config/load.ts: all four JSON-path construction sites (chat models, tab-autocomplete models, embeddings provider, reranker) now catch constructor errors per model, report them as non-fatal ConfigValidationErrors, and continue loading the remaining models — matching the YAML path's behavior
  • core/llm/index.test.ts: 4 new tests under "BaseLLM apiBase normalization"

What this doesn't change

  • OpenAI.getMaxStopWords() — the non-null assertion there is fixed by fix(llm): guard getMaxStopWords against undefined apiBase #12518; this PR intentionally does not touch it
  • The YAML config path, which already catches per-model construction errors
  • Provider-specific URL construction downstream of the validated apiBase
  • Empty-string apiBase GUI placeholders remain falsy and skip validation

Checklist

  • I've read the contributing guide
  • The relevant docs, if any, have been updated or created
  • The relevant tests, if any, have been updated or created

Screen recording or screenshot

Before: Uncaught TypeError: Invalid URL at request time, no indication of source.
After (config load): Invalid apiBase for provider "ollama": "localhost:11434"... reported as a config error while other models keep loading. Bare hosts like localhost:11434 now work via http:// prepending.

Tests

  • cd core && npm test -- llm/index.test.ts — 190/190 passing (186 existing + 4 new). Covers: unresolved template strings throw with provider name and value, bare hosts normalize to http://... with trailing slash, undefined apiBase skips validation, well-formed URLs pass through unchanged.
  • cd core && npm run tsc:check — clean.

Summary by cubic

Normalize and validate LLM apiBase at construction to give clear errors and support bare hosts. JSON config now treats model-construction errors as non-fatal so other models still load.

  • Bug Fixes
    • Prepend http:// when apiBase has no protocol and keep trailing slash normalization.
    • Validate apiBase early and throw a clear error with provider name and value; includes a hint for unresolved secret references.
    • JSON config path now catches constructor errors per model (chat, tab-autocomplete, embeddings, reranker), reports them as non-fatal, and continues loading.
    • Added tests for apiBase normalization and validation.

Written for commit fb45d38. Summary will update on new commits.

Review in cubic

@rodboev
rodboev requested a review from a team as a code owner June 10, 2026 20:02
@rodboev
rodboev requested review from sestinj and removed request for a team June 10, 2026 20:02
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 10, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@rodboev rodboev closed this Oct 29, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Invalid URL" error across multiple providers

1 participant