Third LLM provider: Olava (vLLM/Qwen LoRA) wired into the router
nwhitehouse added a vLLM-backed "Olava" provider - an OpenAI-compatible endpoint serving a Qwen3-based reasoning LoRA - alongside the existing Anthropic and Gemini paths. The interesting part is not the provider addition itself but the scaffolding built to make a small reasoning model behave: think-block stripping, a 16384-token floor, a custom tool-call parser for non-standard LoRA markup, and a tool-stripping default that prevents 400s on vLLM servers without `--enable-auto-tool-choice`.
backend/src/lib/llm/olava.ts handles the awkward details. Reasoning output arrives two different ways depending on the vLLM build: delta.reasoning (DeepSeek-style) or delta.reasoning_content (Qwen3-style), and some builds inline it as <think>...</think> in delta.content instead. The adapter strips all three to keep reasoning tokens out of the visible response. completeOlavaText takes max(caller_max_tokens, OLAVA_MAX_TOKENS) so callers that pass a small cap like 2048 - tuned for non-reasoning models - don't truncate the chain-of-thought before the answer arrives.
The LoRA emits tool calls in a format no standard OpenAI client handles: <tool_call><function=name><parameter=foo>.... The fork ships a client-side parser that iteratively strips trailing XML tags and JSON-decodes parameter values with scalar coercion fallback. Since vLLM streaming silently drops the parsed tool_calls even when finish_reason="tool_calls", the adapter falls back to non-streaming when tools are forwarded. That decision was later revised - see post-456 - but the parser itself is the reusable piece.
Tools are stripped from the request body by default. Set OLAVA_ENABLE_TOOLS=true only if the vLLM server is running with --enable-auto-tool-choice. Without it, the server returns 400. A new GET /user/server-keys endpoint reports which provider env vars are present (booleans, no values), so the frontend can show Olava as available without per-user API keys. The ApiKeyMissingModal message branches on provider === "olava" to explain the server-side configuration path instead of the usual key-entry flow.
Two unrelated fixes rode in the same commit cluster: a **bold** markdown-to-TextRun splitter in generate_docx so party names render correctly instead of leaking literal asterisks, and a system prompt change demoting "MUST call read_document after generate_docx" to MAY - the model was self-critiquing into duplicate downloads on the same turn.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?