You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/plugins/genai/include/genai_plugin.h

271 lines
11 KiB

/**
* @file genai_plugin.h
* @brief GenAI plugin shared context + plugin-wide entry points.
*
* Holds the singletons the plugin owns at runtime (services pointer
* received during init, optional Anomaly_Detector instance, Prometheus
* counter handles), plus declarations for helper functions defined in
* sibling translation units (e.g. plugin_hooks.cpp).
*
* Lifecycle is documented per-field below. Plugins MUST NOT touch any
* field that depends on Admin DBs (none today; Step 4 will add some)
* before `start()` runs — see ProxySQL_Plugin.h for the full contract.
*
* @see plugins/genai/src/plugin_main.cpp for descriptor + lifecycle.
* @see plugins/genai/src/plugin_hooks.cpp for the query-hook adapter.
*/
#ifndef PROXYSQL_GENAI_PLUGIN_H
#define PROXYSQL_GENAI_PLUGIN_H
#include "ProxySQL_Plugin.h"
#include <atomic>
namespace prometheus { class Counter; }
class Anomaly_Detector;
class MCP_Threads_Handler;
class GenAI_Threads_Handler;
/**
* @brief Process-wide state shared across the plugin's translation units.
*
* Singleton accessed via genai_context(). Populated by `genai_init()`
* and `genai_start()` in plugin_main.cpp; torn down by `genai_stop()`.
*/
struct GenAIPluginContext {
/// Services callbacks vended by core during init(). Borrowed; do
/// not free. Valid for the plugin's entire lifetime.
ProxySQL_PluginServices* services { nullptr };
/// True between successful start() and stop(). Hot-path readers
/// outside the plugin lifecycle (e.g. the query-hook adapter) read
/// it relaxed; it is only mutated on the lifecycle thread.
std::atomic<bool> started { false };
/// Anomaly detector instance. Created during init(), deleted
/// during stop(). nullptr before init() and after stop().
Anomaly_Detector* anomaly_detector { nullptr };
/// Prometheus counter for anomalies *detected* (any risk threshold).
/// Registered against the shared registry during init(); pointer
/// remains valid for the registry's lifetime (the plugin does not
/// own the underlying storage). nullptr if registration failed.
prometheus::Counter* metric_detected_anomalies { nullptr };
/// Prometheus counter for anomalies that were *blocked* (DENY
/// returned to the client). Same lifetime rules as above.
prometheus::Counter* metric_blocked_queries { nullptr };
/// MCP listener handler. Replaces the former core global
/// `GloMCPH` as of Step 4.C. Constructed in `genai_init()`,
/// started by `genai_start()`, torn down by `genai_stop()`.
/// See plugins/genai/src/MCP_Thread.cpp for the listener
/// implementation.
MCP_Threads_Handler* mcp { nullptr };
};
/**
* @brief Reload the GenAI runtime components after genai-* variables change.
*
* Defined in plugin_main.cpp. Tears down and reinitialises the
* `GloGATH` (GenAI_Threads_Handler) and `GloAI` (AI_Features_Manager)
* singletons in place — the only way to pick up changes to bootstrap-
* time values like worker count, embedding endpoint, or LLM-bridge
* provider configuration.
*
* @par Concurrency contract
* Caller MUST guarantee no other thread is dereferencing `GloGATH` or
* `GloAI` while this runs. The teardown calls `delete`-equivalent
* `shutdown()` methods on both globals; concurrent reads from MCP
* listener threads, tool-handler workers, or the anomaly-detector hot
* path would observe a half-destroyed handler. Today the function is
* called from:
*
* 1. `genai_start()` — single-threaded plugin lifecycle, no traffic
* yet, always safe.
* 2. `LOAD GENAI VARIABLES TO RUNTIME` admin command — runs on the
* admin SQL thread while MCP listener / tool handlers may be
* actively serving requests. Currently this is racy; the
* practical mitigation is operator-side: quiesce traffic before
* issuing the LOAD command, or accept the brief reload window.
* 3. `LOAD GENAI VARIABLES FROM CONFIG` — same constraint as (2).
*
* Adding a proper rwlock-around-GloGATH/GloAI is a follow-up tracked
* separately; it touches every consumer of those globals (not just
* the reload path) and is too large for this carve-out PR.
*/
bool genai_refresh_runtime_components(GenAIPluginContext& ctx);
/**
* @brief Accessor for the plugin's shared context singleton.
*
* Meyers-singleton; thread-safe construction since C++11. Callers
* may treat the returned reference as having static storage duration.
*/
GenAIPluginContext& genai_context();
/**
* @brief Format-style log helper that routes through the chassis
* services->log_message callback (so messages land in
* proxysql.log) and falls back to stderr if the chassis isn't
* wired up yet (e.g. very early init or unit-test harness).
*
* Severity follows syslog levels: 3=ERROR, 4=WARN, 6=INFO, 7=DEBUG.
* Messages over ~4 KiB after formatting are silently truncated;
* they're admin/operator log lines, not data.
*
* Defined in plugin_main.cpp.
*/
void genai_log(int level, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
/**
* @brief Push admin DB's `mcp-*` global_variables values into the
* running MCP_Threads_Handler.
*
* Defined in plugin_main.cpp. Called from `genai_start()` (initial
* read at plugin start) and from the `LOAD MCP VARIABLES TO RUNTIME`
* admin command (in plugin_commands.cpp) — both go through this one
* helper to keep behavior consistent.
*
* @return true on success; false if admindb is unavailable or the
* lookup query errored out.
*/
bool mcp_load_variables_from_admindb(GenAIPluginContext& ctx);
/**
* @brief Push admin DB's `genai-*` global_variables values into the
* running GenAI_Threads_Handler.
*
* Defined in plugin_main.cpp. Called from the
* `LOAD GENAI VARIABLES TO RUNTIME` admin command. Mirrors the
* pre-Step-5 `flush_genai_variables___database_to_runtime` in core.
*/
bool genai_load_variables_from_admindb(GenAIPluginContext& ctx);
/**
* @brief Pull runtime `genai-*` values from the running
* GenAI_Threads_Handler back into main.global_variables.
*
* Defined in plugin_main.cpp. Called from the
* `SAVE GENAI VARIABLES TO MEMORY` admin command. Mirrors the
* pre-Step-5 `flush_genai_variables___runtime_to_database` in core.
*/
bool genai_save_variables_to_admindb(GenAIPluginContext& ctx);
/**
* @brief Pull runtime mcp-* values from the running
* MCP_Threads_Handler back into main.global_variables.
*
* Defined in plugin_main.cpp. Called from the
* `SAVE MCP VARIABLES TO MEMORY` / `... FROM RUNTIME` admin command.
* Mirrors the pre-4.C `flush_mcp_variables___runtime_to_database`
* with `runtime=false` (write-back to main, not runtime).
*
* @return true on success; false on SQL error or if admindb /
* ctx.mcp are unavailable.
*/
bool mcp_save_variables_to_admindb(GenAIPluginContext& ctx);
/**
* @brief Install main.mcp_auth_profiles + main.mcp_target_profiles into
* the MCP_Threads_Handler in-memory snapshot, then rebuild the
* joined target_auth_map consumed by the listener.
*
* Defined in plugin_main.cpp. Called from `genai_start()` and the
* `LOAD MCP PROFILES TO RUNTIME` admin command. Per the ABI-3
* separation-of-duties contract this MUST NOT touch runtime_mcp_*
* (those are admin-side projections owned by the chassis).
*/
bool mcp_load_target_auth_map_from_admindb(GenAIPluginContext& ctx);
/**
* @brief Dump the MCP_Threads_Handler in-memory profile snapshots back
* to main.mcp_auth_profiles + main.mcp_target_profiles.
*
* Defined in plugin_main.cpp. Called by the
* `SAVE MCP PROFILES TO MEMORY` admin command (ABI-3 SAVE side of the
* triplet). Never reads runtime_mcp_*.
*/
bool mcp_save_target_auth_map_to_admindb(GenAIPluginContext& ctx);
/**
* @brief Install main.mcp_query_rules into the MCP_Threads_Handler
* in-memory snapshot. When the MCP listener is running, the
* rows are also pushed into Discovery_Schema for the request
* hot-path; otherwise the snapshot stays for SAVE / projection.
*
* Defined in plugin_main.cpp. Called by the
* `LOAD MCP QUERY RULES TO RUNTIME` admin command.
*/
bool mcp_load_query_rules_to_runtime(GenAIPluginContext& ctx);
/**
* @brief Dump the MCP_Threads_Handler in-memory query-rule snapshot
* back to main.mcp_query_rules.
*
* Defined in plugin_main.cpp. Called by the
* `SAVE MCP QUERY RULES TO MEMORY` admin command. The legacy second
* parameter is unused (kept only because callers haven't been
* regenerated); pass false.
*/
bool mcp_save_query_rules_from_runtime(GenAIPluginContext& ctx, bool runtime);
/**
* @brief Bring the MCP listener (`ProxySQL_MCP_Server`) up if
* `ctx.mcp->variables.mcp_enabled` is true and no listener is
* currently running.
*
* Defined in plugin_main.cpp. Called from `genai_start()` and from
* the `LOAD MCP VARIABLES TO RUNTIME` admin command (after a
* variable change might have flipped mcp_enabled to true).
*/
void mcp_start_listener_if_enabled(GenAIPluginContext& ctx);
/**
* @brief Register the plugin's admin SQL verbs (LOAD/SAVE MCP …)
* with the chassis command registry.
*
* Defined in plugin_commands.cpp. Called from `genai_init()`.
*
* @param services The same pointer `genai_init` received. Must
* expose `register_command` (and ideally
* `register_command_alias`) — both are valid during
* init() per the chassis ABI.
*/
void genai_register_admin_commands(ProxySQL_PluginServices* services);
/**
* @brief Register all MCP-related admin / config / stats tables
* with the chassis `register_table` registry.
*
* Defined in plugin_tables.cpp. Called from `genai_init()`.
*
* @param services The same pointer `genai_init` received. Must
* expose `register_table` — valid during init()
* per the chassis ABI.
*/
void genai_register_admin_tables(ProxySQL_PluginServices* services);
/**
* @brief Query-hook adapter: ABI callback that runs the anomaly detector.
*
* Defined in plugin_hooks.cpp. Registered with the plugin manager
* during init() via services->register_query_hook for both MySQL and
* PgSQL protocols.
*
* Behaviour:
* - If `genai_context().anomaly_detector` is null (carve-out
* transition window, or init failed) → return ALLOW.
* - Otherwise run `Anomaly_Detector::analyze` on the payload.
* - On `is_anomaly`, increment `metric_detected_anomalies`.
* - On `should_block`, increment `metric_blocked_queries` and return
* DENY with the detector's explanation as message.
*
* @param payload Borrowed; pointers valid only during this call.
* @return ALLOW or DENY (with message) per the plugin ABI.
*/
ProxySQL_PluginQueryHookResult genai_query_hook(const ProxySQL_PluginQueryHookPayload& payload);
#endif /* PROXYSQL_GENAI_PLUGIN_H */