| 1802 | const json body = json::parse(hr.body); |
| 1803 | req.raw_body = body; |
| 1804 | if (!parse_common_request_fields(fd, body, req)) return true; |
| 1805 | if (!parse_endpoint_request( |
| 1806 | hr.path, body, req, count_tokens_only)) return false; |
| 1807 | |
| 1808 | const std::vector<ChatMessage> chat_messages = |
| 1809 | normalize_chat_messages(req.messages, req.format, tool_memory_); |
| 1810 | // Reasoning must be applied BEFORE rendering: the template injects |
| 1811 | // the empty <think>\n\n</think>\n\n block when thinking is disabled. |
| 1812 | apply_request_reasoning(body, req); |
| 1813 | // Bandit: parse session_id from extra_body (opt-in adaptive keep_ratio). |
| 1814 | req.session_id = parse_session_id_from_body(body); |
| 1815 | if (!render_and_tokenize_request(fd, chat_messages, req)) return true; |
| 1816 | |
| 1817 | // count_tokens: short-circuit after tokenization. Skip generation |
| 1818 | // entirely — Anthropic's contract is just {"input_tokens": N}. |
| 1819 | if (count_tokens_only) { |
| 1820 | const json response = { |
| 1821 | {"input_tokens", (int) req.prompt_tokens.size()}, |
| 1822 | }; |
| 1823 | send_response(fd, 200, "application/json", response.dump() + "\n"); |
| 1824 | return true; |
| 1825 | } |
| 1826 | } catch (const std::exception & e) { |
| 1827 | send_error(fd, 400, std::string("JSON parse error: ") + e.what()); |
| 1828 | return true; |
| 1829 | } |
| 1830 | |
| 1831 | if (!validate_request_context(fd, req)) return true; |
| 1832 | log_parsed_request(req); |
| 1833 | enqueue_request_and_wait(fd, std::move(req)); |
| 1834 | return true; |
| 1835 | } |
| 1836 | |
| 1837 | // ─── Worker thread ────────────────────────────────────────────────────── |
| 1838 | |
| 1839 | void HttpServer::worker_loop() { |
| 1840 | while (true) { |
| 1841 | ServerJob * job = dequeue(); |
| 1842 | if (!job) break; // stopping |
| 1843 | |
| 1844 | int fd = job->fd; |
| 1845 | const auto & req = job->req; |
| 1846 | auto started_at = std::chrono::steady_clock::now(); |
| 1847 | |
| 1848 | // Track live status for /status page. RAII guard ensures idle on all paths. |
| 1849 | std::string prompt_excerpt; |
| 1850 | if (!req.prompt_tokens.empty()) { |
| 1851 | // Decode first ~40 tokens as a prompt excerpt (cheap, bounded). |
| 1852 | const int excerpt_len = (std::min)((int)req.prompt_tokens.size(), 40); |
| 1853 | std::vector<int32_t> excerpt_toks(req.prompt_tokens.begin(), |
| 1854 | req.prompt_tokens.begin() + excerpt_len); |
| 1855 | prompt_excerpt = tokenizer_.decode(excerpt_toks); |
| 1856 | if (prompt_excerpt.size() > 200) prompt_excerpt.resize(200); |
| 1857 | } |
| 1858 | { |
| 1859 | ServerStatus::RequestInfo info; |
| 1860 | info.model = req.model; |
| 1861 | info.format = api_format_name(req.format); |
nothing calls this directly
no test coverage detected