Skip to content

feat: add OpenRouter as alternative LLM provider - #3

Open
gibsondevhouse wants to merge 1 commit into
jtangen:mainfrom
gibsondevhouse:feat/openrouter-support
Open

feat: add OpenRouter as alternative LLM provider#3
gibsondevhouse wants to merge 1 commit into
jtangen:mainfrom
gibsondevhouse:feat/openrouter-support

Conversation

@gibsondevhouse

@gibsondevhouse gibsondevhouse commented May 17, 2026

Copy link
Copy Markdown

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

  • Users with existing OpenRouter accounts/credits can use ClassBuild
    without juggling a second billing relationship.
  • Users blocked from Anthropic's direct API (region/billing/org policy)
    get a working path.
  • OpenRouter exposes an Anthropic-compatible Messages API, so the
    integration reuses the existing @anthropic-ai/sdk and streaming
    pipeline — minimal blast radius.

What changed

Provider abstraction (src/services/claude/client.ts)

  • New Provider type and provider-aware getClient().
  • resolveModel() maps bare Claude model IDs to OpenRouter's
    namespaced equivalents.

Store (src/store/apiStore.ts)

  • New openrouterApiKey, validation state, and llmProvider field.
  • Migration added; existing users default to 'anthropic'.

Streaming (src/services/claude/streaming.ts)

  • StreamOptions accepts an 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 the returned citation annotations back into the
    same WebSearchResult[] shape. Research stage has feature parity
    across providers.

UI (src/components/setup/)

  • New tabbed LlmProviderCard replaces the single Claude provider card.
  • Each tab has its own setup steps, deep link, and validation state;
    both keys are remembered when switching tabs.
  • Gemini card untouched.

Call sites

  • SyllabusPage, BuildPage, ResearchPage, ExportPage, and
    answerBalancer thread the selected provider through to
    streamMessage.
  • ResearchPage drops the in-stream "Searching: <query>" status when on
    OpenRouter (Exa doesn't expose queries) — replaced with a generic
    "Searching the web…" indicator. Final citation list renders identically.

CLI (scripts/generate-course.ts)

  • Honors OPENROUTER_API_KEY as an alternative to ANTHROPIC_API_KEY.
  • New --provider flag for explicit selection.

Docs

  • README.md and CLAUDE.md updated.

Caveats

  • Research engine differs by provider — Anthropic's native web search on
    direct, Exa via :online on OpenRouter. Callers see the same
    WebSearchResult[] shape; the engine swap is internal to
    streaming.ts. The underlying search query is not exposed by :online
    so the live "Searching: <query>" status is replaced with a generic
    indicator on OpenRouter.
  • :online requests cost more per call than vanilla OpenRouter requests
    (Exa charges per search). This is reflected in the OpenRouter card's
    cost note.
  • Costs on OpenRouter may differ from Anthropic-direct. The provider
    card surfaces a pricing link rather than quoting a number.

Testing

  • npm run lint
  • npm run build
  • Manual: full chapter generated end-to-end on Anthropic
  • Manual: full chapter generated end-to-end on OpenRouter
  • Manual: research stage returns real citations on OpenRouter
    (via :online)
  • Manual: provider switch mid-session preserves both keys
  • CLI: OPENROUTER_API_KEY=... npx tsx scripts/generate-course.ts ...
    including research stage

Screenshots

Add tabbed provider card screenshots before opening PR

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
Copilot AI review requested due to automatic review settings May 17, 2026 21:17
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown

@gibsondevhouse is attempting to deploy a commit to the JTprojects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.',
},
Comment thread CLAUDE.md
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,
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.

2 participants