Frontend voice picker silently lost Azure voices because /audio/voices
was called without ?model=, so the server fell back to DEFAULT_TTS_MODEL
and only ever returned one model's catalog. /audio/models also hardcoded
a single `auto` alias, so the client could never request a specific
model id even if it wanted to. On top of that, the Azure adapter shipped
only 2 hand-typed voices in voices/azure.json — the rest of Microsoft's
catalog was unreachable.
Fixed in one pass:
- Drop voices/*.json. Every getVoiceCatalog now calls unspeech REST
(backend=microsoft|alibaba|volcengine). Azure proxies Microsoft's live
voices/list; cosyvoice + volcengine use unspeech's embedded catalogs.
- Drop direct upstream provider calls from send(). All three adapters
POST to <UNSPEECH_REST_BASE_URL>/v1/audio/speech with the openai-shaped
body unspeech expects (model: `<backend>/<id>`). Azure SSML still built
on our side so <prosody rate> speed survives — unspeech accepts
pre-built <speak> documents.
- Fail-fast on every voices endpoint: empty ?model= → 400 MISSING_MODEL
(no implicit DEFAULT_TTS_MODEL fallback), only `auto` resolves;
streaming upstream missing → 503; unspeech non-2xx / network err → 502;
malformed body → 502 with grepable message. No silent `{voices: []}`.
- /audio/models lists every LLM_ROUTER_CONFIG.tts.models key + `auto`.
- Frontend providerOfficialSpeech.listVoices passes ?model= and throws
on upstream failure instead of returning [].
- New UNSPEECH_REST_BASE_URL configKV entry (no default, missing → 503).
config-sync subscriber invalidates the voice cache when this key OR
LLM_ROUTER_CONFIG changes.
- Router voice catalog Redis cache covers all providers with per-provider
TTL (azure 6h live, alibaba/volcengine 24h since unspeech embeds them
at build time).
Pending billing-SKU verification: DashScope cosyvoice now goes through
unspeech's WS-internal alibaba backend (was REST `/SpeechSynthesizer`).
Functional output equivalent — confirm bill after deploy, revert via
git history if WS pricing is materially higher.
@proj-airi/server
HTTP and WebSocket backend for AIRI. This app owns auth, billing, chat synchronization, gateway forwarding, and server-side observability export.
What It Does
- Serves the Hono-based API and WebSocket endpoints.
- Uses Postgres as the source of truth for users, billing, and durable state.
- Uses Redis for cache, KV, Pub/Sub, and Streams.
- Forwards GenAI requests to the configured upstream gateway and records billing from usage.
- Exports traces, metrics, and logs through OpenTelemetry.
How To Use It
Install dependencies from the repo root and run scoped commands:
pnpm -F @proj-airi/server typecheck
pnpm -F @proj-airi/server exec vitest run
pnpm -F @proj-airi/server build
For local observability infrastructure, use:
docker compose -f apps/server/docker-compose.otel.yml up -d
ADDITIONAL_TRUSTED_ORIGINS (LAN / Capacitor dev)
When the mobile dev server uses a non-localhost origin (for example https://10.x.x.x:5273 from cap copy ios / capacitor.config.json), set ADDITIONAL_TRUSTED_ORIGINS in apps/server/.env.local to a comma-separated list of exact origins (parsed and normalized at startup). Example:
ADDITIONAL_TRUSTED_ORIGINS=https://10.0.0.129:5273,https://198.18.0.1:5273
Restart the API server after changing this variable.