feat: add OpenRouter as alternative LLM provider - #3
Open
gibsondevhouse wants to merge 1 commit into
Open
Conversation
Adds OpenRouter alongside direct Anthropic API access. Users pick one provider during setup; generation, CLI, and exports work identically on both. Anthropic-direct remains the default — zero behavior change for existing users. Provider abstraction (client.ts): - New Provider type + provider-aware getClient() - resolveModel() maps bare Claude model IDs to OpenRouter's namespaced equivalents (anthropic/claude-opus-4.6, etc.) Store (apiStore.ts): - New openrouterApiKey, validation state, and llmProvider field - Persist version bumped with migration; defaults to 'anthropic' - New useLlmCredentials() selector for ergonomic call-site reads Streaming (streaming.ts): - StreamOptions accepts optional provider - When OpenRouter callers pass Anthropic's web_search_20250305 tool, the streaming layer transparently swaps it for OpenRouter's :online model suffix and maps citation annotations back into the same WebSearchResult[] shape — research stage has feature parity UI: - New tabbed LlmProviderCard replaces the single Claude provider card - Each tab has its own setup steps, deep link, and validation state; both keys remembered when switching - Gemini card untouched Call sites: - SyllabusPage, BuildPage, ResearchPage, ExportPage, and answerBalancer thread provider through to streamMessage via useLlmCredentials() - ResearchPage shows generic 'Searching the web…' on OpenRouter (:online doesn't expose the underlying query) CLI (generate-course.ts): - Honors OPENROUTER_API_KEY as alternative to ANTHROPIC_API_KEY - New --provider flag for explicit selection Docs: - README.md and CLAUDE.md updated with OpenRouter setup + architecture
|
@gibsondevhouse is attempting to deploy a commit to the JTprojects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Adds OpenRouter as an alternate LLM provider alongside Anthropic-direct, threading the selected provider through the app’s generation, research, setup, and CLI flows.
Changes:
- Adds provider-aware Claude/OpenRouter client resolution, model mapping, and OpenRouter web-search bridging.
- Adds persisted OpenRouter credentials and provider selection UI.
- Updates app call sites, CLI flags/env handling, and docs for provider selection.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/store/useLlmCredentials.ts |
Adds active LLM credential selector. |
src/store/apiStore.ts |
Persists OpenRouter key and selected provider. |
src/services/quiz/answerBalancer.ts |
Threads provider into quiz rewrite calls. |
src/services/claude/streaming.ts |
Adds provider-aware streaming and OpenRouter :online bridge. |
src/services/claude/client.ts |
Adds OpenRouter client setup and model mapping. |
src/pages/SyllabusPage.tsx |
Uses active LLM credentials for syllabus generation. |
src/pages/SetupPage.tsx |
Uses active provider key/status in setup gating. |
src/pages/ResearchPage.tsx |
Uses active provider and adjusts OpenRouter research status text. |
src/pages/ExportPage.tsx |
Uses active provider for export generation. |
src/pages/BuildPage.tsx |
Threads active provider through all build generation paths. |
src/components/syllabus/CurriculumMapPanel.tsx |
Uses active provider for curriculum map generation. |
src/components/setup/providerConfigs.ts |
Adds OpenRouter setup config and destination labels. |
src/components/setup/ProviderCard.tsx |
Uses provider destination name in storage notice. |
src/components/setup/LlmProviderCard.tsx |
Adds Anthropic/OpenRouter tabbed provider card. |
src/components/setup/ApiKeyPanel.tsx |
Adds OpenRouter validation and tabbed LLM provider card. |
scripts/generate-course.ts |
Adds OpenRouter env/CLI provider support. |
README.md |
Documents OpenRouter setup and CLI usage. |
CLAUDE.md |
Updates architecture notes and CLI examples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+74
to
+77
| { | ||
| type: 'alert', | ||
| text: 'Live web search during the Research stage is Anthropic-only. On OpenRouter, research falls back to model knowledge.', | ||
| }, |
| ANTHROPIC_API_KEY=sk-... npx tsx scripts/generate-course.ts \ | ||
| --topic "Your Topic" --chapters 12 --output ./output/dir | ||
|
|
||
| # Or via OpenRouter (research stage falls back to model knowledge) |
Comment on lines
+118
to
+122
| } else if (OPENROUTER_API_KEY) { | ||
| PROVIDER = 'openrouter'; | ||
| } else { | ||
| console.error('Error: set ANTHROPIC_API_KEY or OPENROUTER_API_KEY in the environment'); | ||
| console.error(' (use --provider openrouter to force OpenRouter when both are set)'); |
Comment on lines
+63
to
+65
| {llmProvider === 'anthropic' ? ( | ||
| <ProviderCard | ||
| config={CLAUDE_CONFIG} |
Comment on lines
+52
to
+57
| const clientCache = new Map<string, Anthropic>(); | ||
|
|
||
| export function getClient(apiKey: string, provider: Provider = 'anthropic'): Anthropic { | ||
| const cacheKey = `${provider}:${apiKey}`; | ||
| const cached = clientCache.get(cacheKey); | ||
| if (cached) return cached; |
| export function useLlmCredentials(): LlmCredentials { | ||
| const { llmProvider, claudeApiKey, openrouterApiKey } = useApiStore(); | ||
| return { | ||
| apiKey: llmProvider === 'openrouter' ? openrouterApiKey : claudeApiKey, |
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.
Add OpenRouter as an alternative LLM provider
Summary
This PR adds OpenRouter as a first-class alternative to direct
Anthropic API access. Users pick one provider during setup; the rest of
the app (generation, CLI, exports) works identically.
Anthropic-direct remains the default and recommended path — zero
behavior change for existing users.
Why
without juggling a second billing relationship.
get a working path.
integration reuses the existing
@anthropic-ai/sdkand streamingpipeline — minimal blast radius.
What changed
Provider abstraction (
src/services/claude/client.ts)Providertype and provider-awaregetClient().resolveModel()maps bare Claude model IDs to OpenRouter'snamespaced equivalents.
Store (
src/store/apiStore.ts)openrouterApiKey, validation state, andllmProviderfield.'anthropic'.Streaming (
src/services/claude/streaming.ts)StreamOptionsaccepts an optionalprovider.web_search_20250305tool,the streaming layer transparently swaps it for OpenRouter's
:onlinemodel suffix and maps the returned citation annotations back into the
same
WebSearchResult[]shape. Research stage has feature parityacross providers.
UI (
src/components/setup/)LlmProviderCardreplaces the single Claude provider card.both keys are remembered when switching tabs.
Call sites
SyllabusPage,BuildPage,ResearchPage,ExportPage, andanswerBalancerthread the selected provider through tostreamMessage.OpenRouter (Exa doesn't expose queries) — replaced with a generic
"Searching the web…" indicator. Final citation list renders identically.
CLI (
scripts/generate-course.ts)OPENROUTER_API_KEYas an alternative toANTHROPIC_API_KEY.--providerflag for explicit selection.Docs
README.mdandCLAUDE.mdupdated.Caveats
direct, Exa via
:onlineon OpenRouter. Callers see the sameWebSearchResult[]shape; the engine swap is internal tostreaming.ts. The underlying search query is not exposed by:onlineso the live "Searching: <query>" status is replaced with a generic
indicator on OpenRouter.
:onlinerequests cost more per call than vanilla OpenRouter requests(Exa charges per search). This is reflected in the OpenRouter card's
cost note.
card surfaces a pricing link rather than quoting a number.
Testing
npm run lintnpm run build(via
:online)OPENROUTER_API_KEY=... npx tsx scripts/generate-course.ts ...including research stage
Screenshots
Add tabbed provider card screenshots before opening PR