Skip to content

refactor(ai): single provider registry as the backend source of truth - #1075

Merged
lfnovo merged 2 commits into
mainfrom
refactor/provider-registry
Jul 11, 2026
Merged

refactor(ai): single provider registry as the backend source of truth#1075
lfnovo merged 2 commits into
mainfrom
refactor/provider-registry

Conversation

@lfnovo

@lfnovo lfnovo commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Adding an AI provider today means hand-syncing ~6 independent dicts. This PR makes open_notebook/ai/provider_registry.py the backend's single source of truth and derives everything else from it, without changing the shape of any existing surface — every import and call site keeps working.

Before: adding a provider touched 6 places

  1. api/models.pySupportedProvider Literal
  2. api/credentials_service.pyPROVIDER_ENV_CONFIG
  3. api/credentials_service.pyPROVIDER_MODALITIES (+ the discovery url_map)
  4. open_notebook/ai/connection_tester.pyTEST_MODELS
  5. open_notebook/ai/model_discovery.pyOPENAI_COMPAT_PROVIDERS / discovery wiring
  6. frontend/src/lib/providers.tsxALL_PROVIDERS, PROVIDER_MODALITIES, PROVIDER_DOCS

After: registry + 2 manual copies (both test-enforced)

  1. Add a ProviderSpec to PROVIDERS in open_notebook/ai/provider_registry.py — env vars, modalities, test model, discovery URL, display name, docs link, all in one place. PROVIDER_ENV_CONFIG, PROVIDER_MODALITIES, TEST_MODELS, the discovery url_map and OPENAI_COMPAT_PROVIDERS are now thin derivations in their current homes.
  2. Add the name to the SupportedProvider Literal (typing — can't be built at runtime).
  3. Add it to the frontend tables in frontend/src/lib/providers.tsx.

tests/test_credential_provider_validation.py enforces registry keys == Literal == frontend list, checks each spec's internal consistency (env config present, compat-discovery providers have exactly one required env var), and asserts the discovery function table covers the registry.

New endpoint

GET /api/providers exposes the registry: name, display_name, modalities, docs_url, env_configured. The frontend still uses its own tables — migrating it to consume this endpoint (and killing the regex-based .tsx cross-check test) is a deliberate follow-up, not part of this PR.

Docs

open_notebook/AGENTS.md and docs/7-DEVELOPMENT/credentials.md updated from the "keep four locations in sync" rule to the registry + 2 copies rule.

Verification

  • uv run pytest tests/ — 434 passed
  • ruff check . — clean
  • mypy on the touched modules — 36 errors, identical set to the origin/main baseline (all pre-existing, none in the new/changed code)
  • No registry value drifted: all env vars, modalities, test models, URLs and docs links carry the exact values previously hardcoded in each location

Review in cubic

@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.

1 issue found across 11 files

Confidence score: 4/5

  • In api/credentials_service.py, deriving provider metadata dynamically from the registry can hide which providers are OpenAI-compatible and which discovery URLs are expected, so a registry mismatch could slip through and cause runtime credential/discovery failures; before merging, add explicit tests/assertions for provider compatibility and exact discovery URLs (or keep a clear in-file allowlist) to preserve review-time visibility.

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="api/credentials_service.py">

<violation number="1" location="api/credentials_service.py:388">
P2: Replacing the explicit `url_map` with a dynamic derivation from the registry removes in-file visibility of which providers are OpenAI-compatible and what their discovery URLs are. The existing tests verify structural constraints (e.g., exactly one required env var) and that every provider has a discovery function, but they do not assert the exact set of providers with `openai_compat_discovery_url` or pin their URL values. Without that guard, a future registry edit could silently omit, alter, or misassign a discovery URL—causing `discover_models` to return nothing or misparse a non-OpenAI response as `data[]`. Consider adding a test that asserts the exact expected mapping.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

"openrouter": "https://openrouter.ai/api/v1/models",
"dashscope": "https://dashscope.aliyuncs.com/compatible-mode/v1/models",
"minimax": "https://api.minimax.io/v1/models",
name: spec.openai_compat_discovery_url

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.

P2: Replacing the explicit url_map with a dynamic derivation from the registry removes in-file visibility of which providers are OpenAI-compatible and what their discovery URLs are. The existing tests verify structural constraints (e.g., exactly one required env var) and that every provider has a discovery function, but they do not assert the exact set of providers with openai_compat_discovery_url or pin their URL values. Without that guard, a future registry edit could silently omit, alter, or misassign a discovery URL—causing discover_models to return nothing or misparse a non-OpenAI response as data[]. Consider adding a test that asserts the exact expected mapping.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api/credentials_service.py, line 388:

<comment>Replacing the explicit `url_map` with a dynamic derivation from the registry removes in-file visibility of which providers are OpenAI-compatible and what their discovery URLs are. The existing tests verify structural constraints (e.g., exactly one required env var) and that every provider has a discovery function, but they do not assert the exact set of providers with `openai_compat_discovery_url` or pin their URL values. Without that guard, a future registry edit could silently omit, alter, or misassign a discovery URL—causing `discover_models` to return nothing or misparse a non-OpenAI response as `data[]`. Consider adding a test that asserts the exact expected mapping.</comment>

<file context>
@@ -426,16 +383,11 @@ def models_endpoint(url: str) -> str:
-        "openrouter": "https://openrouter.ai/api/v1/models",
-        "dashscope": "https://dashscope.aliyuncs.com/compatible-mode/v1/models",
-        "minimax": "https://api.minimax.io/v1/models",
+        name: spec.openai_compat_discovery_url
+        for name, spec in PROVIDERS.items()
+        if spec.openai_compat_discovery_url
</file context>

Comment thread open_notebook/ai/provider_registry.py Outdated
Comment thread open_notebook/ai/connection_tester.py Outdated
lfnovo added 2 commits July 11, 2026 19:16
Provider metadata (env vars, modalities, connection-test models,
OpenAI-compatible discovery URLs, display names, docs links) is now
defined once in open_notebook/ai/provider_registry.py. The existing
surfaces are derived from it, keeping every import and call-site shape
unchanged:

- api/credentials_service.py: PROVIDER_ENV_CONFIG, PROVIDER_MODALITIES
  and the discovery url_map are built from the registry
- open_notebook/ai/connection_tester.py: TEST_MODELS derived
- open_notebook/ai/model_discovery.py: OPENAI_COMPAT_PROVIDERS built
  from registry entries with a discovery URL (quirk hooks stay local)

The SupportedProvider Literal (typing, can't be built at runtime) and
the frontend provider tables remain manual copies; the cross-check
tests now assert registry keys == Literal == frontend list, plus
registry internal consistency and discovery-table coverage.

New GET /api/providers endpoint exposes the registry (name, display
name, modalities, docs_url, env-configured status) so clients can stop
hardcoding provider lists (frontend adoption is a follow-up).

Docs updated: open_notebook/AGENTS.md and docs/7-DEVELOPMENT/credentials.md
now describe the registry instead of the four-place sync rule.
- Build PROVIDERS via _build_registry(), which raises on a duplicate
  provider name at import time instead of silently dropping the earlier
  spec (dict-comprehension behavior); regression test added
- Pin the exact OpenAI-compatible provider -> discovery URL mapping in
  a test so a registry edit can't silently drop or misassign a URL
- Give TEST_MODELS a real type annotation
  (Dict[str, Tuple[Optional[str], str]]) instead of bare dict
@lfnovo
lfnovo force-pushed the refactor/provider-registry branch from fd03371 to 1a66680 Compare July 11, 2026 22:16
@lfnovo

lfnovo commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the review findings in 1a66680:

  • Duplicate-name safety: PROVIDERS is now built by _build_registry(), which raises ValueError at import time on a duplicate provider name instead of silently dropping the earlier spec; regression test added.
  • Pinned discovery URLs: new test asserts the exact expected provider → OpenAI-compatible discovery URL mapping (both on the registry specs and on the derived OPENAI_COMPAT_PROVIDERS table), so a registry edit can't silently drop or misassign a URL.
  • Typing: TEST_MODELS is now annotated Dict[str, Tuple[Optional[str], str]] instead of bare dict.

Rebased on latest main; 436 tests pass, ruff clean, mypy unchanged vs the main baseline.

@lfnovo

lfnovo commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

The re-review finding was already addressed in 1a66680: test_openai_compat_discovery_urls_are_exactly_as_expected pins the exact 8-entry provider → discovery-URL mapping against both the registry specs and the derived OPENAI_COMPAT_PROVIDERS table — a registry edit that drops/misassigns a URL now fails CI. (The incremental review appears not to have picked up the new test.)

@lfnovo
lfnovo merged commit ad12e99 into main Jul 11, 2026
10 checks passed
mvanhorn added a commit to mvanhorn/open-notebook that referenced this pull request Jul 17, 2026
Register anthropic_compatible in the provider registry so its env config,
modalities, test model, and /api/providers entry are derived from PROVIDERS
(lfnovo#1075's single source of truth); the only manual copy is the SupportedProvider
Literal. Maps to esperanto's anthropic provider with a custom base_url, and
re-injects that base_url via ChatAnthropic since esperanto's to_langchain drops
it. Connection-test and model discovery mirror the openai_compatible siblings,
including DNS-rebinding pinning (prepare_pinned_http_target). A single shared
validator enforces the base_url + api_key requirement on both the create and
update paths.
mvanhorn added a commit to mvanhorn/open-notebook that referenced this pull request Jul 17, 2026
Register anthropic_compatible in the provider registry so its env config,
modalities, test model, and /api/providers entry are derived from PROVIDERS
(lfnovo#1075's single source of truth); the only manual copy is the SupportedProvider
Literal. Maps to esperanto's anthropic provider with a custom base_url, and
re-injects that base_url via ChatAnthropic since esperanto's to_langchain drops
it. Connection-test and model discovery mirror the openai_compatible siblings,
including DNS-rebinding pinning (prepare_pinned_http_target). A single shared
validator enforces the base_url + api_key requirement on both the create and
update paths.
lfnovo pushed a commit to mvanhorn/open-notebook that referenced this pull request Jul 19, 2026
Register anthropic_compatible in the provider registry so its env config,
modalities, test model, and /api/providers entry are derived from PROVIDERS
(lfnovo#1075's single source of truth); the only manual copy is the SupportedProvider
Literal. Maps to esperanto's anthropic provider with a custom base_url, and
re-injects that base_url via ChatAnthropic since esperanto's to_langchain drops
it. Connection-test and model discovery mirror the openai_compatible siblings,
including DNS-rebinding pinning (prepare_pinned_http_target). A single shared
validator enforces the base_url + api_key requirement on both the create and
update paths.
lfnovo added a commit that referenced this pull request Jul 19, 2026
Register anthropic_compatible in the provider registry so its env config,
modalities, test model, and /api/providers entry are derived from PROVIDERS
(#1075's single source of truth); the only manual copy is the SupportedProvider
Literal. Maps to esperanto's anthropic provider with a custom base_url, and
re-injects that base_url via ChatAnthropic since esperanto's to_langchain drops
it. Connection-test and model discovery mirror the openai_compatible siblings,
including DNS-rebinding pinning (prepare_pinned_http_target). A single shared
validator enforces the base_url + api_key requirement on both the create and
update paths.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Luis Novo <lfnovo@gmail.com>
@lfnovo
lfnovo deleted the refactor/provider-registry branch September 2, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant