feat: add OpenRouter as alternative LLM provider - #7
Conversation
OpenRouter ist OpenAI-kompatibel: konfigurierbare base_url + Modell werden in den bestehenden openai-Client gereicht. Neue, nicht-geheime Config-Felder llm_provider/llm_base_url/llm_model mit Validierung (Provider-Whitelist, http(s)://-Pflicht fuer base_url, Modell-Fallback). Anbieter-Dropdown sowie base_url-/Modell-Felder im KI-Tab mit Auto-Prefill fuer OpenRouter. Beide LLMService-Konstruktionsorte (Init + Settings-Save) ueber _build_llm_service() zusammengefuehrt, damit base_url/model nicht auseinanderlaufen. Secret-Modell unveraendert: nur Env-Var-Name in config.json, Key aus secrets.env. Hotkey/Auto-Paste/Whisper unangetastet.
Codex-Finalreview-Finding: llm_provider wurde gespeichert, aber base_url bedingungslos verwendet. Bei manuell editierter config.json (Provider "openai" + gesetzte base_url) waere OpenRouter trotz OpenAI-Auswahl genutzt worden. _build_llm_service() ignoriert jetzt eine gespeicherte base_url, wenn der Provider "openai" ist -> OpenAI-Standardendpunkt. OpenRouter/eigener Endpunkt nutzen base_url nur bei expliziter Auswahl. Regressionstest ergaenzt.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec724c9efa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if provider == "openrouter": | ||
| if not self.edit_base_url.text().strip(): | ||
| self.edit_base_url.setText("https://openrouter.ai/api/v1") | ||
| self.edit_base_url.setEnabled(True) |
There was a problem hiding this comment.
Avoid reusing the OpenAI key for OpenRouter
When a user with an existing OPENAI_API_KEY switches to OpenRouter, this branch only fills base_url; edit_api_key_env remains at the default unless the user notices and changes it manually. _build_llm_service() then resolves that OpenAI token and the SDK sends it as the Authorization header to openrouter.ai, so the common provider-switch path both fails and discloses the user's OpenAI key to the new provider. Please either switch/clear the env field for OpenRouter or block saving until a provider-appropriate key env is selected.
Useful? React with 👍 / 👎.
| autoritativ: bei "openai" wird eine evtl. gespeicherte base_url ignoriert, | ||
| damit der OpenAI-Standardendpunkt genutzt wird (OpenRouter nur bei Auswahl). | ||
| """ | ||
| base_url = "" if self.config.llm_provider == "openai" else self.config.llm_base_url |
There was a problem hiding this comment.
Reject empty endpoints for non-OpenAI providers
For openrouter or custom, an empty llm_base_url can still reach this line because invalid URLs are normalized to "" and custom can be saved blank. Passing that through makes LLMService instantiate the OpenAI SDK with base_url=None, which routes the configured non-OpenAI flow (including the provider token and transcript) to the default OpenAI endpoint instead of failing fast. Please require a non-empty base URL whenever the selected provider is not openai.
Useful? React with 👍 / 👎.
What changed?
Optionaler LLM-Provider neben OpenAI:
LLMServicenimmt jetztbase_url+modelentgegen;gpt-4o-miniist nur noch Default.llm_provider,llm_base_url(http(s)://-Pflicht, sonst leer),llm_model(Fallbackgpt-4o-mini) mit Validierung.BlitztextApp._build_llm_service()→ keine Drift mehr.openaiwird eine evtl. gespeichertebase_urlignoriert (OpenAI-Standardendpunkt). OpenRouter wird nur bei expliziter Auswahl genutzt..venv/zu.gitignoreergänzt.Why?
Ermöglicht das Routen der Umschreib-Workflows über OpenRouter (oder jeden anderen OpenAI-kompatiblen Endpunkt), ohne den Default zu ändern. OpenAI bleibt Standardpfad, OpenRouter ist rein optional. Kein Cloud-TTS in diesem PR.
How did you test it?
QT_QPA_PLATFORM=offscreen WHISPER_GUI_TESTS=1 python -m pytest -q→ 203 passed.sys.modulesinjiziert → Tests laufen ohne echtes Paket), Settings-Persistenz + Provider-Prefill, Regressionstests für_build_llm_service(inkl. Provider-autoritativ beiopenai).config.jsonenthält keinen Key.AI assistance
Ja — umgesetzt mit Claude Code (TDD), read-only gegengeprüft von Codex (GPT-5.4) sowie den code-reviewer- und security-reviewer-Agents (ein Codex-Finding zur Provider-Autorität wurde behoben).
Checklist
pytest tests/(offscreen) — 203 passed.base_urlauf http(s):// validiert.