Files

1 line
6.2 KiB
JSON

{"repo": "big-gates/repopilot", "files": {"src/infrastructure/providers/gemini.rs": {"0c6fb1631da7c9872a45245bc08a8286ee0e90dd": {"t": 1785674563.2745528, "n": 5810, "h": "7eff5c94553d8793", "c": "//! Google Gemini provider 어댑터.\n\nuse anyhow::{Result, bail};\nuse async_trait::async_trait;\nuse reqwest::Client;\nuse serde_json::{Value, json};\nuse url::Url;\n\nuse crate::domain::review::{ProviderResponse, ReviewRequest, TokenUsage};\nuse crate::infrastructure::config::{Config, ProviderCommandSpec, resolve_provider_api_key};\n\nuse super::{\n ReviewProvider, build_primary_prompt, command_available, run_provider_command,\n api_runner::{build_api_client, collect_text, send_json},\n};\n\nstruct CliBackend {\n spec: ProviderCommandSpec,\n auth_command: Option<Vec<String>>,\n auto_auth: bool,\n}\n\nenum GeminiBackend {\n Api(GeminiApiBackend),\n Cli(CliBackend),\n}\n\nstruct GeminiApiBackend {\n client: Client,\n base_url: String,\n model: String,\n credential: String,\n}\n\npub struct GeminiProvider {\n backend: GeminiBackend,\n}\n\nimpl GeminiProvider {\n /// API key가 있으면 API 모드, 없으면 CLI 모드로 provider를 활성화한다.\n pub fn from_config(config: &Config) -> Option<Self> {\n let provider = config.providers.gemini.as_ref()?;\n if !provider.is_enabled() {\n return None;\n }\n\n if let Some(credential) = resolve_provider_api_key(provider).credential {\n let api = GeminiApiBackend {\n client: build_api_client(),\n base_url: provider\n .api_base\n .clone()\n .unwrap_or_else(|| \"https://generativelanguage.googleapis.com/v1beta\".to_string()),\n model: provider\n .model\n .clone()\n .unwrap_or_else(|| \"gemini-2.0-flash\".to_string()),\n credential,\n };\n return Some(Self {\n backend: GeminiBackend::Api(api),\n });\n }\n\n let spec = provider.command_spec(\"gemini\")?;\n if !command_available(&spec.command) {\n return None;\n }\n\n let auth_command = provider\n .auth_command\n .clone()\n .filter(|v| !v.is_empty())\n .or_else(|| Some(vec![spec.command.clone()]));\n\n Some(Self {\n backend: GeminiBackend::Cli(CliBackend {\n spec,\n auth_command,\n auto_auth: provider.auto_auth(),\n }),\n })\n }\n\n async fn review_via_api(&self, prompt: &str) -> Result<ProviderResponse> {\n let GeminiBackend::Api(api) = &self.backend else {\n bail!(\"gemini api backend is not configured\");\n };\n\n let endpoint = format!(\n \"{}/models/{}:generateContent\",\n api.base_url.trim_end_matches('/'),\n api.model\n );\n let payload = json!({\n \"contents\": [\n {\n \"parts\": [\n { \"text\": prompt }\n ]\n }\n ]\n });\n\n // Gemini는 API key(query) 또는 OAuth(Bearer) 방식 모두 허용한다.\n let response = if api.credential.starts_with(\"AIza\") {\n let mut url = Url::parse(&endpoint)?;\n url.query_pairs_mut().append_pair(\"key\", &api.credential);\n send_json(\n self.name(),\n \"request Gemini API\",\n api.client.post(url).json(&payload),\n )\n .await?\n } else {\n send_json(\n self.name(),\n \"request Gemini API\",\n api.client\n .post(endpoint)\n .bearer_auth(&api.credential)\n .json(&payload),\n )\n .await?\n };\n\n let content = extract_gemini_content(&response).trim().to_string();\n if content.is_empty() {\n bail!(\"Gemini: empty response content\");\n }\n\n Ok(ProviderResponse {\n content,\n usage: TokenUsage {\n prompt_tokens: response\n .pointer(\"/usageMetadata/promptTokenCount\")\n .and_then(Value::as_u64),\n completion_tokens: response\n .pointer(\"/usageMetadata/candidatesTokenCount\")\n .and_then(Value::as_u64),\n total_tokens: response\n .pointer(\"/usageMetadata/totalTokenCount\")\n .and_then(Value::as_u64),\n },\n })\n }\n}\n\nfn extract_gemini_content(response: &Value) -> String {\n if let Some(content) = response.pointer(\"/candidates/0/content\") {\n return collect_text(content);\n }\n String::new()\n}\n\n#[async_trait]\nimpl ReviewProvider for GeminiProvider {\n fn id(&self) -> &'static str {\n \"gemini\"\n }\n\n fn name(&self) -> &'static str {\n \"Gemini\"\n }\n\n async fn review(&self, request: &ReviewRequest) -> Result<ProviderResponse> {\n let prompt = build_primary_prompt(request);\n match &self.backend {\n GeminiBackend::Api(_) => self.review_via_api(&prompt).await,\n GeminiBackend::Cli(cli) => {\n run_provider_command(\n self.name(),\n &cli.spec,\n &prompt,\n cli.auth_command.as_deref(),\n cli.auto_auth,\n )\n .await\n }\n }\n }\n\n async fn review_prompt(&self, prompt: &str) -> Result<ProviderResponse> {\n match &self.backend {\n GeminiBackend::Api(_) => self.review_via_api(prompt).await,\n GeminiBackend::Cli(cli) => {\n run_provider_command(\n self.name(),\n &cli.spec,\n prompt,\n cli.auth_command.as_deref(),\n cli.auto_auth,\n )\n .await\n }\n }\n }\n}\n"}}}}