1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub const PROTOCOL_VERSION: u32 = 1;
11
12pub const MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum EventType {
19 Configure,
21 RequestHeaders,
23 RequestBodyChunk,
25 ResponseHeaders,
27 ResponseBodyChunk,
29 RequestComplete,
31 WebSocketFrame,
33 GuardrailInspect,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
39#[serde(rename_all = "snake_case")]
40pub enum Decision {
41 #[default]
43 Allow,
44 Block {
46 status: u16,
48 body: Option<String>,
50 headers: Option<HashMap<String, String>>,
52 },
53 Redirect {
55 url: String,
57 status: u16,
59 },
60 Challenge {
62 challenge_type: String,
64 params: HashMap<String, String>,
66 },
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub enum HeaderOp {
73 Set { name: String, value: String },
75 Add { name: String, value: String },
77 Remove { name: String },
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct BodyMutation {
93 pub data: Option<String>,
99
100 #[serde(default)]
104 pub chunk_index: u32,
105}
106
107impl BodyMutation {
108 pub fn pass_through(chunk_index: u32) -> Self {
110 Self {
111 data: None,
112 chunk_index,
113 }
114 }
115
116 pub fn drop_chunk(chunk_index: u32) -> Self {
118 Self {
119 data: Some(String::new()),
120 chunk_index,
121 }
122 }
123
124 pub fn replace(chunk_index: u32, data: String) -> Self {
126 Self {
127 data: Some(data),
128 chunk_index,
129 }
130 }
131
132 pub fn is_pass_through(&self) -> bool {
134 self.data.is_none()
135 }
136
137 pub fn is_drop(&self) -> bool {
139 matches!(&self.data, Some(d) if d.is_empty())
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct RequestMetadata {
146 pub correlation_id: String,
148 pub request_id: String,
150 pub client_ip: String,
152 pub client_port: u16,
154 pub server_name: Option<String>,
156 pub protocol: String,
158 pub tls_version: Option<String>,
160 pub tls_cipher: Option<String>,
162 pub route_id: Option<String>,
164 pub upstream_id: Option<String>,
166 pub timestamp: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
175 pub traceparent: Option<String>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ConfigureEvent {
184 pub agent_id: String,
186 pub config: serde_json::Value,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct RequestHeadersEvent {
196 pub metadata: RequestMetadata,
198 pub method: String,
200 pub uri: String,
202 pub headers: HashMap<String, Vec<String>>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct RequestBodyChunkEvent {
209 pub correlation_id: String,
211 pub data: String,
213 pub is_last: bool,
215 pub total_size: Option<usize>,
217 #[serde(default)]
221 pub chunk_index: u32,
222 #[serde(default)]
224 pub bytes_received: usize,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct ResponseHeadersEvent {
230 pub correlation_id: String,
232 pub status: u16,
234 pub headers: HashMap<String, Vec<String>>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct ResponseBodyChunkEvent {
241 pub correlation_id: String,
243 pub data: String,
245 pub is_last: bool,
247 pub total_size: Option<usize>,
249 #[serde(default)]
251 pub chunk_index: u32,
252 #[serde(default)]
254 pub bytes_sent: usize,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct RequestCompleteEvent {
260 pub correlation_id: String,
262 pub status: u16,
264 pub duration_ms: u64,
266 pub request_body_size: usize,
268 pub response_body_size: usize,
270 pub upstream_attempts: u32,
272 pub error: Option<String>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct WebSocketFrameEvent {
286 pub correlation_id: String,
288 pub opcode: String,
290 pub data: String,
292 pub client_to_server: bool,
294 pub frame_index: u64,
296 pub fin: bool,
298 pub route_id: Option<String>,
300 pub client_ip: String,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "snake_case")]
307pub enum WebSocketOpcode {
308 Continuation,
310 Text,
312 Binary,
314 Close,
316 Ping,
318 Pong,
320}
321
322impl WebSocketOpcode {
323 pub fn as_str(&self) -> &'static str {
325 match self {
326 Self::Continuation => "continuation",
327 Self::Text => "text",
328 Self::Binary => "binary",
329 Self::Close => "close",
330 Self::Ping => "ping",
331 Self::Pong => "pong",
332 }
333 }
334
335 pub fn from_u8(value: u8) -> Option<Self> {
337 match value {
338 0x0 => Some(Self::Continuation),
339 0x1 => Some(Self::Text),
340 0x2 => Some(Self::Binary),
341 0x8 => Some(Self::Close),
342 0x9 => Some(Self::Ping),
343 0xA => Some(Self::Pong),
344 _ => None,
345 }
346 }
347
348 pub fn as_u8(&self) -> u8 {
350 match self {
351 Self::Continuation => 0x0,
352 Self::Text => 0x1,
353 Self::Binary => 0x2,
354 Self::Close => 0x8,
355 Self::Ping => 0x9,
356 Self::Pong => 0xA,
357 }
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
365#[serde(rename_all = "snake_case")]
366pub enum WebSocketDecision {
367 #[default]
369 Allow,
370 Drop,
372 Close {
374 code: u16,
376 reason: String,
378 },
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct AgentRequest {
384 pub version: u32,
386 pub event_type: EventType,
388 pub payload: serde_json::Value,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct AgentResponse {
395 pub version: u32,
397 pub decision: Decision,
399 #[serde(default)]
401 pub request_headers: Vec<HeaderOp>,
402 #[serde(default)]
404 pub response_headers: Vec<HeaderOp>,
405 #[serde(default)]
407 pub routing_metadata: HashMap<String, String>,
408 #[serde(default)]
410 pub audit: AuditMetadata,
411
412 #[serde(default)]
423 pub needs_more: bool,
424
425 #[serde(default)]
430 pub request_body_mutation: Option<BodyMutation>,
431
432 #[serde(default)]
437 pub response_body_mutation: Option<BodyMutation>,
438
439 #[serde(default)]
443 pub websocket_decision: Option<WebSocketDecision>,
444}
445
446impl AgentResponse {
447 pub fn default_allow() -> Self {
449 Self {
450 version: PROTOCOL_VERSION,
451 decision: Decision::Allow,
452 request_headers: vec![],
453 response_headers: vec![],
454 routing_metadata: HashMap::new(),
455 audit: AuditMetadata::default(),
456 needs_more: false,
457 request_body_mutation: None,
458 response_body_mutation: None,
459 websocket_decision: None,
460 }
461 }
462
463 pub fn block(status: u16, body: Option<String>) -> Self {
465 Self {
466 version: PROTOCOL_VERSION,
467 decision: Decision::Block {
468 status,
469 body,
470 headers: None,
471 },
472 request_headers: vec![],
473 response_headers: vec![],
474 routing_metadata: HashMap::new(),
475 audit: AuditMetadata::default(),
476 needs_more: false,
477 request_body_mutation: None,
478 response_body_mutation: None,
479 websocket_decision: None,
480 }
481 }
482
483 pub fn redirect(url: String, status: u16) -> Self {
485 Self {
486 version: PROTOCOL_VERSION,
487 decision: Decision::Redirect { url, status },
488 request_headers: vec![],
489 response_headers: vec![],
490 routing_metadata: HashMap::new(),
491 audit: AuditMetadata::default(),
492 needs_more: false,
493 request_body_mutation: None,
494 response_body_mutation: None,
495 websocket_decision: None,
496 }
497 }
498
499 pub fn needs_more_data() -> Self {
501 Self {
502 version: PROTOCOL_VERSION,
503 decision: Decision::Allow,
504 request_headers: vec![],
505 response_headers: vec![],
506 routing_metadata: HashMap::new(),
507 audit: AuditMetadata::default(),
508 needs_more: true,
509 request_body_mutation: None,
510 response_body_mutation: None,
511 websocket_decision: None,
512 }
513 }
514
515 pub fn websocket_allow() -> Self {
517 Self {
518 websocket_decision: Some(WebSocketDecision::Allow),
519 ..Self::default_allow()
520 }
521 }
522
523 pub fn websocket_drop() -> Self {
525 Self {
526 websocket_decision: Some(WebSocketDecision::Drop),
527 ..Self::default_allow()
528 }
529 }
530
531 pub fn websocket_close(code: u16, reason: String) -> Self {
533 Self {
534 websocket_decision: Some(WebSocketDecision::Close { code, reason }),
535 ..Self::default_allow()
536 }
537 }
538
539 pub fn with_websocket_decision(mut self, decision: WebSocketDecision) -> Self {
541 self.websocket_decision = Some(decision);
542 self
543 }
544
545 pub fn with_request_body_mutation(mut self, mutation: BodyMutation) -> Self {
547 self.request_body_mutation = Some(mutation);
548 self
549 }
550
551 pub fn with_response_body_mutation(mut self, mutation: BodyMutation) -> Self {
553 self.response_body_mutation = Some(mutation);
554 self
555 }
556
557 pub fn set_needs_more(mut self, needs_more: bool) -> Self {
559 self.needs_more = needs_more;
560 self
561 }
562
563 pub fn add_request_header(mut self, op: HeaderOp) -> Self {
565 self.request_headers.push(op);
566 self
567 }
568
569 pub fn add_response_header(mut self, op: HeaderOp) -> Self {
571 self.response_headers.push(op);
572 self
573 }
574
575 pub fn with_audit(mut self, audit: AuditMetadata) -> Self {
577 self.audit = audit;
578 self
579 }
580}
581
582#[derive(Debug, Clone, Default, Serialize, Deserialize)]
584pub struct AuditMetadata {
585 #[serde(default)]
587 pub tags: Vec<String>,
588 #[serde(default)]
590 pub rule_ids: Vec<String>,
591 pub confidence: Option<f32>,
593 #[serde(default)]
595 pub reason_codes: Vec<String>,
596 #[serde(default)]
598 pub custom: HashMap<String, serde_json::Value>,
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
607#[serde(rename_all = "snake_case")]
608pub enum GuardrailInspectionType {
609 PromptInjection,
611 PiiDetection,
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
620pub struct GuardrailInspectEvent {
621 pub correlation_id: String,
623 pub inspection_type: GuardrailInspectionType,
625 pub content: String,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub model: Option<String>,
630 #[serde(default)]
633 pub categories: Vec<String>,
634 #[serde(skip_serializing_if = "Option::is_none")]
636 pub route_id: Option<String>,
637 #[serde(default)]
639 pub metadata: HashMap<String, String>,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize)]
644pub struct GuardrailResponse {
645 pub detected: bool,
647 #[serde(default)]
649 pub confidence: f64,
650 #[serde(default)]
652 pub detections: Vec<GuardrailDetection>,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub redacted_content: Option<String>,
656}
657
658impl Default for GuardrailResponse {
659 fn default() -> Self {
660 Self {
661 detected: false,
662 confidence: 0.0,
663 detections: Vec::new(),
664 redacted_content: None,
665 }
666 }
667}
668
669impl GuardrailResponse {
670 pub fn clean() -> Self {
672 Self::default()
673 }
674
675 pub fn with_detection(detection: GuardrailDetection) -> Self {
677 Self {
678 detected: true,
679 confidence: detection.confidence.unwrap_or(1.0),
680 detections: vec![detection],
681 redacted_content: None,
682 }
683 }
684
685 pub fn add_detection(&mut self, detection: GuardrailDetection) {
687 self.detected = true;
688 if let Some(conf) = detection.confidence {
689 self.confidence = self.confidence.max(conf);
690 }
691 self.detections.push(detection);
692 }
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct GuardrailDetection {
698 pub category: String,
700 pub description: String,
702 #[serde(default)]
704 pub severity: DetectionSeverity,
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub confidence: Option<f64>,
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub span: Option<TextSpan>,
711}
712
713impl GuardrailDetection {
714 pub fn new(category: impl Into<String>, description: impl Into<String>) -> Self {
716 Self {
717 category: category.into(),
718 description: description.into(),
719 severity: DetectionSeverity::Medium,
720 confidence: None,
721 span: None,
722 }
723 }
724
725 pub fn with_severity(mut self, severity: DetectionSeverity) -> Self {
727 self.severity = severity;
728 self
729 }
730
731 pub fn with_confidence(mut self, confidence: f64) -> Self {
733 self.confidence = Some(confidence);
734 self
735 }
736
737 pub fn with_span(mut self, start: usize, end: usize) -> Self {
739 self.span = Some(TextSpan { start, end });
740 self
741 }
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct TextSpan {
747 pub start: usize,
749 pub end: usize,
751}
752
753#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
755#[serde(rename_all = "lowercase")]
756pub enum DetectionSeverity {
757 Low,
759 #[default]
761 Medium,
762 High,
764 Critical,
766}