refactor: table-driven LLM provider registry - #27
Open
amal66 wants to merge 1 commit into
Open
Conversation
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
This was referenced Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the per-provider
if/elsechains in the LLM dispatch layer and the per-providerswitchin the API-key lookup with a table-driven provider registry. Adding a provider (including OpenAI-compatible endpoints) becomes a singleregisterProvider()/registerApiKeyProvider()call — no edits toindex.ts,models.ts, oruserApiKeys.tsat every call site.Changes
backend/src/lib/llm/registry.ts(new):LLMProviderAdaptercontract (id,matchesModel,stream,complete, tieredmodels) plusregisterProvider,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) withregisterApiKeyProvider,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 ofif (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-varswitchand the hand-maintainedPROVIDERSarray replaced by the table;getUserApiKeyStatus()/getUserApiKeys()build their result dynamically from the registered provider list.backend/src/lib/llm/types.ts:Providerbecomes an open string id;UserApiKeysgains an index signature so third-party providers can carry keys without type edits;CompleteTextParamsextracted.backend/tsconfig.json: exclude__tests__/*.test.tsfrom the production build.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.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:
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.tskeeps 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