init commit
This commit is contained in:
+278
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
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
|
||||
Reference in New Issue
Block a user