Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/atlaslattice_tucker-gemini-gpt-.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
8.2 KiB
JSON

{"repo": "atlaslattice/tucker-gemini-gpt-", "files": {"cli/src/council/models.rs": {"c1edab1895418e08d0206b6ddf25606a78b64d25": {"t": 1786068337.7938316, "n": 7814, "h": "b5b3225ff4c98d2f", "c": "//! Model Adapters — Unified interface for all council members\n//!\n//! Each adapter normalizes the API interface so the council\n//! orchestrator can treat all models uniformly.\n\nuse anyhow::{bail, Result};\nuse crate::config::TuckerConfig;\nuse reqwest::Client;\nuse serde_json::json;\n\n/// Trait for all model adapters\n#[async_trait::async_trait]\npub trait ModelAdapter: Send + Sync {\n async fn generate(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String>;\n fn provider_name(&self) -> &str;\n}\n\n// We use a simple trait-object approach without async_trait for now\n// by returning boxed futures. For simplicity, we use a unified adapter.\n\n/// Unified model adapter that handles all providers via HTTP\npub struct UnifiedAdapter {\n client: Client,\n provider: String,\n api_key: Option<String>,\n}\n\nimpl UnifiedAdapter {\n pub fn new(provider: &str, api_key: Option<String>) -> Self {\n Self {\n client: Client::new(),\n provider: provider.to_string(),\n api_key,\n }\n }\n\n pub async fn generate(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n match self.provider.as_str() {\n \"openai\" => self.generate_openai(model, prompt, system).await,\n \"google\" => self.generate_gemini(model, prompt, system).await,\n \"anthropic\" => self.generate_anthropic(model, prompt, system).await,\n \"deepseek\" => self.generate_deepseek(model, prompt, system).await,\n \"xai\" => self.generate_xai(model, prompt, system).await,\n _ => bail!(\"Unknown provider: {}\", self.provider),\n }\n }\n\n /// OpenAI / GPT-4o adapter\n async fn generate_openai(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n let api_key = self.api_key.as_deref()\n .ok_or_else(|| anyhow::anyhow!(\"OPENAI_API_KEY not set\"))?;\n\n let mut messages = Vec::new();\n if let Some(sys) = system {\n messages.push(json!({\"role\": \"system\", \"content\": sys}));\n }\n messages.push(json!({\"role\": \"user\", \"content\": prompt}));\n\n let response = self.client\n .post(\"https://api.openai.com/v1/chat/completions\")\n .header(\"Authorization\", format!(\"Bearer {}\", api_key))\n .json(&json!({\n \"model\": model,\n \"messages\": messages,\n \"max_tokens\": 4096,\n \"temperature\": 0.7,\n }))\n .send()\n .await?;\n\n let body: serde_json::Value = response.json().await?;\n let content = body[\"choices\"][0][\"message\"][\"content\"]\n .as_str()\n .unwrap_or(\"(no response)\")\n .to_string();\n\n Ok(content)\n }\n\n /// Google Gemini adapter\n async fn generate_gemini(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n let api_key = self.api_key.as_deref()\n .ok_or_else(|| anyhow::anyhow!(\"GEMINI_API_KEY not set\"))?;\n\n let full_prompt = match system {\n Some(sys) => format!(\"{}\\n\\n{}\", sys, prompt),\n None => prompt.to_string(),\n };\n\n let url = format!(\n \"https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}\",\n model, api_key\n );\n\n let response = self.client\n .post(&url)\n .json(&json!({\n \"contents\": [{\"parts\": [{\"text\": full_prompt}]}],\n \"generationConfig\": {\n \"temperature\": 0.7,\n \"maxOutputTokens\": 4096,\n }\n }))\n .send()\n .await?;\n\n let body: serde_json::Value = response.json().await?;\n let content = body[\"candidates\"][0][\"content\"][\"parts\"][0][\"text\"]\n .as_str()\n .unwrap_or(\"(no response)\")\n .to_string();\n\n Ok(content)\n }\n\n /// Anthropic Claude adapter\n async fn generate_anthropic(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n let api_key = self.api_key.as_deref()\n .ok_or_else(|| anyhow::anyhow!(\"ANTHROPIC_API_KEY not set\"))?;\n\n let mut body = json!({\n \"model\": model,\n \"max_tokens\": 4096,\n \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n });\n\n if let Some(sys) = system {\n body[\"system\"] = json!(sys);\n }\n\n let response = self.client\n .post(\"https://api.anthropic.com/v1/messages\")\n .header(\"x-api-key\", api_key)\n .header(\"anthropic-version\", \"2023-06-01\")\n .header(\"content-type\", \"application/json\")\n .json(&body)\n .send()\n .await?;\n\n let resp_body: serde_json::Value = response.json().await?;\n let content = resp_body[\"content\"][0][\"text\"]\n .as_str()\n .unwrap_or(\"(no response)\")\n .to_string();\n\n Ok(content)\n }\n\n /// DeepSeek adapter (OpenAI-compatible API)\n async fn generate_deepseek(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n let api_key = self.api_key.as_deref()\n .ok_or_else(|| anyhow::anyhow!(\"DEEPSEEK_API_KEY not set\"))?;\n\n let mut messages = Vec::new();\n if let Some(sys) = system {\n messages.push(json!({\"role\": \"system\", \"content\": sys}));\n }\n messages.push(json!({\"role\": \"user\", \"content\": prompt}));\n\n let response = self.client\n .post(\"https://api.deepseek.com/chat/completions\")\n .header(\"Authorization\", format!(\"Bearer {}\", api_key))\n .json(&json!({\n \"model\": model,\n \"messages\": messages,\n \"max_tokens\": 4096,\n \"temperature\": 0.7,\n }))\n .send()\n .await?;\n\n let body: serde_json::Value = response.json().await?;\n let content = body[\"choices\"][0][\"message\"][\"content\"]\n .as_str()\n .unwrap_or(\"(no response)\")\n .to_string();\n\n Ok(content)\n }\n\n /// xAI Grok adapter (OpenAI-compatible API)\n async fn generate_xai(&self, model: &str, prompt: &str, system: Option<&str>) -> Result<String> {\n let api_key = self.api_key.as_deref()\n .ok_or_else(|| anyhow::anyhow!(\"XAI_API_KEY not set\"))?;\n\n let mut messages = Vec::new();\n if let Some(sys) = system {\n messages.push(json!({\"role\": \"system\", \"content\": sys}));\n }\n messages.push(json!({\"role\": \"user\", \"content\": prompt}));\n\n let response = self.client\n .post(\"https://api.x.ai/v1/chat/completions\")\n .header(\"Authorization\", format!(\"Bearer {}\", api_key))\n .json(&json!({\n \"model\": model,\n \"messages\": messages,\n \"max_tokens\": 4096,\n \"temperature\": 0.7,\n }))\n .send()\n .await?;\n\n let body: serde_json::Value = response.json().await?;\n let content = body[\"choices\"][0][\"message\"][\"content\"]\n .as_str()\n .unwrap_or(\"(no response)\")\n .to_string();\n\n Ok(content)\n }\n}\n\n/// Factory function to get the right adapter for a provider\npub fn get_adapter(provider: &str, config: &TuckerConfig) -> Result<UnifiedAdapter> {\n let api_key = match provider {\n \"openai\" => config.api_keys.openai.clone(),\n \"google\" => config.api_keys.gemini.clone(),\n \"anthropic\" => config.api_keys.anthropic.clone(),\n \"deepseek\" => config.api_keys.deepseek.clone(),\n \"xai\" => config.api_keys.xai.clone(),\n _ => bail!(\"Unknown provider: {}\", provider),\n };\n\n Ok(UnifiedAdapter::new(provider, api_key))\n}\n"}}}}