Files
2026-10-14 09:18:19 +03:00

279 lines
8.2 KiB
C++

/*
Dynagate LLM Gateway: dynamically change models on the fly (C++20 port)
Created by Luxferre in 2026, released into the public domain
*/
#ifndef DYNAGATE_HPP
#define DYNAGATE_HPP
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <initializer_list>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
// Minimal RFC 8259 JSON value, parser and serializer (json.cpp)
enum class JType { Null, Bool, Int, Float, Str, Arr, Obj };
struct Json {
JType type = JType::Null;
bool bval = false;
int64_t ival = 0;
double dval = 0.0;
std::string sval;
std::vector<Json> aval;
std::vector<std::pair<std::string, Json>> oval;
Json();
~Json();
Json(const Json &);
Json(Json &&) noexcept;
Json &operator=(const Json &);
Json &operator=(Json &&) noexcept;
Json(std::nullptr_t);
Json(bool b);
Json(int n);
Json(int64_t n);
Json(double d);
Json(const char *s);
Json(std::string s);
Json(std::string_view s);
static Json object();
static Json object(
std::initializer_list<std::pair<std::string_view, Json>> init);
static Json array();
static Json array(std::initializer_list<Json> init);
bool is_null() const { return type == JType::Null; }
bool is_bool() const { return type == JType::Bool; }
bool is_boolean() const { return type == JType::Bool; }
bool is_number_integer() const { return type == JType::Int; }
bool is_number() const { return type == JType::Int || type == JType::Float; }
bool is_string() const { return type == JType::Str; }
bool is_array() const { return type == JType::Arr; }
bool is_object() const { return type == JType::Obj; }
bool empty() const;
size_t size() const;
bool contains(std::string_view k) const;
void erase(std::string_view k);
Json &operator[](std::string_view k);
const Json &operator[](std::string_view k) const;
Json &at(size_t i);
const Json &at(size_t i) const;
void push_back(Json v);
std::string str(std::string_view def = "") const;
bool as_bool(bool def = false) const;
int64_t as_int64(int64_t def = 0) const;
int as_int(int def = 0) const;
std::string dump(int indent = -1) const;
static bool parse(std::string_view src, Json &out);
static Json parse(std::string_view src);
};
struct ModelConfig {
std::string model;
std::string key;
std::string endpoint;
std::string extra;
bool operator==(const ModelConfig &other) const = default;
};
class ConfigManager {
public:
explicit ConfigManager(std::string file_path);
ConfigManager(std::vector<ModelConfig> configs,
std::vector<std::string> unique_models);
bool load(std::string *err_out = nullptr);
std::vector<ModelConfig> get_configs() const;
std::vector<std::string> get_unique_models() const;
void watch(std::atomic<bool> &stop_flag);
private:
void update(std::vector<ModelConfig> configs,
std::filesystem::file_time_type mod_time,
std::uintmax_t size,
bool has_stat);
std::string file_path_;
mutable std::shared_mutex mu_;
std::vector<ModelConfig> configs_;
std::vector<std::string> unique_models_;
std::filesystem::file_time_type last_mod_time_{};
std::uintmax_t last_size_ = 0;
bool has_stat_ = false;
};
struct HeaderList {
std::vector<std::pair<std::string, std::string>> items;
void add(std::string_view key, std::string_view value);
void set(std::string_view key, std::string_view value);
void del(std::string_view key);
std::string get(std::string_view key) const;
std::vector<std::string> values(std::string_view key) const;
};
struct HttpRequest {
std::string method;
std::string path;
HeaderList headers;
std::string body;
int client_fd = -1;
std::function<bool()> is_cancelled;
};
class ResponseWriter {
public:
virtual ~ResponseWriter() = default;
virtual HeaderList &header() = 0;
virtual void write_header(int status_code) = 0;
virtual bool write(std::string_view data) = 0;
virtual void flush() = 0;
};
class RecorderResponseWriter : public ResponseWriter {
public:
HeaderList &header() override { return headers_; }
void write_header(int status_code) override {
if (!header_written_) {
status_code_ = status_code;
header_written_ = true;
}
}
bool write(std::string_view data) override {
if (!header_written_) {
write_header(200);
}
body_.append(data.data(), data.size());
return true;
}
void flush() override {}
int status_code() const { return status_code_; }
const HeaderList &headers() const { return headers_; }
const std::string &body() const { return body_; }
private:
int status_code_ = 200;
bool header_written_ = false;
HeaderList headers_;
std::string body_;
};
// Logging and string utilities (config.cpp)
void log_msg(const char *fmt, ...);
std::string str_trim(std::string_view sv);
std::string str_to_lower(std::string_view sv);
bool str_iequals(std::string_view a, std::string_view b);
bool parse_csv(std::string_view content,
std::vector<std::vector<std::string>> &rows,
std::string *err_out = nullptr);
std::vector<std::string> parse_auth_tokens(std::string_view content);
void start_csv_updater(std::atomic<bool> &stop_flag,
const std::string &command,
std::chrono::milliseconds interval);
// Core gateway logic and handlers (handler.cpp)
void ensure_curl_initialized();
bool check_auth(const std::vector<std::string> &expected_tokens,
const HttpRequest &req);
void send_unauthorized(ResponseWriter &w);
std::vector<ModelConfig> get_trial_configs(
const std::vector<ModelConfig> &configs,
const std::vector<std::string> &unique_models,
std::string_view requested_model);
std::string build_url(std::string_view endpoint, std::string_view req_path);
int64_t fibonacci(int n);
bool is_media_content(const Json &m);
Json normalize_content(const Json &content_any);
void autodetect_and_normalize_messages(Json &body_map);
Json convert_anthropic_to_openai(const Json &body_map);
Json convert_anthropic_content_to_openai(const Json &cnt_any);
Json convert_openai_to_anthropic_response(
const Json &openai_resp,
const std::string &requested_model);
Json convert_responses_to_openai(const Json &body_map);
Json convert_openai_to_responses_response(
const Json &openai_resp,
const std::string &requested_model);
void handle_models(ConfigManager &cm,
const std::vector<std::string> &expected_tokens,
const HttpRequest &req,
ResponseWriter &w);
void handle_chat_completions(
ConfigManager &cm,
const std::vector<std::string> &expected_tokens,
std::chrono::milliseconds retry_base_delay,
const HttpRequest &req,
ResponseWriter &w);
void handle_image_generations(
ConfigManager &cm,
const std::vector<std::string> &expected_tokens,
std::chrono::milliseconds retry_base_delay,
const HttpRequest &req,
ResponseWriter &w);
void handle_messages(
ConfigManager &cm,
const std::vector<std::string> &expected_tokens,
std::chrono::milliseconds retry_base_delay,
const HttpRequest &req,
ResponseWriter &w);
void handle_responses(
ConfigManager &cm,
const std::vector<std::string> &expected_tokens,
std::chrono::milliseconds retry_base_delay,
const HttpRequest &req,
ResponseWriter &w);
// HTTP server (server.cpp)
class GatewayServer {
public:
GatewayServer(int port,
ConfigManager &cm,
std::vector<std::string> auth_tokens,
std::chrono::milliseconds retry_delay);
~GatewayServer();
GatewayServer(const GatewayServer &) = delete;
GatewayServer &operator=(const GatewayServer &) = delete;
bool start(std::string *err_out = nullptr);
void serve();
void stop();
int bound_port() const { return bound_port_; }
private:
void handle_client(int client_fd);
int requested_port_;
int bound_port_ = 0;
int listen_fd_ = -1;
ConfigManager &cm_;
std::vector<std::string> auth_tokens_;
std::chrono::milliseconds retry_delay_;
std::atomic<bool> stopping_{false};
mutable std::mutex active_mu_;
std::condition_variable active_cv_;
int active_connections_ = 0;
static constexpr int kMaxConcurrentConnections = 256;
};
#endif // DYNAGATE_HPP