1# PolyLLM — repository guide23PolyLLM (www.polyllm.io) is a BYOK multi-provider AI workspace: OpenAI, Anthropic, Gemini, xAI, Mistral, DeepSeek, Kimi,4OpenRouter and Cerebras behind one normalized adapter layer (`createOpenAICompatAdapter` for the OpenAI-shaped ones), real streaming, capability-driven configuration, Arena comparisons, usage/cost tracking.5The original product brief lives in `docs/SPEC-original.md`; the UI conventions in `docs/UI-BRIEF.md`.67## Non-negotiables8- **Research before provider changes.** Read `docs/provider-research/<provider>.md`, re-fetch the official docs, probe the9 real API (`pnpm providers:matrix`), then update `src/lib/ai/providers/<provider>/catalog*.ts`. Never assume model ids,10 parameters, event names or limits from memory. Bump `Last documentation audit`.11- **Provider quirks stay inside `src/lib/ai/providers/*`.** UI and services only see `PolyModel`, `UnifiedChatRequest`,12 `UnifiedStreamEvent`, `PolyProviderError`.13- **Capabilities drive the UI.** Never branch on a model id in components; use `model.capabilities` / `model.parameters`.14 Never send a parameter the model does not accept (`filterSettings()` + adapter translation).15- **Keys.** User keys are encrypted (`src/lib/crypto/keys.ts`) and decrypted only in `getDecryptedKey()` right before a16 request. Never log, return or serialize them; `PublicConnection` exposes `keyHint` only. Owner keys in `.env` are for the17 registry sync, admin diagnostics and tests — never for user requests.18- **No fake integrations.** A capability is ✅ only after a real request passed (`docs/provider-test-matrix.md`).19- Secrets only in `.env` (gitignored) and the mld manifest on M1M32; `.env.example` documents the variables.2021## Commands22`pnpm dev` · `pnpm typecheck` · `pnpm lint` · `pnpm test` (vitest) · `pnpm test:e2e` (Playwright; dev server with23`EMAIL_DRY_RUN=1 EMAIL_DRY_RUN_PRINT=1`) · `pnpm db:generate` / `pnpm db:migrate` · `pnpm models:sync [provider]` ·24`pnpm providers:matrix [provider]`.2526## Deploy27`mld` from the laptop (see `docs/deployment-cluster.md`): `mld stage . polyllm && mld deploy polyllm --node M3U96a`.28Manifest with secrets: `M1M32:~/dispatch/apps/polyllm.json` (local gitignored copy `deploy/polyllm.mld.json`).2930## Gotchas31- React Compiler lint: no `Date.now()`/impure calls in render, no setState synchronously in effects without an explicit32 eslint-disable, memoize derived lists.33- `import "server-only"` modules are used by CLI scripts through `scripts/hooks.mjs` (tsx `--import`) and by vitest via an alias.34- Anthropic: adaptive thinking may legitimately produce zero thinking tokens on easy prompts; Haiku 4.5 needs `budget_tokens`.35- Gemini: free-tier keys have tiny per-model daily quotas (429 `limit: 20`) and some models are paid-tier only (429 `limit: 0`);36 thought signatures must be replayed on function-call parts.37- xAI: bad keys return HTTP **400** (`Incorrect API key`), `frequency/presence_penalty` always 400, web search only via `/v1/responses`.38- OpenAI: Responses API has no `seed`/`stop`; sampling params only when `reasoning.effort === "none"` on reasoning models.39- Mistral: strict 422 on unknown keys (`random_seed`, `max_tokens`, no `developer` role); reasoning is binary `reasoning_effort` none|high and streams as `thinking` content-array chunks; `magistral-*` are aliases.40- DeepSeek: thinking ON by default (`thinking:{type}` + `reasoning_effort` low|high|max); only `json_object`; `max_tokens` caps reasoning + answer; images silently dropped except on the vision model.41- Kimi: temperature must be 1 / top_p 0.95 (400 otherwise); `/v1/models/{id}` gives capabilities; `reasoning_content` replayed; json_schema not enforced (json_object + prompt).42- OpenRouter: unsupported params are silently dropped — gate on `supported_parameters`; `usage.cost` is exact; `:batch` ids 404 on chat; finish chunk arrives twice.43- Cerebras: unknown params → 400 (whitelist); reasoning via `reasoning_effort` only; `tools`+`response_format` → 400 on gpt-oss; strict schema needs `additionalProperties:false`.44