Skip to main content

gproxy_protocol/transform/generate/gemini_responses/stream/
context.rs

1use super::super::GeminiResponseContext;
2use super::common::{StreamLimits, invalid, limit, measure};
3use crate::{transform::TransformError, wire::DeclaredFields};
4
5pub struct GeminiToResponsesContext {
6    pub response: GeminiResponseContext,
7    /// Actual selected/observed upstream model, never the client's routing alias.
8    pub actual_model: Option<String>,
9    /// Explicit final thinking observation when native cumulative fields omit the final split.
10    pub final_thinking_tokens: Option<i64>,
11}
12
13#[derive(Default)]
14pub struct ResponsesToGeminiContext {
15    pub response_modalities: Option<Vec<crate::wire::gemini::Modality>>,
16    pub image_mime: Option<crate::wire::gemini::ImageMimeType>,
17}
18
19pub(super) fn clean_context(
20    mut c: GeminiToResponsesContext,
21    limits: StreamLimits,
22) -> Result<GeminiToResponsesContext, TransformError> {
23    if c.response.created_at < 0
24        || c.actual_model.as_ref().is_some_and(|v| v.trim().is_empty())
25        || [
26            c.response.usage.cache_write_tokens,
27            c.response.usage.cached_tokens,
28            c.final_thinking_tokens,
29        ]
30        .into_iter()
31        .flatten()
32        .any(|v| v < 0)
33    {
34        return Err(invalid("negative facts or empty actual model"));
35    }
36    c.response.request = c.response.request.into_declared();
37    c.response.effective_tool_choice = c.response.effective_tool_choice.into_declared();
38    c.response.effective_prompt_cache_options =
39        c.response.effective_prompt_cache_options.into_declared();
40    let bytes = measure(&c.response.request, limits.max_bytes)?
41        .checked_add(measure(
42            &c.response.effective_tool_choice,
43            limits.max_bytes,
44        )?)
45        .and_then(|v| v.checked_add(c.actual_model.as_ref().map_or(0, String::len)))
46        .ok_or_else(limit)?;
47    if bytes > limits.max_bytes {
48        return Err(limit());
49    }
50    measure(
51        &c.response.effective_prompt_cache_options,
52        limits.max_bytes.saturating_sub(bytes),
53    )?;
54    Ok(c)
55}
56
57pub(super) fn clone_response(c: &GeminiResponseContext) -> GeminiResponseContext {
58    GeminiResponseContext {
59        request: c.request.clone(),
60        effective_parallel_tool_calls: c.effective_parallel_tool_calls,
61        effective_tool_choice: c.effective_tool_choice.clone(),
62        usage: c.usage,
63        created_at: c.created_at,
64        effective_prompt_cache_options: c.effective_prompt_cache_options.clone(),
65    }
66}