Files
moeka-project/apps/server
RainbowBird 46fb391330 feat(server/llm-router): propagate upstream attempts via ApiError.cause
When `mapUpstreamError` produced the final 502/503/504, it only carried
`{triedKeys, triedUpstreams, lastStatusCode}` in `details`. The upstream
response body was `.cancel()`'d on the wire and the network error
message vanished into the catch arm — operators staring at a 502 had no
way to tell "OpenRouter region-blocked us" from "key revoked" from
"DNS failed" without re-probing the upstream by hand.

Now each recorded failure carries the diagnostic snippet:
- chat upstreams read at most 256 bytes of the failed body via a
  drain-aware reader before cancelling the rest (socket still returns
  to the pool, no fallback-storm pool exhaustion).
- TTS upstreams reuse `errorMessageFrom(err)` — adapters already bake
  the status + body snippet into `err.message`, so one field carries
  both.
- network / timeout attempts record `errorMessageFromUnknown(err)` so
  "attempt-timeout" vs "ECONNRESET" vs "DNS failed" stays
  distinguishable.

The collected `UpstreamAttempt[]` is attached to `ApiError.cause`
rather than `details`. SEC-5 (no upstream content in client-facing
response body) still holds — only the server-side logger + OTel pick
the cause up. `app.onError` now logs `{details, cause}` together so a
single log line tells the operator both the contract-level summary
and the actual upstream payload.

Adds a router.test.ts regression covering both shapes (HTTP 401 body
snippet + network ECONNRESET errorMessage), with an explicit assertion
that `details` does NOT contain the body text so SEC-5 doesn't drift.
2026-05-18 23:32:34 +08:00
..
2026-05-18 23:29:14 +08:00
2026-01-05 16:26:44 +08:00

@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.