Skip to main content

grapsus_config/
agents.rs

1//! Agent configuration types
2//!
3//! This module contains configuration types for external processing agents
4//! (WAF, auth, rate limiting, custom logic).
5
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use validator::Validate;
9
10use grapsus_common::types::CircuitBreakerConfig;
11
12use crate::routes::FailureMode;
13
14// ============================================================================
15// Body Streaming Mode
16// ============================================================================
17
18/// Body streaming mode for agent processing
19///
20/// Controls how request/response bodies are sent to agents:
21/// - `Buffer`: Collect entire body before sending (default, backwards compatible)
22/// - `Stream`: Send chunks as they arrive (lower latency, lower memory)
23/// - `Hybrid`: Buffer small bodies, stream large ones
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26#[derive(Default)]
27pub enum BodyStreamingMode {
28    /// Buffer entire body before sending to agent (default)
29    ///
30    /// - Simpler agent implementation
31    /// - Higher memory usage for large bodies
32    /// - Agent sees complete body for decisions
33    #[default]
34    Buffer,
35
36    /// Stream body chunks as they arrive
37    ///
38    /// - Lower latency and memory usage
39    /// - Agent must handle partial data
40    /// - Supports progressive decisions
41    Stream,
42
43    /// Hybrid: buffer up to threshold, then stream
44    ///
45    /// - Best of both worlds for mixed workloads
46    /// - Small bodies buffered for simplicity
47    /// - Large bodies streamed for efficiency
48    Hybrid {
49        /// Buffer threshold in bytes (default: 64KB)
50        #[serde(default = "default_hybrid_threshold")]
51        buffer_threshold: usize,
52    },
53}
54
55fn default_hybrid_threshold() -> usize {
56    64 * 1024 // 64KB
57}
58
59// ============================================================================
60// Agent Configuration
61// ============================================================================
62
63/// Pool configuration for agent connections
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct AgentPoolConfig {
66    /// Number of connections to maintain per agent (default: 4)
67    #[serde(default = "default_connections_per_agent")]
68    pub connections_per_agent: usize,
69
70    /// Load balancing strategy (default: round_robin)
71    #[serde(default)]
72    pub load_balance_strategy: LoadBalanceStrategy,
73
74    /// Connection timeout in milliseconds (default: 5000)
75    #[serde(default = "default_connect_timeout_ms")]
76    pub connect_timeout_ms: u64,
77
78    /// Time between reconnection attempts in milliseconds (default: 5000)
79    #[serde(default = "default_reconnect_interval_ms")]
80    pub reconnect_interval_ms: u64,
81
82    /// Maximum reconnection attempts before marking unhealthy (default: 3)
83    #[serde(default = "default_max_reconnect_attempts")]
84    pub max_reconnect_attempts: usize,
85
86    /// Time to wait for in-flight requests during shutdown in milliseconds (default: 30000)
87    #[serde(default = "default_drain_timeout_ms")]
88    pub drain_timeout_ms: u64,
89
90    /// Maximum concurrent requests per connection (default: 100)
91    #[serde(default = "default_max_concurrent_per_connection")]
92    pub max_concurrent_per_connection: usize,
93
94    /// Health check interval in milliseconds (default: 10000)
95    #[serde(default = "default_health_check_interval_ms")]
96    pub health_check_interval_ms: u64,
97}
98
99impl Default for AgentPoolConfig {
100    fn default() -> Self {
101        Self {
102            connections_per_agent: default_connections_per_agent(),
103            load_balance_strategy: LoadBalanceStrategy::default(),
104            connect_timeout_ms: default_connect_timeout_ms(),
105            reconnect_interval_ms: default_reconnect_interval_ms(),
106            max_reconnect_attempts: default_max_reconnect_attempts(),
107            drain_timeout_ms: default_drain_timeout_ms(),
108            max_concurrent_per_connection: default_max_concurrent_per_connection(),
109            health_check_interval_ms: default_health_check_interval_ms(),
110        }
111    }
112}
113
114fn default_connections_per_agent() -> usize {
115    4
116}
117fn default_connect_timeout_ms() -> u64 {
118    5000
119}
120fn default_reconnect_interval_ms() -> u64 {
121    5000
122}
123fn default_max_reconnect_attempts() -> usize {
124    3
125}
126fn default_drain_timeout_ms() -> u64 {
127    30000
128}
129fn default_max_concurrent_per_connection() -> usize {
130    100
131}
132fn default_health_check_interval_ms() -> u64 {
133    10000
134}
135
136/// Load balancing strategy for v2 agent pool
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
138#[serde(rename_all = "snake_case")]
139pub enum LoadBalanceStrategy {
140    /// Round-robin across all healthy connections
141    #[default]
142    RoundRobin,
143    /// Route to connection with fewest in-flight requests
144    LeastConnections,
145    /// Route based on health score (prefer healthier agents)
146    HealthBased,
147    /// Random selection
148    Random,
149}
150
151/// Agent configuration
152#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
153pub struct AgentConfig {
154    /// Unique agent identifier
155    pub id: String,
156
157    /// Agent type
158    #[serde(rename = "type")]
159    pub agent_type: AgentType,
160
161    /// Transport configuration
162    pub transport: AgentTransport,
163
164    /// Events this agent handles
165    pub events: Vec<AgentEvent>,
166
167    /// Pool configuration
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub pool: Option<AgentPoolConfig>,
170
171    /// Timeout for agent calls
172    #[serde(default = "default_agent_timeout")]
173    pub timeout_ms: u64,
174
175    /// Failure mode when agent is unavailable
176    #[serde(default)]
177    pub failure_mode: FailureMode,
178
179    /// Circuit breaker configuration
180    #[serde(default)]
181    pub circuit_breaker: Option<CircuitBreakerConfig>,
182
183    /// Maximum request body to send
184    pub max_request_body_bytes: Option<usize>,
185
186    /// Maximum response body to send
187    pub max_response_body_bytes: Option<usize>,
188
189    /// Request body streaming mode
190    ///
191    /// Controls how request bodies are sent to this agent:
192    /// - `buffer`: Collect entire body before sending (default)
193    /// - `stream`: Send chunks as they arrive
194    /// - `hybrid`: Buffer small bodies, stream large ones
195    #[serde(default)]
196    pub request_body_mode: BodyStreamingMode,
197
198    /// Response body streaming mode
199    ///
200    /// Controls how response bodies are sent to this agent.
201    /// Same options as request_body_mode.
202    #[serde(default)]
203    pub response_body_mode: BodyStreamingMode,
204
205    /// Timeout per body chunk when streaming (milliseconds)
206    ///
207    /// Only applies when using `stream` or `hybrid` mode.
208    /// Default: 5000ms (5 seconds)
209    #[serde(default = "default_chunk_timeout")]
210    pub chunk_timeout_ms: u64,
211
212    /// Agent-specific configuration
213    ///
214    /// This configuration is passed to the agent via the Configure event
215    /// when the agent connects. The structure depends on the agent type.
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub config: Option<serde_json::Value>,
218
219    /// Maximum concurrent calls to this agent
220    ///
221    /// Limits the number of simultaneous requests that can be processed by this agent.
222    /// This provides per-agent queue isolation to prevent a slow agent from affecting
223    /// other agents (noisy neighbor problem).
224    ///
225    /// Default: 100 concurrent calls per agent
226    #[serde(default = "default_max_concurrent_calls")]
227    pub max_concurrent_calls: usize,
228}
229
230fn default_chunk_timeout() -> u64 {
231    5000 // 5 seconds
232}
233
234fn default_max_concurrent_calls() -> usize {
235    100 // Per-agent concurrency limit
236}
237
238// ============================================================================
239// Agent Type
240// ============================================================================
241
242/// Agent type
243#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "snake_case")]
245pub enum AgentType {
246    Waf,
247    Auth,
248    RateLimit,
249    Custom(String),
250}
251
252// ============================================================================
253// Agent Transport
254// ============================================================================
255
256/// Agent transport configuration
257#[derive(Debug, Clone, Serialize, Deserialize)]
258#[serde(rename_all = "snake_case")]
259pub enum AgentTransport {
260    /// Unix domain socket
261    UnixSocket { path: PathBuf },
262
263    /// gRPC over TCP
264    Grpc {
265        address: String,
266        tls: Option<AgentTlsConfig>,
267    },
268
269    /// HTTP REST API
270    Http {
271        url: String,
272        tls: Option<AgentTlsConfig>,
273    },
274}
275
276/// Agent TLS configuration
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct AgentTlsConfig {
279    /// Skip certificate verification
280    #[serde(default)]
281    pub insecure_skip_verify: bool,
282
283    /// CA certificate
284    pub ca_cert: Option<PathBuf>,
285
286    /// Client certificate for mTLS
287    pub client_cert: Option<PathBuf>,
288
289    /// Client key for mTLS
290    pub client_key: Option<PathBuf>,
291}
292
293// ============================================================================
294// Agent Events
295// ============================================================================
296
297/// Agent events
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299#[serde(rename_all = "snake_case")]
300pub enum AgentEvent {
301    RequestHeaders,
302    RequestBody,
303    ResponseHeaders,
304    ResponseBody,
305    Log,
306    /// WebSocket frame inspection (after upgrade)
307    WebSocketFrame,
308    /// Guardrail inspection (prompt injection, PII detection)
309    Guardrail,
310}
311
312// ============================================================================
313// Default Value Functions
314// ============================================================================
315
316fn default_agent_timeout() -> u64 {
317    1000
318}