Skip to content

fix: TTS voice mapping, response model names, Termux support, docs index, health-check error surfacing - #573

Merged
tashfeenahmed merged 1 commit into
mainfrom
sprint/issue-fixes-523-568-308-436-536
Jul 20, 2026
Merged

fix: TTS voice mapping, response model names, Termux support, docs index, health-check error surfacing#573
tashfeenahmed merged 1 commit into
mainfrom
sprint/issue-fixes-523-568-308-436-536

Conversation

@tashfeenahmed

@tashfeenahmed tashfeenahmed commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Resolves five issues from this sprint in one change set:

  • Closes TTS: OpenAI voice names are not mapped to provider voices #523TTS: OpenAI voice names mapped to provider voices. Per-provider maps for SiliconFlow, Gemini, and Pollinations with safe default fallbacks for unknown voices; Cloudflare MeloTTS no longer receives the voice name as a language code. Default OpenAI-SDK TTS requests now work out of the box.
  • Closes Wrong model name in response for reka-edge-2603 #568Responses report the correct model name. The proxy rewrites the response model field to the routed model id for all providers, on both streaming (every SSE frame) and non-streaming paths, so upstream placeholders like Reka's "default" never leak.
  • Closes [Docs] Add Android/Termux installation guide #308Android/Termux support. A node:sqlite adapter is auto-selected on Android (migrations, nested transactions via SAVEPOINT, WAL, file-backed DBs); better-sqlite3 moves to optionalDependencies so its failed native build no longer breaks install; adds docs/install/android-termux.md. Thanks @PSYCH0patch for the groundwork in feat: experimental Termux/Android compatibility #497.
  • Closes docs: link docs/ + CONTRIBUTING.md prominently from README — 49KB README, adjacent docs gaps (#308 #418 #408) #436Docs discoverability. New docs/README.md index covering all docs with verified links, plus prominent README TOC links to the documentation index and contributor guide.
  • Closes Display health check error messages #536Health-check failure reasons surfaced. Probe failures capture the provider's actual reason, run through secret redaction, persist to a new last_health_error column (idempotent migration), appear in /api/keys and /api/health (auth-protected), clear on recovery, and display beneath the affected key in the dashboard. Cloudflare token verification now returns diagnostic failures instead of a bare boolean, and the redactor additionally catches JSON-quoted secret labels ({"api_key": "..."}) and bare high-entropy tokens.

Also adopts live-verified provider quirks encountered while testing (Requesty sampling workaround, Pollinations base URL move).

Testing: full server suite passes — 107 files, 1,078 tests (including new suites for voice mapping, stream/model-name integrity, node:sqlite adapter, error redaction, and the health-error pipeline). Server tsc --noEmit clean.

…odel names, Termux support, docs index, health-check error surfacing

- TTS (#523): map OpenAI voice names (alloy, echo, fable, ...) to native
  voices per provider (SiliconFlow, Gemini, Pollinations) with safe default
  fallbacks; Cloudflare MeloTTS no longer receives the voice as a language code.
- Model names (#568): chat responses report the routed model id for all
  providers on both streaming and non-streaming paths, instead of leaking
  upstream placeholders like "default" (Reka).
- Android/Termux (#308): node:sqlite adapter auto-selected on Android with
  migration, nested-transaction, WAL, and file-backed support; better-sqlite3
  becomes an optionalDependency; adds docs/install/android-termux.md.
- Docs (#436): docs/README.md index plus prominent README links to the
  documentation index and contributor guide.
- Health errors (#536): key health-check failures are captured with the
  provider's reason, redacted, persisted (last_health_error), exposed via
  /api/keys and /api/health, cleared on recovery, and shown under the key
  in the dashboard. Cloudflare token verification now returns diagnostic
  failures instead of a bare boolean, and the redactor also catches
  JSON-quoted secret labels and bare high-entropy tokens.

Also adopts live-verified provider quirks encountered while testing
(Requesty sampling workaround, Pollinations base URL move).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tashfeenahmed
tashfeenahmed merged commit 099fcd5 into main Jul 20, 2026
3 of 4 checks passed
@tashfeenahmed
tashfeenahmed deleted the sprint/issue-fixes-523-568-308-436-536 branch July 20, 2026 17:48
tashfeenahmed added a commit that referenced this pull request Jul 26, 2026
…rved-model drift guard, failover-ladder UI, credits (#603)

* fix(config): skip missing-key entries at boot instead of crashing

A declarative config written when a platform was still keyless (pollinations
lost `keyless: true` in #573) carries a `keys` entry with no `key`.
applyDeclarativeConfigFromEnv() runs inside main(), and upsertApiKey threw
`key is required for <platform>`, so main().catch exited 1 — a stale config
bricked the install at startup.

Boot-time apply now degrades that shape to a per-entry skip: any non-keyless
platform entry without a key logs a warning naming the platform (with a
remediation hint — pollinations points at enter.pollinations.ai) and is
skipped, while the rest of the config still applies. The result gains a
`warnings` array and the boot summary reports the skip count. Genuinely
malformed config (unparseable JSON, schema violations, unknown platforms,
custom entries without baseUrl) still fails loudly, and the write-time API
path in routes/keys.ts is untouched.

Fixes the boot-crash from #600 (legacy keyless pollinations config).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(readme): credit issue reporters; document auto:* routing strategies

Credit reporters and a proposer from adopted issues:
- @NirvanaCh7 (#549 GitHub Models retirement heads-up; #335 routing/
  observability write-up that led to same-model unification in #341)
- @Mohamed3nan (#335 comment proposing same-model-first failover)
- @Arman-Espiar (#592 local Ollama routes hitting false 429 cooldowns)
- @MetaMysteries8 (#600 Pollinations community models + keyless boot crash)
- @lujun880726 (#499 disabled models stuck in Fusion slots)
@LoneRifle (#534 Kilo auto-route aliasing) is already on the wall for
#179/#161, so no new avatar.

Also document the previously undocumented routing strategy suffixes in
"Using the API": auto:smart | auto:fast | auto:cheap | auto:reliable |
auto:balanced, their synonyms and case-insensitivity, and
auto:<profile-name> for routing through a named profile
(server/src/services/router.ts GLOBAL_SORT_ALIASES + resolveRoutingChain).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Stop benching local Ollama routes with cloud-quota cooldowns

Local inference routes (custom-platform Ollama/llama.cpp/LM Studio) were
getting fake 429s: a slow generation timed out, classified retryable, and
fed the null-limits exhaustion heuristic — 90s transient bench, then the
2m -> 10m -> 1h -> 24h ladder — stranding what is usually the user's ONLY
route and rendering "All models exhausted".

Three changes at the existing choke points:

- Error-kind gating: the null-limits "effectively daily-exhausted"
  heuristic in getCooldownDecisionForLimit now only fires on an actual
  quota signal (new isRateLimitSignal: structured 429 or rate-limit
  wording). Timeouts and 5xx keep the short transient bench and no longer
  record heuristic hits. cooldownDecisionForError and fusion's decision
  sites thread the error kind through; the Ollama-Cloud protection (real
  429s on opaque-quota providers escalating) is preserved and now tested.

- Locality exemption: a key whose base_url points at loopback/RFC1918
  (new sync isLoopbackOrPrivateUrl in url-guard — literal IPs and
  'localhost' only, no DNS on the hot path) is capped at a 5s bench with
  source 'heuristic', never the ladder, even on a literal 429 or a
  Retry-After. Verdict cached per key id (base_url is immutable per row).

- Ladder decay: cooldownHits now clears on a successful request, like
  nullLimitHits — a served request proves the quota is alive, so the next
  failure starts the ladder over instead of inheriting up-to-24h steps
  from stale hits. Escalation while a quota is genuinely spent is
  untouched (no successes can occur then), and the documented ladder
  contract keeps its coverage.

17 new tests (red-green via patch method); full suite 1369 passing.

Fixes #592.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Served-model observability guard: detect upstream model drift (#534)

The proxy overwrites the upstream `model` field with the routed id before
the caller sees it (the gateway contract — providers like Reka return
placeholders such as "default"). That overwrite also destroyed the only
evidence when a provider silently serves a DIFFERENT model than requested.
Nothing in the codebase detected served != requested.

- Capture the raw upstream model BEFORE the overwrite, on both the
  non-streaming path (result.model) and the streaming path (first frame
  that carries one). Covers openai-compat, cohere, and cloudflare, whose
  adapters pass the upstream body through; google/aihorde synthesize their
  responses with the routed id, so they carry no upstream signal.
- lib/served-model.ts compares served vs routed ignoring cosmetic spelling
  (case, provider/org prefix, ":free"-style tier suffix, separator runs)
  and placeholders ("default"), warn-logs `[ServedModel] platform=...
  requested=... served=...` once per (platform, requested, served) tuple
  per process, and returns the raw served id for persistence.
- New nullable requests.served_model column (migration
  20260726_000005_request_served_model) written via lib/request-log.ts
  only when the served model genuinely differs — NULL in the healthy case
  keeps the table small and drifted rows trivially queryable.
- Per-request analytics detail payload now includes servedModel.

The response contract is unchanged: clients still see the routed model in
bodies and streamed frames. Tests: 17 new (unit normalization/log-cap +
integration for both paths, cosmetic equality, NULL column, contract);
full suite 1369 passing, zero regressions from the 1352 baseline.

Served-model drift detection for #534.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(analytics): failover-ladder drill-down + provider stats UI (#335)

Server:
- GET /api/analytics/requests accepts optional status ('success'|'error')
  and platform query filters; both validated (400 on bad input), applied as
  bound parameters, and absent filters keep the behavior byte-identical.
- New migration 20260726_000005_attempt_error_summary: nullable
  request_attempts.error_summary. Populated by the fallback loop for every
  attempt that ended on an error — sanitized through lib/error-redaction.ts
  (same secret/URL scrubbing as the parent row's error text) and capped at
  200 chars via the new summarizeAttemptError(). NULL for ok/committed hops
  and pre-existing rows. GET /api/analytics/requests/:id now returns
  errorSummary per attempt.

Client (Analytics page):
- Recent calls: renders the previously-dropped attemptCount, adds
  status (All/Success/Errors) + provider filters in the panel header, and
  makes rows clickable.
- Clicking a row opens a request-detail dialog (the app's dialog idiom):
  parent-row fields plus the failover ladder — ordinal, platform (with its
  routing.ts color), model, key ordinal, outcome badge, start-offset +
  duration, and the redacted per-hop error summary when present.
- New per-provider breakdown table surfacing the successRate and errorCount
  the by-platform endpoint already returned (plus p95/TTFT/tok-s/tokens).
- All strings added to all six locales (en, es, fr, it, pt-BR, zh-CN).

Tests: +7 (filter validation/behavior, error_summary persistence with
redaction + cap, detail payload, summarizeAttemptError); full suite 1359
passing, client build clean.

Failover-ladder drill-down + provider stats UI — closes the remaining slice of #335.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant