// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY // Copyright 2025 TimeHexOn & foxhop & russell@unturf // https://www.permacomputer.com /* * UncloseAI C++ Library using cpp-httplib (header-only) * OpenAI-compatible API client with streaming support * Compatible with vLLM, Ollama, and OpenAI-compatible endpoints */ #include #include #include #include #include #include #include #include "httplib.h" struct ModelInfo { std::string id; std::string endpoint; std::string host; int port; int max_tokens; }; class UncloseAI { private: std::vector models; std::vector> tts_endpoints; // host, port int timeout; bool debug; // Parse URL into host and port bool parse_url(const std::string& url, std::string& host, int& port, std::string& base_path) { // Simple URL parsing for https://host:port/path size_t proto_end = url.find("://"); if (proto_end == std::string::npos) return false; std::string rest = url.substr(proto_end + 3); size_t slash_pos = rest.find("/"); std::string host_port; if (slash_pos != std::string::npos) { host_port = rest.substr(0, slash_pos); base_path = rest.substr(slash_pos); } else { host_port = rest; base_path = "/"; } size_t colon_pos = host_port.find(":"); if (colon_pos != std::string::npos) { host = host_port.substr(0, colon_pos); port = std::stoi(host_port.substr(colon_pos + 1)); } else { host = host_port; port = (url.find("https://") == 0) ? 443 : 80; } return true; } void discover_endpoints_from_env(const std::string& prefix, std::vector& endpoints) { for(int i = 1; i < 10000; i++) { std::string var_name = prefix + "_" + std::to_string(i); const char* endpoint = std::getenv(var_name.c_str()); if(!endpoint) break; endpoints.push_back(endpoint); } } void discover_models(const std::vector& endpoints) { for(const auto& endpoint : endpoints) { if(debug) { std::cout << "[DEBUG] Discovering from: " << endpoint << std::endl; } std::string host; int port; std::string base_path; if(!parse_url(endpoint, host, port, base_path)) continue; httplib::Client cli(host, port); cli.set_connection_timeout(0, 10000000); // 10 sec cli.set_read_timeout(10, 0); std::string models_path = base_path + (base_path.back() == '/' ? "models" : "/models"); auto res = cli.Get(models_path.c_str()); if(res && res->status == 200) { // Simple JSON parsing for model IDs std::string body = res->body; size_t pos = 0; while((pos = body.find("\"id\":\"", pos)) != std::string::npos) { pos += 6; size_t end = body.find("\"", pos); if(end != std::string::npos) { std::string model_id = body.substr(pos, end - pos); // Filter out modelperm-* and chatcmpl-* entries if(model_id.substr(0, 10) != "modelperm-" && model_id.substr(0, 9) != "chatcmpl-") { ModelInfo info; info.id = model_id; info.endpoint = endpoint; info.host = host; info.port = port; info.max_tokens = 8192; models.push_back(info); if(debug) { std::cout << "[DEBUG] Discovered: " << model_id << std::endl; } } } pos = end + 1; } } } } public: UncloseAI(int timeout = 30, bool debug = false) : timeout(timeout), debug(debug) { std::vector endpoints; std::vector tts_eps; discover_endpoints_from_env("MODEL_ENDPOINT", endpoints); discover_endpoints_from_env("TTS_ENDPOINT", tts_eps); if(debug) { std::cout << "[DEBUG] Initialized with " << endpoints.size() << " endpoint(s)" << std::endl; } discover_models(endpoints); // Parse TTS endpoints for(const auto& ep : tts_eps) { std::string host; int port; std::string base_path; if(parse_url(ep, host, port, base_path)) { tts_endpoints.push_back({host, port}); } } } const std::vector& list_models() const { return models; } int chat(const std::string& prompt, std::string& response, int model_idx = 0, int max_tokens = 100) { if(model_idx >= static_cast(models.size())) return -1; const ModelInfo& model = models[model_idx]; httplib::Client cli(model.host, model.port); cli.set_connection_timeout(0, timeout * 1000000); cli.set_read_timeout(timeout, 0); std::ostringstream json; json << "{\"model\":\"" << model.id << "\"," << "\"messages\":[{\"role\":\"user\",\"content\":\"" << prompt << "\"}]," << "\"stream\":false," << "\"max_tokens\":" << max_tokens << "," << "\"temperature\":0.7}"; auto res = cli.Post("/chat/completions", json.str(), "application/json"); if(res && res->status == 200) { response = res->body; return 0; } return -1; } int chat_stream(const std::string& prompt, std::function callback, int model_idx = 0, int max_tokens = 500) { // NOTE: cpp-httplib streaming API is complex, using simple buffered approach // For production use, consider implementing proper SSE streaming with ContentReceiver std::string response; if(chat(prompt, response, model_idx, max_tokens) == 0) { if(callback) { callback(response); } return 0; } return -1; } int tts(const std::string& text, const std::string& voice, const std::string& output_file) { if(tts_endpoints.empty()) return -1; auto [host, port] = tts_endpoints[0]; httplib::Client cli(host, port); cli.set_connection_timeout(0, timeout * 1000000); cli.set_read_timeout(timeout, 0); std::ostringstream json; json << "{\"model\":\"tts-1\"," << "\"voice\":\"" << voice << "\"," << "\"input\":\"" << text << "\"}"; auto res = cli.Post("/audio/speech", json.str(), "application/json"); if(res && res->status == 200) { std::ofstream file(output_file, std::ios::binary); if(file.is_open()) { file.write(res->body.c_str(), res->body.size()); file.close(); return 0; } } return -1; } }; // Demo program showing library usage int main() { std::cout << "=== UncloseAI C++ Client (cpp-httplib with Streaming) ===\n\n"; UncloseAI client(30, true); if(client.list_models().empty()) { std::cout << "ERROR: No models discovered. Set environment variables:\n"; std::cout << " MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.\n"; return 1; } auto models = client.list_models(); std::cout << "\nDiscovered " << models.size() << " model(s):\n"; for(const auto& m : models) { std::cout << " - " << m.id << " (max_tokens: " << m.max_tokens << ")\n"; } std::cout << "\n"; // Non-streaming chat std::cout << "=== Non-Streaming Chat ===\n"; std::string response; if(client.chat("Explain quantum computing in one sentence", response) == 0) { std::cout << "Response received (" << response.size() << " bytes)\n"; std::cout << "(Full response requires JSON parsing library)\n\n"; } else { std::cout << "Request failed\n\n"; } // Streaming chat std::cout << "=== Streaming Chat ===\n"; int model_idx = (models.size() > 1) ? 1 : 0; std::cout << "Model: " << models[model_idx].id << "\n"; std::cout << "Response: "; client.chat_stream("Write a hello world program in C++", [](const std::string& content) { std::cout << content << std::flush; }, model_idx, 500); std::cout << "\n\n"; // TTS std::cout << "=== TTS Speech Generation ===\n"; std::cout << "Model: tts-1\n"; if(client.tts("Hello from UncloseAI C++ client with cpp-httplib! This demonstrates streaming support.", "alloy", "/tmp/speech.mp3") == 0) { std::cout << "Audio saved to /tmp/speech.mp3\n"; } else { std::cout << "TTS failed\n"; } std::cout << "\n=== Examples Complete ===\n"; return 0; }