Phase 3: provider abstraction + retry/timeout#6
Conversation
callOpenAI / callGrok / callGemini были тремя почти одинаковыми функциями
с голым fetch и без retry/timeout. Свёрнуты в registry PROVIDERS + общий
aiFetch().
- aiFetch() обрабатывает retry на 429 и 5xx с экспоненциальным backoff
(~400ms → 1.6s, jitter ±25%, до 2 ретраев). Все три провайдера ходят
через одну обёртку с AbortController-таймаутом 30с.
- PROVIDERS — registry { openai, grok, gemini } с keyField / label /
buildRequest / parseResponse. Различия (chat-completions vs Gemini
payload, Authorization vs x-goog-api-key) изолированы в buildRequest.
- callAI() стал тонким диспатчером без switch'а: смотрит в registry,
тянет ключ из prefs, собирает request через buildRequest, гонит через
aiFetch, парсит через parseResponse, добавляет usage.
- TTA_TEST_KEY (handler) переписан с трёх веток if/else на единый
callAI('provider', model, ping). Удалена ручная сборка OpenAI-compat
payload в дублирующем месте.
- TTA_HEALTH_CHECK получил probe() helper, который тоже идёт через
callAI. Поведение проверок не меняется.
- Поведенческий апгрейд: Gemini теперь тоже проходит checkBudget()
(раньше budget применялся только к OpenAI/Grok — несогласованность
исходного кода).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eba4b175f9
ℹ️ 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".
| } catch (e) { | ||
| // AbortError (timeout) тоже ретраим как сетевую ошибку. | ||
| lastErr = e; | ||
| if (attempt === retries) throw lastErr; |
There was a problem hiding this comment.
Stop retrying non-retriable HTTP failures
When a provider returns a non-retriable HTTP error such as 400/401/403, the throw lastErr from the try block is immediately caught here, and the loop continues unless this is the final attempt. That makes bad API keys or invalid model names issue three requests and wait through backoff before surfacing the error, despite the intended “retry only 429/5xx” behavior; rethrow non-retriable HTTP errors outside this catch path or tag them so they are not retried.
Useful? React with 👍 / 👎.
Summary
callOpenAI/callGrok/callGeminiбыли тремя почти одинаковыми функциями с голым fetch без retry/timeout. Свёрнуты в registryPROVIDERS+ общийaiFetch()с AbortController и экспоненциальным backoff.Что изменилось
aiFetch(url, headers, body, opts)— retry на 429 и 5xx, экспоненциальный backoff ~400ms → 1.6s с jitter ±25%, AbortController-таймаут 30с. Все три провайдера ходят через одну обёртку.PROVIDERSregistry —{ openai, grok, gemini }с полямиkeyField/label/buildRequest/parseResponse. Различия в wire-format (chat-completions vs Gemini payload, Authorization vsx-goog-api-key) изолированы вbuildRequest.callAI()— тонкий диспатчер без switch'а: registry → request → aiFetch → parse → addUsage.TTA_TEST_KEYhandler — три веткиif/elseсвёрнуты в одинcallAI(provider, defaultModel, ping).TTA_HEALTH_CHECKhandler —probe()helper тоже идёт черезcallAI.Поведенческий апгрейд (бонус)
Gemini теперь проходит через
checkBudget()— раньше дневной токен-лимит применялся только к OpenAI/Grok, у Gemini можно было его обойти.Test plan
Daily token budget exceeded.🤖 Generated with Claude Code