Skip to main content

gproxy_protocol/transform/openai/compact/gemini/
response.rs

1use crate::gemini::generate_content::response::GeminiGenerateContentResponse;
2use crate::openai::compact_response::response::OpenAiCompactResponse;
3use crate::openai::compact_response::response::{
4    OpenAiCompactedResponseObject, ResponseBody as CompactResponseBody,
5};
6use crate::openai::compact_response::types as cpt;
7use crate::openai::count_tokens::types as ot;
8use crate::transform::openai::generate_content::openai_chat_completions::gemini::utils::{
9    gemini_function_response_to_text, json_object_to_string, prompt_feedback_refusal_text,
10};
11use crate::transform::openai::generate_content::openai_response::gemini::utils::{
12    gemini_citation_annotations, gemini_logprobs,
13};
14use crate::transform::openai::model_list::gemini::utils::openai_error_response_from_gemini;
15use crate::transform::utils::TransformError;
16
17impl TryFrom<GeminiGenerateContentResponse> for OpenAiCompactResponse {
18    type Error = TransformError;
19
20    fn try_from(value: GeminiGenerateContentResponse) -> Result<Self, TransformError> {
21        Ok(match value {
22            GeminiGenerateContentResponse::Success {
23                stats_code,
24                headers,
25                body,
26            } => {
27                let response_id = body.response_id.unwrap_or_else(|| "compact".to_string());
28                let prompt_feedback = body.prompt_feedback;
29                let mut output = Vec::new();
30
31                for (candidate_index, candidate) in
32                    body.candidates.unwrap_or_default().into_iter().enumerate()
33                {
34                    let mut message_content = Vec::new();
35
36                    if let Some(content) = candidate.content.clone() {
37                        for part in content.parts {
38                            if let Some(text) = part.text
39                                && !text.is_empty()
40                            {
41                                message_content.push(
42                                    cpt::CompactedResponseMessageContent::OutputText(
43                                        ot::ResponseOutputText {
44                                            annotations: gemini_citation_annotations(
45                                                candidate.citation_metadata.as_ref(),
46                                            ),
47                                            logprobs: gemini_logprobs(
48                                                candidate.logprobs_result.as_ref(),
49                                            ),
50                                            text,
51                                            type_: ot::ResponseOutputTextType::OutputText,
52                                        },
53                                    ),
54                                );
55                            }
56
57                            if let Some(function_call) = part.function_call {
58                                output.push(cpt::CompactedResponseOutputItem::FunctionToolCall(
59                                    ot::ResponseFunctionToolCall {
60                                        arguments: function_call
61                                            .args
62                                            .as_ref()
63                                            .map(json_object_to_string)
64                                            .unwrap_or_else(|| "{}".to_string()),
65                                        call_id: function_call.id.unwrap_or_else(|| {
66                                            format!(
67                                                "{}_candidate_{}_tool_call",
68                                                response_id, candidate_index
69                                            )
70                                        }),
71                                        name: function_call.name,
72                                        type_: ot::ResponseFunctionToolCallType::FunctionCall,
73                                        id: None,
74                                        status: Some(ot::ResponseItemStatus::Completed),
75                                    },
76                                ));
77                            }
78
79                            if let Some(function_response) = part.function_response {
80                                output.push(cpt::CompactedResponseOutputItem::FunctionCallOutput(
81                                    ot::ResponseFunctionCallOutput {
82                                        call_id: function_response
83                                            .id
84                                            .clone()
85                                            .unwrap_or_else(|| function_response.name.clone()),
86                                        output: ot::ResponseFunctionCallOutputContent::Text(
87                                            gemini_function_response_to_text(function_response),
88                                        ),
89                                        type_:
90                                            ot::ResponseFunctionCallOutputType::FunctionCallOutput,
91                                        id: None,
92                                        status: Some(ot::ResponseItemStatus::Completed),
93                                    },
94                                ));
95                            }
96
97                            if let Some(inline_data) = part.inline_data {
98                                message_content.push(
99                                    cpt::CompactedResponseMessageContent::OutputText(
100                                        ot::ResponseOutputText {
101                                            annotations: Vec::new(),
102                                            logprobs: None,
103                                            text: format!(
104                                                "data:{};base64,{}",
105                                                inline_data.mime_type, inline_data.data
106                                            ),
107                                            type_: ot::ResponseOutputTextType::OutputText,
108                                        },
109                                    ),
110                                );
111                            }
112
113                            if let Some(file_data) = part.file_data {
114                                message_content.push(
115                                    cpt::CompactedResponseMessageContent::OutputText(
116                                        ot::ResponseOutputText {
117                                            annotations: Vec::new(),
118                                            logprobs: None,
119                                            text: file_data.file_uri,
120                                            type_: ot::ResponseOutputTextType::OutputText,
121                                        },
122                                    ),
123                                );
124                            }
125
126                            if let Some(executable_code) = part.executable_code {
127                                message_content.push(
128                                    cpt::CompactedResponseMessageContent::OutputText(
129                                        ot::ResponseOutputText {
130                                            annotations: Vec::new(),
131                                            logprobs: None,
132                                            text: executable_code.code,
133                                            type_: ot::ResponseOutputTextType::OutputText,
134                                        },
135                                    ),
136                                );
137                            }
138
139                            if let Some(code_execution_result) = part.code_execution_result
140                                && let Some(output_text) = code_execution_result.output
141                                && !output_text.is_empty()
142                            {
143                                message_content.push(
144                                    cpt::CompactedResponseMessageContent::OutputText(
145                                        ot::ResponseOutputText {
146                                            annotations: Vec::new(),
147                                            logprobs: None,
148                                            text: output_text,
149                                            type_: ot::ResponseOutputTextType::OutputText,
150                                        },
151                                    ),
152                                );
153                            }
154                        }
155                    }
156
157                    if message_content.is_empty()
158                        && let Some(finish_message) = candidate.finish_message
159                        && !finish_message.is_empty()
160                    {
161                        message_content.push(cpt::CompactedResponseMessageContent::OutputText(
162                            ot::ResponseOutputText {
163                                annotations: Vec::new(),
164                                logprobs: None,
165                                text: finish_message,
166                                type_: ot::ResponseOutputTextType::OutputText,
167                            },
168                        ));
169                    }
170
171                    if !message_content.is_empty() {
172                        let status = match candidate.finish_reason.as_ref() {
173                            Some(
174                                crate::gemini::generate_content::types::GeminiFinishReason::MaxTokens
175                                | crate::gemini::generate_content::types::GeminiFinishReason::Safety
176                                | crate::gemini::generate_content::types::GeminiFinishReason::Recitation
177                                | crate::gemini::generate_content::types::GeminiFinishReason::Blocklist
178                                | crate::gemini::generate_content::types::GeminiFinishReason::ProhibitedContent
179                                | crate::gemini::generate_content::types::GeminiFinishReason::Spii
180                                | crate::gemini::generate_content::types::GeminiFinishReason::ImageSafety
181                                | crate::gemini::generate_content::types::GeminiFinishReason::ImageProhibitedContent
182                                | crate::gemini::generate_content::types::GeminiFinishReason::ImageRecitation,
183                            ) => ot::ResponseItemStatus::Incomplete,
184                            _ => ot::ResponseItemStatus::Completed,
185                        };
186                        output.push(cpt::CompactedResponseOutputItem::Message(
187                            cpt::CompactedResponseMessage {
188                                id: format!("{}_message_{}", response_id, candidate_index),
189                                content: message_content,
190                                role: cpt::CompactedResponseMessageRole::Assistant,
191                                status,
192                                type_: cpt::CompactedResponseMessageType::Message,
193                            },
194                        ));
195                    }
196                }
197
198                if output.is_empty()
199                    && let Some(refusal) = prompt_feedback_refusal_text(prompt_feedback.as_ref())
200                {
201                    output.push(cpt::CompactedResponseOutputItem::Message(
202                        cpt::CompactedResponseMessage {
203                            id: format!("{}_message_0", response_id),
204                            content: vec![cpt::CompactedResponseMessageContent::Refusal(
205                                ot::ResponseOutputRefusal {
206                                    refusal,
207                                    type_: ot::ResponseOutputRefusalType::Refusal,
208                                },
209                            )],
210                            role: cpt::CompactedResponseMessageRole::Assistant,
211                            status: ot::ResponseItemStatus::Incomplete,
212                            type_: cpt::CompactedResponseMessageType::Message,
213                        },
214                    ));
215                }
216
217                let usage = body
218                    .usage_metadata
219                    .map(|usage| {
220                        let input_tokens = usage
221                            .prompt_token_count
222                            .unwrap_or(0)
223                            .saturating_add(usage.tool_use_prompt_token_count.unwrap_or(0));
224                        let output_tokens = usage
225                            .candidates_token_count
226                            .unwrap_or(0)
227                            .saturating_add(usage.thoughts_token_count.unwrap_or(0));
228                        cpt::ResponseUsage {
229                            input_tokens,
230                            input_tokens_details: cpt::ResponseInputTokensDetails {
231                                cached_tokens: usage.cached_content_token_count.unwrap_or(0),
232                            },
233                            output_tokens,
234                            output_tokens_details: cpt::ResponseOutputTokensDetails {
235                                reasoning_tokens: usage.thoughts_token_count.unwrap_or(0),
236                            },
237                            total_tokens: usage
238                                .total_token_count
239                                .unwrap_or_else(|| input_tokens.saturating_add(output_tokens)),
240                        }
241                    })
242                    .unwrap_or(cpt::ResponseUsage {
243                        input_tokens: 0,
244                        input_tokens_details: cpt::ResponseInputTokensDetails { cached_tokens: 0 },
245                        output_tokens: 0,
246                        output_tokens_details: cpt::ResponseOutputTokensDetails {
247                            reasoning_tokens: 0,
248                        },
249                        total_tokens: 0,
250                    });
251
252                OpenAiCompactResponse::Success {
253                    stats_code,
254                    headers: crate::openai::types::OpenAiResponseHeaders {
255                        extra: headers.extra,
256                    },
257                    body: CompactResponseBody {
258                        id: response_id,
259                        created_at: 0,
260                        object: OpenAiCompactedResponseObject::ResponseCompaction,
261                        output,
262                        usage,
263                    },
264                }
265            }
266            GeminiGenerateContentResponse::Error {
267                stats_code,
268                headers,
269                body,
270            } => OpenAiCompactResponse::Error {
271                stats_code,
272                headers: crate::openai::types::OpenAiResponseHeaders {
273                    extra: headers.extra,
274                },
275                body: openai_error_response_from_gemini(stats_code, body),
276            },
277        })
278    }
279}