Repository navigation
Add Google Gemini as a course-content provider (and fix provider-key gating) - #3
Merged
Merged
Conversation
…ovider's key Gemini joins Claude and Ollama Cloud as a course-content provider. Backend (src/services/llm/gemini.ts) - streamGenerateContent over SSE, called straight from the browser: Google answers CORS preflights for the x-goog-api-key header from any origin (verified with a keyless preflight), so no proxy is needed. The key is sent in a header, never the URL. - Maps Anthropic-style messages to Gemini contents/systemInstruction; requests thought summaries only with a thinking budget and retries once without them on a 400 that complains about thinking; leaves output-token headroom for thinking (capped at 65,536). - Streams text and thought summaries; reassembles SSE events split across network chunks, CRLF delimiters and a final event with no blank line; explains blocked prompts/responses; returns partial text on MAX_TOKENS like the Anthropic path; errors keep the HTTP code (so 429 backoff works) and carry .status. - Router: resolveProvider swaps in the Gemini key/model and ignores the claude-* model ids every call site passes (same rule now shared with Ollama). Store / UI / CLI - apiStore: geminiApiKey, geminiModel (default gemini-3.8-flash, free-text editable), validation state; new selectActiveLlm() = "the key that matters right now". - Setup keys dialog: third provider pill, Gemini key + model fields; Verify GETs the chosen model (proves key AND model id; 404 = unknown model). Free-tier privacy note. - canvas-course CLI: --provider gemini, --gemini-model, GEMINI_API_KEY. Fixes a latent bug that also affected Ollama - Syllabus auto-start, Export learning outcomes, outline upload and the Research gate required a *Claude* key regardless of provider, silently blocking anyone using Ollama or Gemini without one. They now use selectActiveLlm(). - friendlyError understands Google's wording: "API key not valid" (a 400), unknown model (404 → "check the model name"), RECITATION/BLOCKLIST blocks. Tests (+75): backend contract/streaming/errors, router key+model resolution, store and persistence upgrade, Setup dialog flows, per-provider gates (verified to fail against the old gating), error wording. tools/smoke-gemini.ts (npm run smoke:gemini, key from GEMINI_API_KEY) exercises the live API; its bad-key check ran against Google for real. Docs: README, CLAUDE.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…ejection
The test fed a fake .docx to the real parser, which rejects asynchronously; on CI's
timing the rejection escaped the component's try/catch chain and Vitest failed the run
("1 unhandled error") although every test passed. Stub the DOCX parser and LLM
extraction instead, and assert what the test is really about: with only a Gemini /
Ollama / Anthropic key set, extraction runs and receives THAT provider's key.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
Adds Google Gemini as a third course-content provider next to Claude and Ollama Cloud, and fixes a latent bug that blocked non-Claude users.
What it does
src/services/llm/gemini.ts—streamGenerateContentover SSE, called directly from the browser (Google answers CORS preflights for thex-goog-api-keyheader from any origin — verified with a keyless preflight — so no proxy, unlike Ollama). Key goes in a header, never the URL.gemini-3.8-flash; ids change often). Verify GETs the chosen model, proving the key and the model id. Free-tier privacy note.--provider gemini,--gemini-model,GEMINI_API_KEY.MAX_TOKENS(returns partial text like the Anthropic path), 429 backoff.Latent bug fixed (also affected Ollama)
Syllabus auto-start, Export "learning outcomes", outline upload and the Research gate all required a Claude key regardless of provider — an Ollama/Gemini user without one was silently blocked (the syllabus never started). They now ask the active provider via a new
selectActiveLlm().friendlyErroralso learns Google's wording ("API key not valid" is a 400; unknown model 404; RECITATION/BLOCKLIST).Verification
tsc -b, eslint (0 errors, original 7 warnings),vitest --coverage394 passed (+75),vite build— all green locally.npm run smoke:gemini, needs no key): Google replies400 INVALID_ARGUMENT: API key not valid…, our parser reads it,friendlyError→ "API key rejected".NOT verified — needs a real key
Unit tests mock
fetch, so the success path against the live API hasn't been run (plain text, streaming, thinking, structured-JSON quizzes, model id). Run it yourself — the key is read from your environment and never printed:Model ids came from Google's docs page; if
gemini-3.8-flashisn't available to your key, set another in the dialog (orGEMINI_MODEL=...for the script). Quality on the app's large structured-JSON prompts (quizzes, slides) is the thing to watch — those prompts were tuned on Claude.Notes
🤖 Generated with Claude Code