Skip to main content

gproxy_protocol/transform/gemini/generate_content/claude/
request.rs

1use crate::claude::create_message::request::{
2    ClaudeCreateMessageRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
3};
4use crate::claude::create_message::types::{HttpMethod, Model};
5use crate::gemini::generate_content::request::GeminiGenerateContentRequest;
6use crate::transform::gemini::utils::{
7    claude_output_config_from_effort_and_format,
8    claude_thinking_effort_format_from_gemini_generation_config,
9    gemini_contents_to_claude_messages, gemini_system_instruction_to_claude,
10    gemini_tool_choice_to_claude, gemini_tools_to_claude, strip_models_prefix,
11};
12use crate::transform::utils::TransformError;
13
14impl TryFrom<GeminiGenerateContentRequest> for ClaudeCreateMessageRequest {
15    type Error = TransformError;
16
17    fn try_from(value: GeminiGenerateContentRequest) -> Result<Self, TransformError> {
18        let body = value.body;
19        let model = Model::Custom(strip_models_prefix(&value.path.model));
20        let messages = gemini_contents_to_claude_messages(body.contents);
21        let system = gemini_system_instruction_to_claude(body.system_instruction);
22        let tool_choice = gemini_tool_choice_to_claude(body.tool_config);
23        let tools = gemini_tools_to_claude(body.tools);
24
25        let generation_config = body.generation_config;
26        let max_tokens = generation_config
27            .as_ref()
28            .and_then(|config| config.max_output_tokens)
29            .map(u64::from)
30            .unwrap_or(8192);
31        let stop_sequences = generation_config
32            .as_ref()
33            .and_then(|config| config.stop_sequences.clone());
34        let temperature = generation_config
35            .as_ref()
36            .and_then(|config| config.temperature);
37        let top_k = generation_config
38            .as_ref()
39            .and_then(|config| config.top_k)
40            .map(u64::from);
41        let top_p = generation_config.as_ref().and_then(|config| config.top_p);
42
43        let (thinking, output_effort, output_format) =
44            claude_thinking_effort_format_from_gemini_generation_config(
45                generation_config.as_ref(),
46                Some(&model),
47            );
48        let output_config =
49            claude_output_config_from_effort_and_format(output_effort, output_format.clone());
50
51        Ok(ClaudeCreateMessageRequest {
52            method: HttpMethod::Post,
53            path: PathParameters::default(),
54            query: QueryParameters::default(),
55            headers: RequestHeaders::default(),
56            body: RequestBody {
57                max_tokens,
58                messages,
59                model,
60                container: None,
61                context_management: None,
62                inference_geo: None,
63                mcp_servers: None,
64                metadata: None,
65                cache_control: None,
66                output_config,
67                service_tier: None,
68                speed: None,
69                stop_sequences,
70                stream: None,
71                system,
72                temperature,
73                thinking,
74                tool_choice,
75                tools,
76                top_k,
77                top_p,
78            },
79        })
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::claude::count_tokens::types as ct;
87    use crate::gemini::count_tokens::types::{GeminiContentRole, GeminiPart};
88    use crate::gemini::generate_content::request::{
89        GeminiGenerateContentRequest, PathParameters as GeminiPathParameters,
90        QueryParameters as GeminiQueryParameters, RequestBody as GeminiRequestBody,
91        RequestHeaders as GeminiRequestHeaders,
92    };
93    use crate::gemini::generate_content::types::{
94        GeminiContent, GeminiGenerationConfig, GeminiThinkingConfig,
95    };
96
97    #[test]
98    fn opus_47_converts_budgeted_gemini_thinking_to_adaptive() {
99        let request = GeminiGenerateContentRequest {
100            method: crate::gemini::types::HttpMethod::Post,
101            path: GeminiPathParameters {
102                model: "models/claude-opus-4-7".to_string(),
103            },
104            query: GeminiQueryParameters::default(),
105            headers: GeminiRequestHeaders::default(),
106            body: GeminiRequestBody {
107                contents: vec![GeminiContent {
108                    parts: vec![GeminiPart {
109                        text: Some("hi".to_string()),
110                        ..Default::default()
111                    }],
112                    role: Some(GeminiContentRole::User),
113                }],
114                generation_config: Some(GeminiGenerationConfig {
115                    thinking_config: Some(GeminiThinkingConfig {
116                        thinking_budget: Some(4_096),
117                        ..Default::default()
118                    }),
119                    ..Default::default()
120                }),
121                ..Default::default()
122            },
123        };
124
125        let claude_request = ClaudeCreateMessageRequest::try_from(request).expect("transform");
126        assert!(matches!(
127            claude_request.body.thinking,
128            Some(ct::BetaThinkingConfigParam::Adaptive(_))
129        ));
130    }
131}