OpenAI: Server-Key-Setting, Filter-Fix fuer o-Reihe, ehrlicher Modell-Refresh

Der Modell-Refresh war fuer OpenAI faktisch ein No-Op: der Key wurde nur aus
der api_keys-Tabelle geholt, und ohne Key blieb die alte Liste im Speicher
stehen und wurde direkt wieder in models_cache.json geschrieben - waehrend der
Endpoint pauschal Erfolg meldete.

- ServerSettingsService: neues Setting OpenAiApiKey (OpenAI war der einzige
  Provider ohne Server-Key, analog Gemini/MiniMax/z.ai)
- OpenAiService: ResolveOpenAiKey() (Setting -> api_keys), HasServerOpenAiKey(),
  ResolveProviderConfig zieht den Setting-Key auch im Chat
- OpenAiService: IsOpenAiChatModel() erfasst die o-Reihe inkl. bare IDs
  (o1, o3, kuenftig o5) - StartsWith("o3-") hat die verschluckt
- RefreshModelsFromApis() liefert ProviderRefreshResult[] pro Provider;
  ModelsEndpoint gibt providers[] zurueck, agent-page.js meldet uebersprungene
  Provider als Fehler-Toast statt pauschalem Erfolg
- ChatEndpoint/TelegramService/CronjobService: OpenAI ohne Agent-Key faellt auf
  den Server-Key zurueck statt hart abzubrechen
- ARCHITECTURE.md nachgezogen

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 5 committed 2026-08-15 11:55:22 +02:00
1 parent f38f3a42d1
commit 7305a5a50f
8 files changed
+136 -31

No files matched your search

+10 -2
View File
@@ -624,6 +624,8 @@ Neben Anthropic Claude unterstützt Gott drei **OpenAI-kompatible Provider**. De
| `nvidia:` | Nvidia (g4f.space Proxy) | `g4f.space/api/nvidia/v1` | keiner |
| `pollinations:` | Pollinations | `text.pollinations.ai/openai` | keiner |
| `gemini:` | Google Gemini | `generativelanguage.googleapis.com/v1beta/openai` | `GeminiApiKey` |
| `openai:` | OpenAI | `api.openai.com/v1` | `agent_api_keys`, sonst Server-Setting `OpenAiApiKey` |
| `minimax:` | MiniMax | `api.minimax.io/v1` | `MinimaxApiKey` |
### Model-String-Format
@@ -2042,10 +2044,16 @@ Wenn ein **Sub-Agent** (Preparer, Review, Finalizer) keinem API-Key zugewiesen i
Dasselbe Muster gilt für **Cronjobs** (`CronjobService.ExecuteCronjob` + `SendCronjobNotification`) und **Telegram** (`TelegramService.HandleMessage`) – exakt die **gleiche Provider-/Key-Dreiteilung** wie im `ChatEndpoint`:
- **Anthropic ohne zugewiesenen Key** → `ChatNonStreamingViaCli()` (CLI-Fallback), sofern der User `CanUseCli`/`IsAdmin` hat und die CLI verfügbar ist
- **`openai` ohne Key** → harter Fehler (kein Server-Key, kein CLI-Äquivalent)
- **`openai` ohne Agent-Key** → Server-Setting `OpenAiApiKey` (in `ResolveProviderConfig` aufgelöst, analog Gemini/MiniMax); fehlt auch das → harter Fehler (kein CLI-Äquivalent)
- **keyless Provider (nvidia/gemini/minimax/pollinations)** → Request wird ohne User-Key durchgelassen; der **Server-seitige** Key wird in `ResolveProviderConfig` aufgelöst
Der frühere harte Abbruch („Kein API-Key zugewiesen") blockte irrtümlich **jeden** nicht-leeren Provider – jetzt blockiert nur noch `openai` ohne Key.
Der frühere harte Abbruch („Kein API-Key zugewiesen") blockte irrtümlich **jeden** nicht-leeren Provider – jetzt blockiert nur noch `openai` ganz ohne Key (weder Agent-Key noch `OpenAiApiKey`). Die Prüfung erfolgt im `CronjobService` über `OpenAiService.HasServerOpenAiKey()`, in `ChatEndpoint`/`TelegramService` direkt über `settings.Get("OpenAiApiKey")`.
### Modell-Refresh (`POST /api/models/refresh`)
`RefreshModelsFromApis()` gibt pro Provider ein `ProviderRefreshResult` (`provider`, `loaded`, `count`, `note`) zurück; der Endpoint liefert das als `providers[]` mit. Wird ein Provider **übersprungen** (kein Key, API leer), bleibt seine **alte Liste im Cache stehen** – das wird jetzt explizit gemeldet (Log + Toast im Agent-Formular), statt pauschal „Modelle neu geladen" zu behaupten.
Der OpenAI-Key für die Modell-Liste kommt aus `OpenAiApiKey` (Setting), sonst aus dem ersten aktiven `openai`-Eintrag der `api_keys`-Tabelle (`ResolveOpenAiKey()`). Der Chat-Modell-Filter (`IsOpenAiChatModel`) erfasst `gpt-*`, `chatgpt-*` und die komplette o-Reihe **inklusive bare IDs** (`o1`, `o3`, künftig `o5`) – der frühere `StartsWith("o3-")`-Filter hat die verschluckt.
### Python-Detection für MCP-Config