Skip to content

refactor: table-driven LLM provider registry - #27

Open
amal66 wants to merge 1 commit into
upstream-mainfrom
upstream-pr/provider-registry
Open

refactor: table-driven LLM provider registry#27
amal66 wants to merge 1 commit into
upstream-mainfrom
upstream-pr/provider-registry

Conversation

@amal66

@amal66 amal66 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the per-provider if/else chains in the LLM dispatch layer and the per-provider switch in the API-key lookup with a table-driven provider registry. Adding a provider (including OpenAI-compatible endpoints) becomes a single registerProvider() / registerApiKeyProvider() call — no edits to index.ts, models.ts, or userApiKeys.ts at every call site.

Changes

  • backend/src/lib/llm/registry.ts (new): LLMProviderAdapter contract (id, matchesModel, stream, complete, tiered models) plus registerProvider, getRegisteredProvider, findProviderForModel, registeredProviderIds, allRegisteredModels, and a test-only reset hook.
  • backend/src/core/apiKeyProviders.ts (new): table of provider → env-var names (claude, gemini, openai, openrouter, courtlistener) with registerApiKeyProvider, getRegisteredProviders, envApiKey, hasEnvApiKey, isApiKeyProvider, normalizeApiKeyProvider.
  • backend/src/lib/llm/index.ts: built-in providers (claude/gemini/openai) are registered on module load; streamChatWithTools() / completeText() dispatch through the registry instead of if (provider === "claude") ... chains.
  • backend/src/lib/llm/models.ts: providerForModel() consults the registry first (prefix heuristics kept as fallback for test contexts); resolveModel() also accepts any model declared by a registered provider.
  • backend/src/lib/userApiKeys.ts: env-var switch and the hand-maintained PROVIDERS array replaced by the table; getUserApiKeyStatus() / getUserApiKeys() build their result dynamically from the registered provider list.
  • backend/src/lib/llm/types.ts: Provider becomes an open string id; UserApiKeys gains an index signature so third-party providers can carry keys without type edits; CompleteTextParams extracted.
  • backend/tsconfig.json: exclude __tests__/*.test.ts from the production build.
  • Tests (new): backend/src/lib/llm/__tests__/registry.test.ts (registration, first-match-wins routing, insertion order, model-set union) and __tests__/models.test.ts (provider inference + model resolution).

Behavior is preserved: same three built-in providers, same env-var precedence (ANTHROPIC_API_KEY || CLAUDE_API_KEY, etc.), same model routing, same API-key status/lookup results.

Why

Today every new provider requires touching each dispatch site (streamChatWithTools, completeText), the model tables, and the API-key switch. With the registry, local LLMs and other OpenAI-compatible endpoints can be added from a single setup file — the extensibility path Will endorsed for OpenAI-compatible endpoints — without touching call sites.

Testing

  • npm run build --prefix backend — passes (tsc, no errors).
  • npx vitest run src/lib/llm/__tests__ — 2 files, 20 tests, all passing.
  • Upstream has no test runner yet, so vitest was installed temporarily and uncommitted (npm install --no-save vitest); the committed tests are ready to run atop a test-harness PR once one lands.

Provenance

All changes are mechanical ports of code in amal66/mike@origin/main (commit b3166dd); exceptions:

  • Fork-only features intertwined with the ported code were omitted, not rewritten: the air-gapped-mode branches in resolveModel/registerBuiltinProviders, the Ollama/demo provider setup, and the retry/circuit-breaker wrapper around dispatch (upstream calls adapters directly, as before). Doc comments referencing those omitted features were trimmed accordingly.
  • userApiKeys.ts keeps upstream's existing encryption and logging; only the fork's table-driven lookup delta is ported.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC

Replace the per-provider if/else chains in lib/llm/index.ts and the
env-var switch in lib/userApiKeys.ts with a provider registry
(lib/llm/registry.ts) and an API-key provider table
(core/apiKeyProviders.ts). Adding a provider is now a
registerProvider()/registerApiKeyProvider() call — no edits to
index.ts, models.ts, or userApiKeys.ts required.

Ported from the amal66/mike monorepo fork (origin/main, b3166dd);
mechanical translation into the backend/ layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
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