use eyre::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
#[derive(Debug, Deserialize)]
pub struct GeminiRequest {
#[serde(default)]
pub contents: Vec<Content>,
#[serde(default)]
pub generation_config: Option<GenerationConfig>,
#[serde(default)]
pub system_instruction: Option<Content>,
#[serde(default)]
pub tools: Option<Value>,
#[serde(default)]
pub tool_config: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Content {
pub role: Option<String>,
pub parts: Vec<Part>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Part {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thought: Option<bool>,
#[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
pub thought_signature: Option<String>,
#[serde(rename = "inlineData", skip_serializing_if = "Option::is_none")]
pub inline_data: Option<InlineData>,
#[serde(rename = "functionCall", skip_serializing_if = "Option::is_none")]
pub function_call: Option<FunctionCall>,
#[serde(rename = "functionResponse", skip_serializing_if = "Option::is_none")]
pub function_response: Option<FunctionResponse>,
}
impl Part {
fn is_thought(&self) -> bool {
self.thought == Some(true)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InlineData {
#[serde(rename = "mimeType")]
pub mime_type: String,
pub data: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FunctionCall {
pub name: String,
pub args: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FunctionResponse {
pub name: String,
pub response: Value,
}
#[derive(Debug, Deserialize)]
pub struct GenerationConfig {
#[serde(default)]
pub temperature: Option<f32>,
#[serde(default)]
pub max_output_tokens: Option<i32>,
#[serde(default)]
pub top_p: Option<f32>,
#[serde(default)]
pub top_k: Option<i32>,
}
#[derive(Debug, Serialize)]
pub struct CodeAssistRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub project: Option<String>,
pub model: String,
#[serde(rename = "userPromptId")]
pub user_prompt_id: String,
pub request: GeminiRequestPayload,
}
#[derive(Debug, Serialize)]
pub struct GeminiRequestPayload {
pub contents: Vec<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "generationConfig")]
pub generation_config: Option<serde_json::Map<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "systemInstruction")]
pub system_instruction: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_config: Option<Value>,
}
pub fn extract_model_from_path(path: &str) -> String {
path.split('/')
.find(|s| s.starts_with("gemini-"))
.map(|s| s.split(':').next().unwrap_or(s).to_string())
.unwrap_or_else(|| "gemini-2.5-flash".to_string())
}
pub fn transform_request(
gemini_req: GeminiRequest,
model: String,
project_id: Option<String>,
) -> CodeAssistRequest {
let generation_config = gemini_req.generation_config.map(|config| {
let mut map = serde_json::Map::new();
if let Some(temp) = config.temperature {
map.insert("temperature".to_string(), serde_json::json!(temp));
}
if let Some(max_tokens) = config.max_output_tokens {
map.insert("maxOutputTokens".to_string(), serde_json::json!(max_tokens));
}
if let Some(top_p) = config.top_p {
map.insert("topP".to_string(), serde_json::json!(top_p));
}
if let Some(top_k) = config.top_k {
map.insert("topK".to_string(), serde_json::json!(top_k));
}
map
});
let contents = gemini_req
.contents
.into_iter()
.map(|mut content| {
if content.role.as_deref() == Some("model") {
content.parts.retain(|p| !p.is_thought());
}
content
})
.collect();
CodeAssistRequest {
project: project_id,
model,
user_prompt_id: Uuid::new_v4().to_string(),
request: GeminiRequestPayload {
contents,
generation_config,
system_instruction: gemini_req.system_instruction,
tools: gemini_req.tools,
tool_config: gemini_req.tool_config,
},
}
}
pub fn transform_response(code_assist_response: Value) -> Result<Value> {
let mut response = if let Some(inner) = code_assist_response.get("response") {
inner.clone()
} else {
code_assist_response
};
if response.get("responseId").is_none() {
if let Some(obj) = response.as_object_mut() {
obj.insert(
"responseId".to_string(),
serde_json::json!(Uuid::new_v4().to_string()),
);
}
}
Ok(response)
}