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}