Skip to main content

gproxy_protocol/transform/guardian/request/
gemini.rs

1use super::*;
2
3pub fn prepare_gemini(
4    input: source::GuardianRequestBody,
5    context: GuardianRequestContext,
6) -> Result<Converted<GuardianPreparedRequest>, TransformError> {
7    prepare_gemini_with_limits(input, context, default_history_limits())
8}
9
10pub fn prepare_gemini_with_limits(
11    input: source::GuardianRequestBody,
12    context: GuardianRequestContext,
13    limits: CodecLimits,
14) -> Result<Converted<GuardianPreparedRequest>, TransformError> {
15    prepare_gemini_plan(input, context, limits, true)
16}
17
18pub(super) fn prepare_gemini_plan(
19    input: source::GuardianRequestBody,
20    context: GuardianRequestContext,
21    limits: CodecLimits,
22    attach: bool,
23) -> Result<Converted<GuardianPreparedRequest>, TransformError> {
24    let input = input.into_declared();
25
26    bound_source(&input, limits)?;
27    let payload = task_payload(&input, context.operation, limits)?;
28    let model = context
29        .target_model
30        .strip_prefix("models/")
31        .unwrap_or(&context.target_model);
32    if model.is_empty()
33        || model.contains(['/', '?', '#', '\\'])
34        || model.chars().any(char::is_control)
35    {
36        return Err(TransformError::shape(
37            "guardian.target_model",
38            "one Gemini model path segment required",
39        ));
40    }
41    let model = encode_model_segment(model);
42    let mut generation = g::GenerationConfig::builder()
43        .max_output_tokens(context.max_tokens)
44        .build();
45    let service_tier = input
46        .service_tier
47        .as_deref()
48        .map(|value| {
49            Ok(match value {
50                "auto" => g::ServiceTier::Unspecified,
51                "default" => g::ServiceTier::Standard,
52                "flex" => g::ServiceTier::Flex,
53                "priority" => g::ServiceTier::Priority,
54                _ => {
55                    return Err(TransformError::unsupported(
56                        "guardian.service_tier",
57                        "Gemini lacks requested service tier",
58                    ));
59                }
60            })
61        })
62        .map(crate::transform::optional)
63        .transpose()?
64        .flatten();
65
66    if let Some(level) = effort(&input) {
67        let mut thinking = g::ThinkingConfig::builder().build();
68        match level {
69            "none" => thinking.thinking_budget = Some(0),
70            "minimal" => thinking.thinking_level = Some(g::ThinkingLevel::Minimal),
71            "low" => thinking.thinking_level = Some(g::ThinkingLevel::Low),
72            "medium" => thinking.thinking_level = Some(g::ThinkingLevel::Medium),
73            "high" => thinking.thinking_level = Some(g::ThinkingLevel::High),
74            _ => {}
75        }
76        generation.thinking_config = Some(thinking);
77    }
78    if context.operation == GuardianOperation::Review {
79        let (_, output_schema, _) = review_schema(&input)?;
80        generation.response_mime_type = Some("application/json".into());
81        generation.response_json_schema = Some(output_schema);
82    }
83    let mut body = g::GenerateContentRequestBody::builder(vec![
84        g::Content::builder()
85            .role("user")
86            .parts(vec![g::Part::builder().text(payload).build()])
87            .build(),
88    ])
89    .system_instruction(
90        g::Content::builder()
91            .role("system")
92            .parts(vec![
93                g::Part::builder()
94                    .text(instructions(&input, context.operation))
95                    .build(),
96            ])
97            .build(),
98    )
99    .generation_config(generation)
100    .store(input.store)
101    .build();
102    body.service_tier = service_tier;
103    let mut target = GuardianDialectRequest::Gemini(request(
104        format!("/v1beta/models/{model}:generateContent"),
105        body,
106    ));
107    let media = media(&input)?;
108
109    if attach {
110        attach_media(&mut target, &media)?;
111    }
112    preflight_request(&target, limits, limits.max_body_bytes)?;
113    let report = report(&input, &target);
114    Ok(Converted {
115        value: GuardianPreparedRequest {
116            operation: context.operation,
117            request: target,
118            source: input,
119        },
120        report,
121    })
122}
123
124fn encode_model_segment(value: &str) -> String {
125    use std::fmt::Write;
126    let mut output = String::new();
127    for byte in value.bytes() {
128        if byte.is_ascii_alphanumeric() || b"-._~".contains(&byte) {
129            output.push(char::from(byte));
130        } else {
131            write!(&mut output, "%{byte:02X}").expect("writing to String");
132        }
133    }
134    output
135}