Skip to main content

gproxy_protocol/transform/guardian/
response.rs

1use crate::{
2    codec,
3    transform::{TransformError, TransformErrorKind},
4    wire::{
5        claude::generate_content as c,
6        gemini as g,
7        openai::{chat as o, responses as r},
8    },
9};
10use serde::{Deserialize, Serialize};
11
12/// The exact structured result requested by the Guardian review client.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct GuardianReviewResult {
15    #[serde(default)]
16    pub risk_level: Option<GuardianRiskLevel>,
17    #[serde(default)]
18    pub user_authorization: Option<GuardianUserAuthorization>,
19    pub outcome: GuardianOutcome,
20    #[serde(default)]
21    pub rationale: Option<String>,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum GuardianRiskLevel {
27    Low,
28    Medium,
29    High,
30    Critical,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum GuardianUserAuthorization {
36    Unknown,
37    Low,
38    Medium,
39    High,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "lowercase")]
44pub enum GuardianOutcome {
45    Allow,
46    Deny,
47}
48
49/// Classification labels are intentionally the complete native contract.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "lowercase")]
52pub enum GuardianClassifyResult {
53    High,
54    Low,
55}
56
57fn parse_review(text: String) -> Result<GuardianReviewResult, TransformError> {
58    let result: GuardianReviewResult = codec::decode_json(text.as_bytes(), result_limits())
59        .map_err(|e| {
60            TransformError::new(
61                TransformErrorKind::InvalidResult,
62                "guardian.review",
63                e.to_string(),
64            )
65        })?;
66    Ok(result)
67}
68
69fn result_limits() -> codec::CodecLimits {
70    codec::CodecLimits {
71        max_buffer_bytes: 256 * 1024,
72        max_value_bytes: 256 * 1024,
73        max_body_bytes: 256 * 1024,
74        max_line_bytes: 256 * 1024,
75        max_part_bytes: 256 * 1024,
76        max_parts: 16,
77    }
78}
79
80fn parse_classify(text: String) -> Result<GuardianClassifyResult, TransformError> {
81    match text.trim() {
82        "high" => Ok(GuardianClassifyResult::High),
83        "low" => Ok(GuardianClassifyResult::Low),
84        _ => Err(TransformError::invalid_result(
85            "guardian.classify",
86            "expected exactly the native high or low label",
87        )),
88    }
89}
90
91pub fn extract_claude(
92    input: c::GenerateContentResponseBody,
93    operation: super::GuardianOperation,
94) -> Result<GuardianExtraction, TransformError> {
95    let text = crate::transform::memory::claude_text(input)?;
96    extract_text(text, operation)
97}
98
99pub fn extract_gemini(
100    input: g::GenerateContentResponseBody,
101    operation: super::GuardianOperation,
102) -> Result<GuardianExtraction, TransformError> {
103    let text = crate::transform::memory::gemini_text(input)?;
104    extract_text(text, operation)
105}
106
107pub fn extract_chat(
108    input: o::GenerateContentResponseBody,
109    operation: super::GuardianOperation,
110) -> Result<GuardianExtraction, TransformError> {
111    let text = crate::transform::memory::chat_text(input)?;
112    extract_text(text, operation)
113}
114
115pub fn extract_responses(
116    input: r::GenerateContentResponseBody,
117    operation: super::GuardianOperation,
118) -> Result<GuardianExtraction, TransformError> {
119    let text = crate::transform::memory::responses_text(input)?;
120    extract_text(text, operation)
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum GuardianExtraction {
125    Review(GuardianReviewResult),
126    Classify(GuardianClassifyResult),
127}
128
129fn extract_text(
130    text: String,
131    operation: super::GuardianOperation,
132) -> Result<GuardianExtraction, TransformError> {
133    match operation {
134        super::GuardianOperation::Review => parse_review(text).map(GuardianExtraction::Review),
135        super::GuardianOperation::Classify => {
136            parse_classify(text).map(GuardianExtraction::Classify)
137        }
138    }
139}