| 1802 | "HTTP/1.1 200 OK\r\n" |
| 1803 | "Content-Type: text/event-stream\r\n" |
| 1804 | "Cache-Control: no-cache\r\n" |
| 1805 | "Connection: keep-alive\r\n" |
| 1806 | "Access-Control-Allow-Origin: *\r\n" |
| 1807 | "\r\n"; |
| 1808 | if (!send_all(fd, headers, std::strlen(headers))) { |
| 1809 | socket_close(fd); |
| 1810 | return; |
| 1811 | } |
| 1812 | // Send initial state immediately. |
| 1813 | std::string initial = status_.to_sse_event(); |
| 1814 | send_all(fd, initial.data(), initial.size()); |
| 1815 | // Register for future broadcasts. The fd is NOT closed here — it stays |
| 1816 | // open until the client disconnects (detected on next broadcast send). |
| 1817 | { |
| 1818 | std::lock_guard<std::mutex> lk(sse_mu_); |
| 1819 | sse_fds_.push_back(fd); |
| 1820 | } |
| 1821 | return; // Do NOT close fd — it's now owned by the SSE broadcast loop. |
| 1822 | } |
| 1823 | |
| 1824 | // Models endpoint (including the existing Codex discovery schema). |
| 1825 | if (hr.method == "GET" && hr.path == "/v1/models") { |
| 1826 | const bool codex_schema = hr.query.find("client_version") != std::string::npos; |
| 1827 | json response = model_list(config_, codex_schema); |
| 1828 | if (!models_.empty()) { |
| 1829 | const char * key = codex_schema ? "models" : "data"; |
| 1830 | for (size_t i = 0; i < models_.size(); ++i) { |
| 1831 | const auto & cfg = models_[i].server->config_; |
| 1832 | if (i != 0) response[key].push_back(model_list(cfg, codex_schema)[key][0]); |
| 1833 | } |
| 1834 | } |
| 1835 | send_response(fd, 200, "application/json", response.dump() + "\n"); |
| 1836 | socket_close(fd); |
| 1837 | return; |
| 1838 | } |
| 1839 | |
| 1840 | // Route POST endpoints. |
| 1841 | if (!route_request(fd, hr)) { |
| 1842 | send_error(fd, 404, "unknown endpoint"); |
| 1843 | } |
| 1844 | socket_close(fd); |
| 1845 | } |
| 1846 | |
| 1847 | // Endpoint structure is parsed once by the listener. Model defaults, templates |
| 1848 | // and token IDs are resolved only by the selected model's handler. |
| 1849 | bool HttpServer::route_model_request(SocketHandle fd, ParsedRequest & req, |
| 1850 | bool count_only) { |
| 1851 | json & body = req.raw_body; |
| 1852 | const std::string requested = body.value("model", std::string()); |
| 1853 | const bool unnamed = requested.empty() || requested == "auto"; |
| 1854 | if (count_only && unnamed) { |
| 1855 | send_error(fd, 400, "token counting requires an explicit model name"); |
| 1856 | return true; |
| 1857 | } |
| 1858 | // Generation always follows operator priority. Only token counting needs |
| 1859 | // an explicit tokenizer, since no generating model has been selected yet. |
| 1860 | const bool automatic = !count_only; |
| 1861 |
nothing calls this directly
no test coverage detected