{"repo": "bitan-del/gcode-harness", "files": {"src/message.rs": {"009beba7598882f4ba1e167a9b4ed98882579a7f": {"t": 1785745831.6590357, "n": 9956, "h": "a09ddcdec09b8302", "c": "use crate::bus::{BackgroundTaskCompleted, BackgroundTaskProgressEvent, BackgroundTaskStatus};\nuse base64::Engine as _;\nuse regex::Regex;\nuse std::collections::HashSet;\nuse std::path::Path;\nuse std::sync::OnceLock;\n\npub use gcode_message_types::{\n CacheControl, ConnectionPhase, ContentBlock, InputShellResult, Message, Role, StreamEvent,\n TOOL_OUTPUT_MISSING_TEXT, ToolCall, ToolDefinition, ends_with_fresh_user_turn,\n extend_stable_hash, messages_with_dynamic_system_context, sanitize_tool_id,\n stable_message_hash,\n};\n\nmod notifications;\n\npub use notifications::{\n ParsedBackgroundTaskNotification, ParsedBackgroundTaskProgressNotification,\n background_task_display_label, background_task_status_notice,\n format_background_task_notification_markdown, format_background_task_progress_markdown,\n format_input_shell_result_markdown, input_shell_status_notice,\n parse_background_task_notification_markdown,\n parse_background_task_progress_notification_markdown,\n};\n\nfn compile_static_regex(pattern: &str) -> Option {\n match Regex::new(pattern) {\n Ok(regex) => Some(regex),\n Err(err) => {\n eprintln!(\"gcode: failed to compile static regex: {err}\");\n None\n }\n }\n}\n\nfn compile_static_regexes(patterns: &[&str]) -> Vec {\n patterns\n .iter()\n .filter_map(|pattern| compile_static_regex(pattern))\n .collect()\n}\n\n/// Redact likely secrets from persisted tool output.\n///\n/// This is a best-effort safeguard for local session history files. It targets\n/// high-confidence token/key patterns and common `KEY=VALUE` assignments used by\n/// auth flows.\npub fn redact_secrets(text: &str) -> String {\n // Fast path to avoid regex work for most tool outputs.\n let lower = text.to_ascii_lowercase();\n\n if !text.contains(\"sk-\")\n && !text.contains(\"ghp_\")\n && !text.contains(\"github_pat_\")\n && !text.contains(\"AIza\")\n && !text.contains(\"ya29.\")\n && !text.contains(\"xox\")\n && !lower.contains(\"api_key\")\n && !lower.contains(\"token\")\n {\n return text.to_string();\n }\n\n static DIRECT_PATTERNS: OnceLock> = OnceLock::new();\n static ASSIGNMENT_PATTERNS: OnceLock> = OnceLock::new();\n\n let direct_patterns = DIRECT_PATTERNS.get_or_init(|| {\n compile_static_regexes(&[\n r\"sk-ant-(?:oat|ort)01-[A-Za-z0-9_-]{20,}\",\n r\"sk-or-v1-[A-Za-z0-9_-]{20,}\",\n r\"ghp_[A-Za-z0-9]{20,}\",\n r\"github_pat_[A-Za-z0-9_]{20,}\",\n r\"ya29\\.[A-Za-z0-9._-]{20,}\",\n r\"AIza[0-9A-Za-z_-]{20,}\",\n r\"xox[baprs]-[A-Za-z0-9-]{10,}\",\n ])\n });\n\n let assignment_patterns = ASSIGNMENT_PATTERNS.get_or_init(|| {\n compile_static_regexes(&[\n r\"(?m)^\\s*(OPENROUTER_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENCODE_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENCODE_GO_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ZHIPU_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ZAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(302AI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(BASETEN_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CORTECS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(DEEPSEEK_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(FIRMWARE_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(HF_TOKEN\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(MOONSHOT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(NEBIUS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(SCALEWAY_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(STACKIT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(GROQ_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(MISTRAL_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(PERPLEXITY_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(TOGETHER_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(DEEPINFRA_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(XAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(LMSTUDIO_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OLLAMA_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CHUTES_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CEREBRAS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENAI_COMPAT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ANTHROPIC_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(AZURE_OPENAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CURSOR_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(GITHUB_TOKEN\\s*=\\s*)[^\\r\\n]+\",\n ])\n });\n\n let mut redacted = text.to_string();\n let mut redacted_keys: HashSet = [\n \"OPENROUTER_API_KEY\",\n \"OPENCODE_API_KEY\",\n \"OPENCODE_GO_API_KEY\",\n \"ZHIPU_API_KEY\",\n \"ZAI_API_KEY\",\n \"302AI_API_KEY\",\n \"BASETEN_API_KEY\",\n \"CORTECS_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"FIRMWARE_API_KEY\",\n \"HF_TOKEN\",\n \"MOONSHOT_API_KEY\",\n \"NEBIUS_API_KEY\",\n \"SCALEWAY_API_KEY\",\n \"STACKIT_API_KEY\",\n \"GROQ_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"TOGETHER_API_KEY\",\n \"DEEPINFRA_API_KEY\",\n \"XAI_API_KEY\",\n \"LMSTUDIO_API_KEY\",\n \"OLLAMA_API_KEY\",\n \"CHUTES_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"OPENAI_COMPAT_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"CURSOR_API_KEY\",\n \"GITHUB_TOKEN\",\n ]\n .iter()\n .map(|k| (*k).to_string())\n .collect();\n\n for re in direct_patterns {\n redacted = re.replace_all(&redacted, \"[REDACTED_SECRET]\").into_owned();\n }\n\n for re in assignment_patterns {\n redacted = re\n .replace_all(&redacted, \"${1}[REDACTED_SECRET]\")\n .into_owned();\n }\n\n // Also redact custom API key variable names configured at runtime.\n for source in [\n \"GCODE_OPENROUTER_API_KEY_NAME\",\n \"GCODE_OPENAI_COMPAT_API_KEY_NAME\",\n ] {\n let Some(key_name) = std::env::var(source)\n .ok()\n .map(|v| v.trim().to_string())\n .filter(|v| !v.is_empty())\n else {\n continue;\n };\n\n if !key_name\n .chars()\n .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')\n {\n continue;\n }\n if !redacted_keys.insert(key_name.clone()) {\n continue;\n }\n\n let pattern = format!(r\"(?m)^\\s*({}\\s*=\\s*)[^\\r\\n]+\", regex::escape(&key_name));\n if let Ok(re) = Regex::new(&pattern) {\n redacted = re\n .replace_all(&redacted, \"${1}[REDACTED_SECRET]\")\n .into_owned();\n }\n }\n\n redacted\n}\n\npub const GENERATED_IMAGE_TOOL_NAME: &str = \"image_generation\";\npub const GENERATED_IMAGE_MAX_AUTO_VISION_BYTES: u64 = 20 * 1024 * 1024;\n\npub fn generated_image_tool_input(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> serde_json::Value {\n serde_json::json!({\n \"path\": path,\n \"metadata_path\": metadata_path,\n \"output_format\": output_format,\n \"revised_prompt\": revised_prompt,\n })\n}\n\npub fn generated_image_summary(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> String {\n let mut summary = format!(\"Generated image ({}) saved to `{}`.\", output_format, path);\n if let Some(metadata_path) = metadata_path {\n summary.push_str(&format!(\"\\nMetadata saved to `{}`.\", metadata_path));\n }\n if let Some(revised_prompt) = revised_prompt.filter(|prompt| !prompt.trim().is_empty()) {\n summary.push_str(\"\\n\\nRevised prompt:\\n\");\n summary.push_str(revised_prompt.trim());\n }\n summary\n}\n\npub fn generated_image_visual_context_blocks(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> Option> {\n let path_ref = Path::new(path);\n let metadata = std::fs::metadata(path_ref).ok()?;\n if !metadata.is_file() || metadata.len() > GENERATED_IMAGE_MAX_AUTO_VISION_BYTES {\n return None;\n }\n\n let data = std::fs::read(path_ref).ok()?;\n let media_type = generated_image_media_type(path_ref, output_format).to_string();\n let data_b64 = base64::engine::general_purpose::STANDARD.encode(data);\n let mut reminder = format!(\n \"\\nA provider-native image generation call created `{}`. Jcode attached the image pixels as visual context for future turns because the active provider supports image input and the file is under the safe {} MB limit.\\nFormat: {}\",\n path,\n GENERATED_IMAGE_MAX_AUTO_VISION_BYTES / 1024 / 1024,\n output_format,\n );\n if let Some(metadata_path) = metadata_path.filter(|value| !value.trim().is_empty()) {\n reminder.push_str(&format!(\"\\nMetadata: {}\", metadata_path));\n }\n if let Some(revised_prompt) = revised_prompt.filter(|value| !value.trim().is_empty()) {\n reminder.push_str(\"\\nRevised prompt:\\n\");\n reminder.push_str(revised_prompt.trim());\n }\n reminder.push_str(\"\\n\");\n\n Some(vec![\n ContentBlock::Text {\n text: reminder,\n cache_control: None,\n },\n ContentBlock::Image {\n media_type,\n data: data_b64,\n },\n ])\n}\n\nfn generated_image_media_type(path: &Path, output_format: &str) -> &'static str {\n let ext = path\n .extension()\n .and_then(|value| value.to_str())\n .unwrap_or(output_format)\n .to_ascii_lowercase();\n match ext.as_str() {\n \"jpg\" | \"jpeg\" => \"image/jpeg\",\n \"webp\" => \"image/webp\",\n \"gif\" => \"image/gif\",\n \"bmp\" => \"image/bmp\",\n \"ico\" => \"image/x-icon\",\n _ => \"image/png\",\n }\n}\n\n#[cfg(test)]\nmod tests;\n"}, "b9d671f95571e3f7d7117d53745232afcd358d58": {"t": 1785756945.8309247, "n": 9956, "h": "a09ddcdec09b8302", "c": "use crate::bus::{BackgroundTaskCompleted, BackgroundTaskProgressEvent, BackgroundTaskStatus};\nuse base64::Engine as _;\nuse regex::Regex;\nuse std::collections::HashSet;\nuse std::path::Path;\nuse std::sync::OnceLock;\n\npub use gcode_message_types::{\n CacheControl, ConnectionPhase, ContentBlock, InputShellResult, Message, Role, StreamEvent,\n TOOL_OUTPUT_MISSING_TEXT, ToolCall, ToolDefinition, ends_with_fresh_user_turn,\n extend_stable_hash, messages_with_dynamic_system_context, sanitize_tool_id,\n stable_message_hash,\n};\n\nmod notifications;\n\npub use notifications::{\n ParsedBackgroundTaskNotification, ParsedBackgroundTaskProgressNotification,\n background_task_display_label, background_task_status_notice,\n format_background_task_notification_markdown, format_background_task_progress_markdown,\n format_input_shell_result_markdown, input_shell_status_notice,\n parse_background_task_notification_markdown,\n parse_background_task_progress_notification_markdown,\n};\n\nfn compile_static_regex(pattern: &str) -> Option {\n match Regex::new(pattern) {\n Ok(regex) => Some(regex),\n Err(err) => {\n eprintln!(\"gcode: failed to compile static regex: {err}\");\n None\n }\n }\n}\n\nfn compile_static_regexes(patterns: &[&str]) -> Vec {\n patterns\n .iter()\n .filter_map(|pattern| compile_static_regex(pattern))\n .collect()\n}\n\n/// Redact likely secrets from persisted tool output.\n///\n/// This is a best-effort safeguard for local session history files. It targets\n/// high-confidence token/key patterns and common `KEY=VALUE` assignments used by\n/// auth flows.\npub fn redact_secrets(text: &str) -> String {\n // Fast path to avoid regex work for most tool outputs.\n let lower = text.to_ascii_lowercase();\n\n if !text.contains(\"sk-\")\n && !text.contains(\"ghp_\")\n && !text.contains(\"github_pat_\")\n && !text.contains(\"AIza\")\n && !text.contains(\"ya29.\")\n && !text.contains(\"xox\")\n && !lower.contains(\"api_key\")\n && !lower.contains(\"token\")\n {\n return text.to_string();\n }\n\n static DIRECT_PATTERNS: OnceLock> = OnceLock::new();\n static ASSIGNMENT_PATTERNS: OnceLock> = OnceLock::new();\n\n let direct_patterns = DIRECT_PATTERNS.get_or_init(|| {\n compile_static_regexes(&[\n r\"sk-ant-(?:oat|ort)01-[A-Za-z0-9_-]{20,}\",\n r\"sk-or-v1-[A-Za-z0-9_-]{20,}\",\n r\"ghp_[A-Za-z0-9]{20,}\",\n r\"github_pat_[A-Za-z0-9_]{20,}\",\n r\"ya29\\.[A-Za-z0-9._-]{20,}\",\n r\"AIza[0-9A-Za-z_-]{20,}\",\n r\"xox[baprs]-[A-Za-z0-9-]{10,}\",\n ])\n });\n\n let assignment_patterns = ASSIGNMENT_PATTERNS.get_or_init(|| {\n compile_static_regexes(&[\n r\"(?m)^\\s*(OPENROUTER_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENCODE_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENCODE_GO_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ZHIPU_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ZAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(302AI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(BASETEN_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CORTECS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(DEEPSEEK_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(FIRMWARE_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(HF_TOKEN\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(MOONSHOT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(NEBIUS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(SCALEWAY_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(STACKIT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(GROQ_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(MISTRAL_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(PERPLEXITY_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(TOGETHER_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(DEEPINFRA_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(XAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(LMSTUDIO_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OLLAMA_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CHUTES_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CEREBRAS_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENAI_COMPAT_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(ANTHROPIC_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(OPENAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(AZURE_OPENAI_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(CURSOR_API_KEY\\s*=\\s*)[^\\r\\n]+\",\n r\"(?m)^\\s*(GITHUB_TOKEN\\s*=\\s*)[^\\r\\n]+\",\n ])\n });\n\n let mut redacted = text.to_string();\n let mut redacted_keys: HashSet = [\n \"OPENROUTER_API_KEY\",\n \"OPENCODE_API_KEY\",\n \"OPENCODE_GO_API_KEY\",\n \"ZHIPU_API_KEY\",\n \"ZAI_API_KEY\",\n \"302AI_API_KEY\",\n \"BASETEN_API_KEY\",\n \"CORTECS_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"FIRMWARE_API_KEY\",\n \"HF_TOKEN\",\n \"MOONSHOT_API_KEY\",\n \"NEBIUS_API_KEY\",\n \"SCALEWAY_API_KEY\",\n \"STACKIT_API_KEY\",\n \"GROQ_API_KEY\",\n \"MISTRAL_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"TOGETHER_API_KEY\",\n \"DEEPINFRA_API_KEY\",\n \"XAI_API_KEY\",\n \"LMSTUDIO_API_KEY\",\n \"OLLAMA_API_KEY\",\n \"CHUTES_API_KEY\",\n \"CEREBRAS_API_KEY\",\n \"OPENAI_COMPAT_API_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"OPENAI_API_KEY\",\n \"AZURE_OPENAI_API_KEY\",\n \"CURSOR_API_KEY\",\n \"GITHUB_TOKEN\",\n ]\n .iter()\n .map(|k| (*k).to_string())\n .collect();\n\n for re in direct_patterns {\n redacted = re.replace_all(&redacted, \"[REDACTED_SECRET]\").into_owned();\n }\n\n for re in assignment_patterns {\n redacted = re\n .replace_all(&redacted, \"${1}[REDACTED_SECRET]\")\n .into_owned();\n }\n\n // Also redact custom API key variable names configured at runtime.\n for source in [\n \"GCODE_OPENROUTER_API_KEY_NAME\",\n \"GCODE_OPENAI_COMPAT_API_KEY_NAME\",\n ] {\n let Some(key_name) = std::env::var(source)\n .ok()\n .map(|v| v.trim().to_string())\n .filter(|v| !v.is_empty())\n else {\n continue;\n };\n\n if !key_name\n .chars()\n .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')\n {\n continue;\n }\n if !redacted_keys.insert(key_name.clone()) {\n continue;\n }\n\n let pattern = format!(r\"(?m)^\\s*({}\\s*=\\s*)[^\\r\\n]+\", regex::escape(&key_name));\n if let Ok(re) = Regex::new(&pattern) {\n redacted = re\n .replace_all(&redacted, \"${1}[REDACTED_SECRET]\")\n .into_owned();\n }\n }\n\n redacted\n}\n\npub const GENERATED_IMAGE_TOOL_NAME: &str = \"image_generation\";\npub const GENERATED_IMAGE_MAX_AUTO_VISION_BYTES: u64 = 20 * 1024 * 1024;\n\npub fn generated_image_tool_input(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> serde_json::Value {\n serde_json::json!({\n \"path\": path,\n \"metadata_path\": metadata_path,\n \"output_format\": output_format,\n \"revised_prompt\": revised_prompt,\n })\n}\n\npub fn generated_image_summary(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> String {\n let mut summary = format!(\"Generated image ({}) saved to `{}`.\", output_format, path);\n if let Some(metadata_path) = metadata_path {\n summary.push_str(&format!(\"\\nMetadata saved to `{}`.\", metadata_path));\n }\n if let Some(revised_prompt) = revised_prompt.filter(|prompt| !prompt.trim().is_empty()) {\n summary.push_str(\"\\n\\nRevised prompt:\\n\");\n summary.push_str(revised_prompt.trim());\n }\n summary\n}\n\npub fn generated_image_visual_context_blocks(\n path: &str,\n metadata_path: Option<&str>,\n output_format: &str,\n revised_prompt: Option<&str>,\n) -> Option> {\n let path_ref = Path::new(path);\n let metadata = std::fs::metadata(path_ref).ok()?;\n if !metadata.is_file() || metadata.len() > GENERATED_IMAGE_MAX_AUTO_VISION_BYTES {\n return None;\n }\n\n let data = std::fs::read(path_ref).ok()?;\n let media_type = generated_image_media_type(path_ref, output_format).to_string();\n let data_b64 = base64::engine::general_purpose::STANDARD.encode(data);\n let mut reminder = format!(\n \"\\nA provider-native image generation call created `{}`. Jcode attached the image pixels as visual context for future turns because the active provider supports image input and the file is under the safe {} MB limit.\\nFormat: {}\",\n path,\n GENERATED_IMAGE_MAX_AUTO_VISION_BYTES / 1024 / 1024,\n output_format,\n );\n if let Some(metadata_path) = metadata_path.filter(|value| !value.trim().is_empty()) {\n reminder.push_str(&format!(\"\\nMetadata: {}\", metadata_path));\n }\n if let Some(revised_prompt) = revised_prompt.filter(|value| !value.trim().is_empty()) {\n reminder.push_str(\"\\nRevised prompt:\\n\");\n reminder.push_str(revised_prompt.trim());\n }\n reminder.push_str(\"\\n\");\n\n Some(vec![\n ContentBlock::Text {\n text: reminder,\n cache_control: None,\n },\n ContentBlock::Image {\n media_type,\n data: data_b64,\n },\n ])\n}\n\nfn generated_image_media_type(path: &Path, output_format: &str) -> &'static str {\n let ext = path\n .extension()\n .and_then(|value| value.to_str())\n .unwrap_or(output_format)\n .to_ascii_lowercase();\n match ext.as_str() {\n \"jpg\" | \"jpeg\" => \"image/jpeg\",\n \"webp\" => \"image/webp\",\n \"gif\" => \"image/gif\",\n \"bmp\" => \"image/bmp\",\n \"ico\" => \"image/x-icon\",\n _ => \"image/png\",\n }\n}\n\n#[cfg(test)]\nmod tests;\n"}}, "crates/gcode-provider-metadata/src/lib.rs": {"009beba7598882f4ba1e167a9b4ed98882579a7f": {"t": 1785745850.9399478, "n": 50284, "h": "d2b2d0f67f0ac2e4", "c": "#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderAuthKind {\n OAuth,\n ApiKey,\n DeviceCode,\n Cli,\n Hybrid,\n Local,\n}\n\nimpl LoginProviderAuthKind {\n pub fn label(self) -> &'static str {\n match self {\n Self::OAuth => \"OAuth\",\n Self::ApiKey => \"API key\",\n Self::DeviceCode => \"device code\",\n Self::Cli => \"CLI\",\n Self::Hybrid => \"API key / CLI\",\n Self::Local => \"local endpoint\",\n }\n }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderTarget {\n AutoImport,\n Jcode,\n Claude,\n OpenAi,\n OpenAiApiKey,\n OpenRouter,\n Azure,\n OpenAiCompatible(OpenAiCompatibleProfile),\n Cursor,\n Copilot,\n Gemini,\n Antigravity,\n Google,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderAuthStateKey {\n ExternalImport,\n Jcode,\n Anthropic,\n OpenAi,\n Azure,\n OpenRouterLike,\n Copilot,\n Gemini,\n Antigravity,\n Cursor,\n Google,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderSurface {\n CliLogin,\n TuiLogin,\n ServerBootstrap,\n AutoInit,\n AuthStatus,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct LoginProviderSurfaceOrder {\n pub cli_login: Option,\n pub tui_login: Option,\n pub server_bootstrap: Option,\n pub auto_init: Option,\n pub auth_status: Option,\n}\n\nimpl LoginProviderSurfaceOrder {\n pub const fn new(\n cli_login: Option,\n tui_login: Option,\n server_bootstrap: Option,\n auto_init: Option,\n auth_status: Option,\n ) -> Self {\n Self {\n cli_login,\n tui_login,\n server_bootstrap,\n auto_init,\n auth_status,\n }\n }\n\n pub const fn for_surface(self, surface: LoginProviderSurface) -> Option {\n match surface {\n LoginProviderSurface::CliLogin => self.cli_login,\n LoginProviderSurface::TuiLogin => self.tui_login,\n LoginProviderSurface::ServerBootstrap => self.server_bootstrap,\n LoginProviderSurface::AutoInit => self.auto_init,\n LoginProviderSurface::AuthStatus => self.auth_status,\n }\n }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct LoginProviderDescriptor {\n pub id: &'static str,\n pub display_name: &'static str,\n pub auth_kind: LoginProviderAuthKind,\n pub auth_state_key: LoginProviderAuthStateKey,\n pub auth_status_method: &'static str,\n pub aliases: &'static [&'static str],\n pub menu_detail: &'static str,\n pub recommended: bool,\n pub target: LoginProviderTarget,\n pub order: LoginProviderSurfaceOrder,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct OpenAiCompatibleProfile {\n pub id: &'static str,\n pub display_name: &'static str,\n pub api_base: &'static str,\n pub api_key_env: &'static str,\n pub env_file: &'static str,\n pub setup_url: &'static str,\n pub default_model: Option<&'static str>,\n pub requires_api_key: bool,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ResolvedOpenAiCompatibleProfile {\n pub id: String,\n pub display_name: String,\n pub api_base: String,\n pub api_key_env: String,\n pub env_file: String,\n pub setup_url: String,\n pub default_model: Option,\n pub requires_api_key: bool,\n}\n\npub const OPENCODE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"opencode\",\n display_name: \"OpenCode Zen\",\n api_base: \"https://opencode.ai/zen/v1\",\n api_key_env: \"OPENCODE_API_KEY\",\n env_file: \"opencode.env\",\n setup_url: \"https://opencode.ai/docs/providers#opencode-zen\",\n default_model: Some(\"qwen/qwen3-coder-plus\"),\n requires_api_key: true,\n};\n\npub const OPENCODE_GO_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"opencode-go\",\n display_name: \"OpenCode Go\",\n api_base: \"https://opencode.ai/zen/go/v1\",\n api_key_env: \"OPENCODE_GO_API_KEY\",\n env_file: \"opencode-go.env\",\n setup_url: \"https://opencode.ai/docs/providers#opencode-go\",\n default_model: Some(\"THUDM/GLM-4.5\"),\n requires_api_key: true,\n};\n\npub const ZAI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"zai\",\n display_name: \"Z.AI\",\n api_base: \"https://api.z.ai/api/coding/paas/v4\",\n api_key_env: \"ZHIPU_API_KEY\",\n env_file: \"zai.env\",\n setup_url: \"https://docs.z.ai/guides/develop/openai/introduction\",\n default_model: Some(\"glm-4.5\"),\n requires_api_key: true,\n};\n\npub const KIMI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"kimi\",\n display_name: \"Kimi Code\",\n api_base: \"https://api.kimi.com/coding/v1\",\n api_key_env: \"KIMI_API_KEY\",\n env_file: \"kimi.env\",\n setup_url: \"https://www.kimi.com/coding/docs/en/more/third-party-agents.html\",\n default_model: Some(\"kimi-for-coding\"),\n requires_api_key: true,\n};\n\npub const AI302_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"302ai\",\n display_name: \"302.AI\",\n api_base: \"https://api.302.ai/v1\",\n api_key_env: \"302AI_API_KEY\",\n env_file: \"302ai.env\",\n setup_url: \"https://opencode.ai/docs/providers#302ai\",\n default_model: Some(\"qwen3-235b-a22b-instruct-2507\"),\n requires_api_key: true,\n};\n\npub const BASETEN_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"baseten\",\n display_name: \"Baseten\",\n api_base: \"https://inference.baseten.co/v1\",\n api_key_env: \"BASETEN_API_KEY\",\n env_file: \"baseten.env\",\n setup_url: \"https://opencode.ai/docs/providers#baseten\",\n default_model: Some(\"zai-org/GLM-4.7\"),\n requires_api_key: true,\n};\n\npub const CORTECS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"cortecs\",\n display_name: \"Cortecs\",\n api_base: \"https://api.cortecs.ai/v1\",\n api_key_env: \"CORTECS_API_KEY\",\n env_file: \"cortecs.env\",\n setup_url: \"https://opencode.ai/docs/providers#cortecs\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const DEEPSEEK_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"deepseek\",\n display_name: \"DeepSeek\",\n api_base: \"https://api.deepseek.com\",\n api_key_env: \"DEEPSEEK_API_KEY\",\n env_file: \"deepseek.env\",\n setup_url: \"https://api-docs.deepseek.com/\",\n default_model: Some(\"deepseek-v4-flash\"),\n requires_api_key: true,\n};\n\npub const COMTEGRA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"comtegra\",\n display_name: \"Comtegra GPU Cloud\",\n api_base: \"https://llm.comtegra.cloud/v1\",\n api_key_env: \"COMTEGRA_API_KEY\",\n env_file: \"comtegra.env\",\n setup_url: \"https://docs.cgc.comtegra.cloud/llm-api\",\n default_model: Some(\"glm-51-nvfp4\"),\n requires_api_key: true,\n};\n\npub const FIRMWARE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"firmware\",\n display_name: \"Firmware\",\n api_base: \"https://app.frogbot.ai/api/v1\",\n api_key_env: \"FIRMWARE_API_KEY\",\n env_file: \"firmware.env\",\n setup_url: \"https://opencode.ai/docs/providers#firmware\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const HUGGING_FACE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"huggingface\",\n display_name: \"Hugging Face\",\n api_base: \"https://router.huggingface.co/v1\",\n api_key_env: \"HF_TOKEN\",\n env_file: \"huggingface.env\",\n setup_url: \"https://opencode.ai/docs/providers#hugging-face\",\n default_model: Some(\"zai-org/GLM-4.7\"),\n requires_api_key: true,\n};\n\npub const MOONSHOT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"moonshotai\",\n display_name: \"Moonshot AI\",\n api_base: \"https://api.moonshot.ai/v1\",\n api_key_env: \"MOONSHOT_API_KEY\",\n env_file: \"moonshotai.env\",\n setup_url: \"https://opencode.ai/docs/providers#moonshot-ai\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const NEBIUS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"nebius\",\n display_name: \"Nebius Token Factory\",\n api_base: \"https://api.tokenfactory.nebius.com/v1\",\n api_key_env: \"NEBIUS_API_KEY\",\n env_file: \"nebius.env\",\n setup_url: \"https://opencode.ai/docs/providers#nebius-token-factory\",\n default_model: Some(\"openai/gpt-oss-120b\"),\n requires_api_key: true,\n};\n\npub const SCALEWAY_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"scaleway\",\n display_name: \"Scaleway\",\n api_base: \"https://api.scaleway.ai/v1\",\n api_key_env: \"SCALEWAY_API_KEY\",\n env_file: \"scaleway.env\",\n setup_url: \"https://opencode.ai/docs/providers#scaleway\",\n default_model: Some(\"qwen3-coder-30b-a3b-instruct\"),\n requires_api_key: true,\n};\n\npub const STACKIT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"stackit\",\n display_name: \"STACKIT\",\n api_base: \"https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1\",\n api_key_env: \"STACKIT_API_KEY\",\n env_file: \"stackit.env\",\n setup_url: \"https://opencode.ai/docs/providers#stackit\",\n default_model: Some(\"openai/gpt-oss-120b\"),\n requires_api_key: true,\n};\n\npub const GROQ_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"groq\",\n display_name: \"Groq\",\n api_base: \"https://api.groq.com/openai/v1\",\n api_key_env: \"GROQ_API_KEY\",\n env_file: \"groq.env\",\n setup_url: \"https://console.groq.com/docs/openai\",\n default_model: Some(\"llama-3.1-8b-instant\"),\n requires_api_key: true,\n};\n\npub const MISTRAL_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"mistral\",\n display_name: \"Mistral\",\n api_base: \"https://api.mistral.ai/v1\",\n api_key_env: \"MISTRAL_API_KEY\",\n env_file: \"mistral.env\",\n setup_url: \"https://docs.mistral.ai/getting-started/models/\",\n default_model: Some(\"devstral-medium-2507\"),\n requires_api_key: true,\n};\n\npub const PERPLEXITY_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"perplexity\",\n display_name: \"Perplexity\",\n api_base: \"https://api.perplexity.ai\",\n api_key_env: \"PERPLEXITY_API_KEY\",\n env_file: \"perplexity.env\",\n setup_url: \"https://docs.perplexity.ai/docs/agent-api/openai-compatibility\",\n default_model: Some(\"sonar\"),\n requires_api_key: true,\n};\n\npub const TOGETHER_AI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"togetherai\",\n display_name: \"Together AI\",\n api_base: \"https://api.together.xyz/v1\",\n api_key_env: \"TOGETHER_API_KEY\",\n env_file: \"togetherai.env\",\n setup_url: \"https://docs.together.ai/docs/openai-api-compatibility\",\n default_model: Some(\"moonshotai/Kimi-K2-Instruct\"),\n requires_api_key: true,\n};\n\npub const DEEPINFRA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"deepinfra\",\n display_name: \"Deep Infra\",\n api_base: \"https://api.deepinfra.com/v1/openai\",\n api_key_env: \"DEEPINFRA_API_KEY\",\n env_file: \"deepinfra.env\",\n setup_url: \"https://deepinfra.com/docs/api-reference\",\n default_model: Some(\"moonshotai/Kimi-K2-Instruct\"),\n requires_api_key: true,\n};\n\npub const FIREWORKS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"fireworks\",\n display_name: \"Fireworks\",\n api_base: \"https://api.fireworks.ai/inference/v1\",\n api_key_env: \"FIREWORKS_API_KEY\",\n env_file: \"fireworks.env\",\n setup_url: \"https://docs.fireworks.ai/tools-sdks/openai-compatibility\",\n default_model: Some(\"accounts/fireworks/routers/kimi-k2p5-turbo\"),\n requires_api_key: true,\n};\n\npub const MINIMAX_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"minimax\",\n display_name: \"MiniMax\",\n api_base: \"https://api.minimaxi.com/v1\",\n api_key_env: \"OPENAI_API_KEY\",\n env_file: \"minimax.env\",\n setup_url: \"https://platform.minimax.io/docs/api-reference/text-openai-api\",\n default_model: Some(\"MiniMax-M2.7\"),\n requires_api_key: true,\n};\n\npub const XAI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"xai\",\n display_name: \"xAI\",\n api_base: \"https://api.x.ai/v1\",\n api_key_env: \"XAI_API_KEY\",\n env_file: \"xai.env\",\n setup_url: \"https://docs.x.ai/developers/quickstart\",\n default_model: Some(\"grok-code-fast-1\"),\n requires_api_key: true,\n};\n\npub const LMSTUDIO_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"lmstudio\",\n display_name: \"LM Studio\",\n api_base: \"http://localhost:1234/v1\",\n api_key_env: \"LMSTUDIO_API_KEY\",\n env_file: \"lmstudio.env\",\n setup_url: \"https://lmstudio.ai/docs/app/api/endpoints/openai\",\n default_model: None,\n requires_api_key: false,\n};\n\npub const OLLAMA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"ollama\",\n display_name: \"Ollama\",\n api_base: \"http://localhost:11434/v1\",\n api_key_env: \"OLLAMA_API_KEY\",\n env_file: \"ollama.env\",\n setup_url: \"https://ollama.com/blog/openai-compatibility\",\n default_model: None,\n requires_api_key: false,\n};\n\npub const CHUTES_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"chutes\",\n display_name: \"Chutes\",\n api_base: \"https://llm.chutes.ai/v1\",\n api_key_env: \"CHUTES_API_KEY\",\n env_file: \"chutes.env\",\n setup_url: \"https://chutes.ai\",\n default_model: Some(\"Qwen/Qwen3-Coder-480B-A35B-Instruct\"),\n requires_api_key: true,\n};\n\npub const CEREBRAS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"cerebras\",\n display_name: \"Cerebras\",\n api_base: \"https://api.cerebras.ai/v1\",\n api_key_env: \"CEREBRAS_API_KEY\",\n env_file: \"cerebras.env\",\n setup_url: \"https://inference-docs.cerebras.ai/introduction\",\n default_model: Some(\"qwen-3-coder-480b\"),\n requires_api_key: true,\n};\n\npub const ALIBABA_CODING_PLAN_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"alibaba-coding-plan\",\n display_name: \"Alibaba Cloud Coding Plan\",\n api_base: \"https://coding-intl.dashscope.aliyuncs.com/v1\",\n api_key_env: \"BAILIAN_CODING_PLAN_API_KEY\",\n env_file: \"alibaba-coding-plan.env\",\n setup_url: \"https://www.alibabacloud.com/help/en/model-studio/coding-plan-quickstart\",\n default_model: Some(\"qwen3-coder-plus\"),\n requires_api_key: true,\n};\n\npub const OPENAI_COMPAT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"openai-compatible\",\n display_name: \"OpenAI-compatible\",\n api_base: \"https://api.openai.com/v1\",\n api_key_env: \"OPENAI_COMPAT_API_KEY\",\n env_file: \"openai-compatible.env\",\n setup_url: \"https://github.com/bitan-del/gcode-harness#openai-compatible-providers\",\n default_model: None,\n requires_api_key: true,\n};\n\nconst OPENAI_COMPAT_PROFILES: [OpenAiCompatibleProfile; 29] = [\n OPENCODE_PROFILE,\n OPENCODE_GO_PROFILE,\n ZAI_PROFILE,\n KIMI_PROFILE,\n CHUTES_PROFILE,\n CEREBRAS_PROFILE,\n ALIBABA_CODING_PLAN_PROFILE,\n AI302_PROFILE,\n BASETEN_PROFILE,\n CORTECS_PROFILE,\n DEEPSEEK_PROFILE,\n COMTEGRA_PROFILE,\n FIRMWARE_PROFILE,\n HUGGING_FACE_PROFILE,\n MOONSHOT_PROFILE,\n NEBIUS_PROFILE,\n SCALEWAY_PROFILE,\n STACKIT_PROFILE,\n GROQ_PROFILE,\n MISTRAL_PROFILE,\n PERPLEXITY_PROFILE,\n TOGETHER_AI_PROFILE,\n DEEPINFRA_PROFILE,\n FIREWORKS_PROFILE,\n MINIMAX_PROFILE,\n XAI_PROFILE,\n LMSTUDIO_PROFILE,\n OLLAMA_PROFILE,\n OPENAI_COMPAT_PROFILE,\n];\n\npub const CLAUDE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"claude\",\n display_name: \"Anthropic/Claude\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Anthropic,\n auth_status_method: \"OAuth / API key\",\n aliases: &[\"anthropic\"],\n menu_detail: \"requires Claude Pro or Max subscription\",\n recommended: true,\n target: LoginProviderTarget::Claude,\n order: LoginProviderSurfaceOrder::new(Some(1), Some(1), Some(1), Some(1), Some(1)),\n};\n\npub const AUTO_IMPORT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"auto-import\",\n display_name: \"Auto Import\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::ExternalImport,\n auth_status_method: \"Reuse detected logins\",\n aliases: &[\"import\", \"reuse\", \"autoimport\"],\n menu_detail: \"review and reuse logins from other tools\",\n recommended: false,\n target: LoginProviderTarget::AutoImport,\n order: LoginProviderSurfaceOrder::new(Some(1), Some(1), None, None, None),\n};\n\npub const GCODE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"gcode\",\n display_name: \"Jcode Subscription\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::Jcode,\n auth_status_method: \"API key\",\n aliases: &[\"subscription\", \"gcode-subscription\"],\n menu_detail: \"curated gcode subscription models\",\n recommended: false,\n target: LoginProviderTarget::Jcode,\n order: LoginProviderSurfaceOrder::new(Some(3), Some(3), Some(3), Some(3), Some(3)),\n};\n\npub const OPENAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai\",\n display_name: \"OpenAI\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::OpenAi,\n auth_status_method: \"OAuth / API key\",\n aliases: &[],\n menu_detail: \"requires ChatGPT Plus or Pro subscription\",\n recommended: true,\n target: LoginProviderTarget::OpenAi,\n order: LoginProviderSurfaceOrder::new(Some(2), Some(2), Some(2), Some(2), Some(2)),\n};\n\npub const OPENAI_API_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai-api\",\n display_name: \"OpenAI API\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenAi,\n auth_status_method: \"API key\",\n aliases: &[\n \"openai-key\",\n \"openai-apikey\",\n \"openai-platform\",\n \"platform-openai\",\n ],\n menu_detail: \"native OpenAI API key, pay-per-token\",\n recommended: false,\n target: LoginProviderTarget::OpenAiApiKey,\n order: LoginProviderSurfaceOrder::new(Some(99), Some(99), Some(99), Some(99), Some(99)),\n};\n\npub const OPENROUTER_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openrouter\",\n display_name: \"OpenRouter\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key, pay-per-token, 200+ models\",\n recommended: false,\n target: LoginProviderTarget::OpenRouter,\n order: LoginProviderSurfaceOrder::new(Some(4), Some(3), Some(4), Some(3), Some(3)),\n};\n\npub const AZURE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"azure\",\n display_name: \"Azure OpenAI\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::Azure,\n auth_status_method: \"Entra ID / API key\",\n aliases: &[\"azure-openai\", \"azure_openai\", \"aoai\"],\n menu_detail: \"Microsoft Entra ID or Azure OpenAI API key\",\n recommended: false,\n target: LoginProviderTarget::Azure,\n order: LoginProviderSurfaceOrder::new(Some(5), None, None, None, Some(4)),\n};\n\npub const OPENCODE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"opencode\",\n display_name: \"OpenCode Zen\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"opencode-zen\", \"zen\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENCODE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(5), Some(4), Some(5), Some(4), Some(4)),\n};\n\npub const OPENCODE_GO_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"opencode-go\",\n display_name: \"OpenCode Go\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"opencodego\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENCODE_GO_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(6), Some(5), Some(6), Some(5), Some(5)),\n};\n\npub const ZAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"zai\",\n display_name: \"Z.AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"z.ai\", \"z-ai\", \"zai-coding\", \"zhipu\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(ZAI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(7), Some(6), Some(7), Some(6), Some(6)),\n};\n\npub const KIMI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"kimi\",\n display_name: \"Kimi Code\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\n \"kimi-code\",\n \"kimi-coding\",\n \"kimi-coding-plan\",\n \"kimi-for-coding\",\n \"moonshot-coding\",\n ],\n menu_detail: \"API key, dedicated Kimi coding endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(KIMI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(36), Some(36), Some(36), Some(36), Some(36)),\n};\n\npub const CHUTES_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"chutes\",\n display_name: \"Chutes\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CHUTES_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(8), Some(7), Some(8), Some(7), Some(7)),\n};\n\npub const CEREBRAS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cerebras\",\n display_name: \"Cerebras\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"cerebrascode\", \"cerberascode\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CEREBRAS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(9), Some(8), Some(9), Some(8), Some(8)),\n};\n\npub const ALIBABA_CODING_PLAN_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"alibaba-coding-plan\",\n display_name: \"Alibaba Cloud Coding Plan\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"bailian\", \"aliyun-bailian\", \"coding-plan\", \"alibaba-coding\"],\n menu_detail: \"API key, dedicated Alibaba coding endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(ALIBABA_CODING_PLAN_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(10), Some(9), Some(10), Some(9), Some(9)),\n};\n\npub const AI302_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"302ai\",\n display_name: \"302.AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"302.ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(AI302_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(18), Some(18), Some(18), Some(18), Some(18)),\n};\n\npub const BASETEN_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"baseten\",\n display_name: \"Baseten\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(BASETEN_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(19), Some(19), Some(19), Some(19), Some(19)),\n};\n\npub const CORTECS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cortecs\",\n display_name: \"Cortecs\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CORTECS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(20), Some(20), Some(20), Some(20), Some(20)),\n};\n\npub const DEEPSEEK_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"deepseek\",\n display_name: \"DeepSeek\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(DEEPSEEK_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(21), Some(21), Some(21), Some(21), Some(21)),\n};\n\npub const COMTEGRA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"comtegra\",\n display_name: \"Comtegra GPU Cloud\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"cgc\", \"comtegra-gpu-cloud\"],\n menu_detail: \"OpenAI-compatible LLM API\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(COMTEGRA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(22), Some(22), Some(22), Some(22), Some(22)),\n};\n\npub const FIRMWARE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"firmware\",\n display_name: \"Firmware\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(FIRMWARE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(22), Some(22), Some(22), Some(22), Some(22)),\n};\n\npub const HUGGING_FACE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"huggingface\",\n display_name: \"Hugging Face\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"hugging-face\", \"hf\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(HUGGING_FACE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(23), Some(23), Some(23), Some(23), Some(23)),\n};\n\npub const MOONSHOT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"moonshotai\",\n display_name: \"Moonshot AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"moonshot\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MOONSHOT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(24), Some(24), Some(24), Some(24), Some(24)),\n};\n\npub const NEBIUS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"nebius\",\n display_name: \"Nebius Token Factory\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(NEBIUS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(25), Some(25), Some(25), Some(25), Some(25)),\n};\n\npub const SCALEWAY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"scaleway\",\n display_name: \"Scaleway\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(SCALEWAY_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(26), Some(26), Some(26), Some(26), Some(26)),\n};\n\npub const STACKIT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"stackit\",\n display_name: \"STACKIT\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(STACKIT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(27), Some(27), Some(27), Some(27), Some(27)),\n};\n\npub const GROQ_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"groq\",\n display_name: \"Groq\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(GROQ_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(28), Some(28), Some(28), Some(28), Some(28)),\n};\n\npub const MISTRAL_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"mistral\",\n display_name: \"Mistral\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"mistralai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MISTRAL_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(29), Some(29), Some(29), Some(29), Some(29)),\n};\n\npub const PERPLEXITY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"perplexity\",\n display_name: \"Perplexity\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"pplx\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(PERPLEXITY_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(30), Some(30), Some(30), Some(30), Some(30)),\n};\n\npub const TOGETHER_AI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"togetherai\",\n display_name: \"Together AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"together\", \"together-ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(TOGETHER_AI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(31), Some(31), Some(31), Some(31), Some(31)),\n};\n\npub const DEEPINFRA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"deepinfra\",\n display_name: \"Deep Infra\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"deep-infra\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(DEEPINFRA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(32), Some(32), Some(32), Some(32), Some(32)),\n};\n\npub const FIREWORKS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"fireworks\",\n display_name: \"Fireworks\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"fireworks-ai\", \"fireworks.ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(FIREWORKS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(37), Some(37), Some(37), Some(37), Some(37)),\n};\n\npub const MINIMAX_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"minimax\",\n display_name: \"MiniMax\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"minimaxi\", \"minimax-ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MINIMAX_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(38), Some(38), Some(38), Some(38), Some(38)),\n};\n\npub const XAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"xai\",\n display_name: \"xAI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"x.ai\", \"x-ai\", \"grok\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(XAI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(33), Some(33), Some(33), Some(33), Some(33)),\n};\n\npub const LMSTUDIO_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"lmstudio\",\n display_name: \"LM Studio\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"local endpoint\",\n aliases: &[\"lm-studio\"],\n menu_detail: \"local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(LMSTUDIO_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(34), Some(34), Some(34), Some(34), Some(34)),\n};\n\npub const OLLAMA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"ollama\",\n display_name: \"Ollama\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"local endpoint\",\n aliases: &[],\n menu_detail: \"local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OLLAMA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(35), Some(35), Some(35), Some(35), Some(35)),\n};\n\npub const OPENAI_COMPAT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai-compatible\",\n display_name: \"OpenAI-compatible\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key / local endpoint\",\n aliases: &[\"openai_compatible\", \"compat\", \"custom\"],\n menu_detail: \"custom hosted or local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENAI_COMPAT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(10), Some(9), None, None, Some(9)),\n};\n\npub const CURSOR_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cursor\",\n display_name: \"Cursor\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::Cursor,\n auth_status_method: \"API key / CLI\",\n aliases: &[],\n menu_detail: \"browser login or API key\",\n recommended: false,\n target: LoginProviderTarget::Cursor,\n order: LoginProviderSurfaceOrder::new(Some(11), Some(12), None, Some(9), Some(12)),\n};\n\npub const COPILOT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"copilot\",\n display_name: \"GitHub Copilot\",\n auth_kind: LoginProviderAuthKind::DeviceCode,\n auth_state_key: LoginProviderAuthStateKey::Copilot,\n auth_status_method: \"device code\",\n aliases: &[],\n menu_detail: \"GitHub device flow\",\n recommended: false,\n target: LoginProviderTarget::Copilot,\n order: LoginProviderSurfaceOrder::new(Some(3), Some(10), Some(3), Some(10), Some(10)),\n};\n\npub const GEMINI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"gemini\",\n display_name: \"Google Gemini\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Gemini,\n auth_status_method: \"OAuth\",\n aliases: &[],\n menu_detail: \"Google Gemini Code Assist OAuth login\",\n recommended: false,\n target: LoginProviderTarget::Gemini,\n order: LoginProviderSurfaceOrder::new(Some(13), Some(11), Some(4), Some(11), Some(13)),\n};\n\npub const ANTIGRAVITY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"antigravity\",\n display_name: \"Antigravity\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Antigravity,\n auth_status_method: \"OAuth\",\n aliases: &[],\n menu_detail: \"Google Antigravity OAuth login\",\n recommended: false,\n target: LoginProviderTarget::Antigravity,\n order: LoginProviderSurfaceOrder::new(Some(12), Some(12), None, Some(12), Some(12)),\n};\n\npub const GOOGLE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"google\",\n display_name: \"Google/Gmail\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Google,\n auth_status_method: \"OAuth\",\n aliases: &[\"gmail\"],\n menu_detail: \"read, draft, and send emails\",\n recommended: false,\n target: LoginProviderTarget::Google,\n order: LoginProviderSurfaceOrder::new(Some(13), None, None, None, None),\n};\n\nconst LOGIN_PROVIDERS: [LoginProviderDescriptor; 41] = [\n AUTO_IMPORT_LOGIN_PROVIDER,\n CLAUDE_LOGIN_PROVIDER,\n OPENAI_LOGIN_PROVIDER,\n OPENAI_API_LOGIN_PROVIDER,\n GCODE_LOGIN_PROVIDER,\n OPENROUTER_LOGIN_PROVIDER,\n AZURE_LOGIN_PROVIDER,\n OPENCODE_LOGIN_PROVIDER,\n OPENCODE_GO_LOGIN_PROVIDER,\n ZAI_LOGIN_PROVIDER,\n KIMI_LOGIN_PROVIDER,\n CHUTES_LOGIN_PROVIDER,\n CEREBRAS_LOGIN_PROVIDER,\n ALIBABA_CODING_PLAN_LOGIN_PROVIDER,\n AI302_LOGIN_PROVIDER,\n BASETEN_LOGIN_PROVIDER,\n CORTECS_LOGIN_PROVIDER,\n DEEPSEEK_LOGIN_PROVIDER,\n COMTEGRA_LOGIN_PROVIDER,\n FIRMWARE_LOGIN_PROVIDER,\n HUGGING_FACE_LOGIN_PROVIDER,\n MOONSHOT_LOGIN_PROVIDER,\n NEBIUS_LOGIN_PROVIDER,\n SCALEWAY_LOGIN_PROVIDER,\n STACKIT_LOGIN_PROVIDER,\n GROQ_LOGIN_PROVIDER,\n MISTRAL_LOGIN_PROVIDER,\n PERPLEXITY_LOGIN_PROVIDER,\n TOGETHER_AI_LOGIN_PROVIDER,\n DEEPINFRA_LOGIN_PROVIDER,\n FIREWORKS_LOGIN_PROVIDER,\n MINIMAX_LOGIN_PROVIDER,\n XAI_LOGIN_PROVIDER,\n LMSTUDIO_LOGIN_PROVIDER,\n OLLAMA_LOGIN_PROVIDER,\n OPENAI_COMPAT_LOGIN_PROVIDER,\n CURSOR_LOGIN_PROVIDER,\n COPILOT_LOGIN_PROVIDER,\n GEMINI_LOGIN_PROVIDER,\n ANTIGRAVITY_LOGIN_PROVIDER,\n GOOGLE_LOGIN_PROVIDER,\n];\n\npub fn openai_compatible_profiles() -> &'static [OpenAiCompatibleProfile] {\n &OPENAI_COMPAT_PROFILES\n}\n\npub fn login_providers() -> &'static [LoginProviderDescriptor] {\n &LOGIN_PROVIDERS\n}\n\nfn login_providers_for_surface(surface: LoginProviderSurface) -> Vec {\n let mut providers = login_providers()\n .iter()\n .copied()\n .filter(|provider| provider.order.for_surface(surface).is_some())\n .collect::>();\n providers.sort_by_key(|provider| provider.order.for_surface(surface).unwrap_or(u8::MAX));\n providers\n}\n\npub fn cli_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::CliLogin)\n}\n\npub fn tui_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::TuiLogin)\n}\n\npub fn server_bootstrap_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::ServerBootstrap)\n}\n\npub fn auto_init_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::AutoInit)\n}\n\npub fn auth_status_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::AuthStatus)\n}\n\npub fn resolve_login_provider(input: &str) -> Option {\n let normalized = normalize_provider_input(input)?;\n login_providers().iter().copied().find(|provider| {\n provider.id == normalized || provider.aliases.iter().any(|alias| *alias == normalized)\n })\n}\n\npub fn resolve_login_selection(\n input: &str,\n providers: &[LoginProviderDescriptor],\n) -> Option {\n let trimmed = input.trim();\n if let Ok(index) = trimmed.parse::() {\n return index\n .checked_sub(1)\n .and_then(|idx| providers.get(idx))\n .copied();\n }\n\n let provider = resolve_login_provider(trimmed)?;\n providers\n .iter()\n .copied()\n .find(|candidate| candidate.id == provider.id)\n}\n\npub fn is_safe_env_key_name(name: &str) -> bool {\n !name.is_empty()\n && name\n .chars()\n .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')\n}\n\npub fn is_safe_env_file_name(name: &str) -> bool {\n !name.is_empty()\n && !name.contains('/')\n && !name.contains('\\\\')\n && name\n .chars()\n .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')\n}\n\npub fn normalize_api_base(raw: &str) -> Option {\n let trimmed = raw.trim();\n if trimmed.is_empty() {\n return None;\n }\n\n let parsed = url::Url::parse(trimmed).ok()?;\n let scheme = parsed.scheme();\n if scheme != \"https\" && scheme != \"http\" {\n return None;\n }\n\n if scheme == \"http\" {\n let host = parsed.host_str()?;\n if !allows_insecure_http_host(host) {\n return None;\n }\n }\n\n Some(trimmed.trim_end_matches('/').to_string())\n}\n\nfn allows_insecure_http_host(host: &str) -> bool {\n let host = host.trim();\n let host = host\n .strip_prefix('[')\n .and_then(|s| s.strip_suffix(']'))\n .unwrap_or(host);\n if host.eq_ignore_ascii_case(\"localhost\") {\n return true;\n }\n\n if let Ok(ip) = host.parse::() {\n return match ip {\n std::net::IpAddr::V4(v4) => {\n v4.is_loopback() || v4.is_private() || v4.is_link_local() || v4.is_unspecified()\n }\n std::net::IpAddr::V6(v6) => {\n v6.is_loopback()\n || v6.is_unique_local()\n || v6.is_unicast_link_local()\n || v6.is_unspecified()\n }\n };\n }\n\n false\n}\n\nfn normalize_provider_input(input: &str) -> Option {\n let trimmed = input.trim();\n if trimmed.is_empty() {\n return None;\n }\n Some(trimmed.to_ascii_lowercase())\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use std::collections::HashSet;\n\n #[test]\n fn matrix_profiles_have_unique_ids_and_safe_metadata() {\n let mut ids = HashSet::new();\n for profile in openai_compatible_profiles() {\n assert!(\n ids.insert(profile.id),\n \"duplicate provider profile id: {}\",\n profile.id\n );\n assert!(is_safe_env_key_name(profile.api_key_env));\n assert!(is_safe_env_file_name(profile.env_file));\n assert_eq!(\n normalize_api_base(profile.api_base).as_deref(),\n Some(profile.api_base)\n );\n }\n }\n\n #[test]\n fn normalize_api_base_accepts_private_http_hosts() {\n assert_eq!(\n normalize_api_base(\"http://192.168.1.25:8000/v1/\").as_deref(),\n Some(\"http://192.168.1.25:8000/v1\")\n );\n assert_eq!(\n normalize_api_base(\"http://10.0.0.8:11434/v1\").as_deref(),\n Some(\"http://10.0.0.8:11434/v1\")\n );\n assert_eq!(\n normalize_api_base(\"http://[fd00::1]:8080/v1\").as_deref(),\n Some(\"http://[fd00::1]:8080/v1\")\n );\n }\n\n #[test]\n fn normalize_api_base_rejects_public_http_hosts() {\n assert_eq!(normalize_api_base(\"http://example.com/v1\"), None);\n assert_eq!(normalize_api_base(\"http://8.8.8.8/v1\"), None);\n }\n\n #[test]\n fn alibaba_coding_plan_uses_current_international_endpoint() {\n assert_eq!(\n ALIBABA_CODING_PLAN_PROFILE.api_base,\n \"https://coding-intl.dashscope.aliyuncs.com/v1\"\n );\n }\n\n #[test]\n fn minimax_profile_uses_official_openai_compatible_configuration() {\n assert_eq!(MINIMAX_PROFILE.api_base, \"https://api.minimaxi.com/v1\");\n assert_eq!(MINIMAX_PROFILE.api_key_env, \"OPENAI_API_KEY\");\n }\n\n #[test]\n fn matrix_login_provider_aliases_resolve_to_canonical_ids() {\n assert_eq!(\n resolve_login_provider(\"subscription\").map(|provider| provider.id),\n Some(\"gcode\")\n );\n assert_eq!(\n resolve_login_provider(\"anthropic\").map(|provider| provider.id),\n Some(\"claude\")\n );\n assert_eq!(\n resolve_login_provider(\"opencodego\").map(|provider| provider.id),\n Some(\"opencode-go\")\n );\n assert_eq!(\n resolve_login_provider(\"z.ai\").map(|provider| provider.id),\n Some(\"zai\")\n );\n assert_eq!(\n resolve_login_provider(\"zhipu\").map(|provider| provider.id),\n Some(\"zai\")\n );\n assert_eq!(\n resolve_login_provider(\"kimi\").map(|provider| provider.id),\n Some(\"kimi\")\n );\n assert_eq!(\n resolve_login_provider(\"kimi-for-coding\").map(|provider| provider.id),\n Some(\"kimi\")\n );\n assert_eq!(\n resolve_login_provider(\"compat\").map(|provider| provider.id),\n Some(\"openai-compatible\")\n );\n assert_eq!(\n resolve_login_provider(\"aoai\").map(|provider| provider.id),\n Some(\"azure\")\n );\n assert_eq!(\n resolve_login_provider(\"cerberascode\").map(|provider| provider.id),\n Some(\"cerebras\")\n );\n assert_eq!(\n resolve_login_provider(\"bailian\").map(|provider| provider.id),\n Some(\"alibaba-coding-plan\")\n );\n assert_eq!(\n resolve_login_provider(\"302.ai\").map(|provider| provider.id),\n Some(\"302ai\")\n );\n assert_eq!(\n resolve_login_provider(\"hf\").map(|provider| provider.id),\n Some(\"huggingface\")\n );\n assert_eq!(\n resolve_login_provider(\"moonshot\").map(|provider| provider.id),\n Some(\"moonshotai\")\n );\n assert_eq!(\n resolve_login_provider(\"mistralai\").map(|provider| provider.id),\n Some(\"mistral\")\n );\n assert_eq!(\n resolve_login_provider(\"pplx\").map(|provider| provider.id),\n Some(\"perplexity\")\n );\n assert_eq!(\n resolve_login_provider(\"together\").map(|provider| provider.id),\n Some(\"togetherai\")\n );\n assert_eq!(\n resolve_login_provider(\"deep-infra\").map(|provider| provider.id),\n Some(\"deepinfra\")\n );\n assert_eq!(\n resolve_login_provider(\"fireworks.ai\").map(|provider| provider.id),\n Some(\"fireworks\")\n );\n assert_eq!(\n resolve_login_provider(\"minimax-ai\").map(|provider| provider.id),\n Some(\"minimax\")\n );\n assert_eq!(\n resolve_login_provider(\"grok\").map(|provider| provider.id),\n Some(\"xai\")\n );\n assert_eq!(\n resolve_login_provider(\"lm-studio\").map(|provider| provider.id),\n Some(\"lmstudio\")\n );\n assert_eq!(\n resolve_login_provider(\"gmail\").map(|provider| provider.id),\n Some(\"google\")\n );\n }\n\n #[test]\n fn matrix_login_provider_ids_and_aliases_are_unique() {\n let mut seen = HashSet::new();\n for provider in login_providers() {\n assert!(\n seen.insert(provider.id),\n \"duplicate login provider identifier: {}\",\n provider.id\n );\n for alias in provider.aliases {\n assert!(\n seen.insert(*alias),\n \"duplicate login provider alias: {}\",\n alias\n );\n }\n }\n }\n\n #[test]\n fn matrix_tui_login_selection_supports_numbers_and_names() {\n let providers = tui_login_providers();\n assert_eq!(\n resolve_login_selection(\"1\", &providers).map(|provider| provider.id),\n Some(\"auto-import\")\n );\n assert_eq!(\n resolve_login_selection(\"2\", &providers).map(|provider| provider.id),\n Some(\"claude\")\n );\n assert_eq!(\n resolve_login_selection(\"15\", &providers).map(|provider| provider.id),\n Some(\"cursor\")\n );\n assert_eq!(\n resolve_login_selection(\"compat\", &providers).map(|provider| provider.id),\n Some(\"openai-compatible\")\n );\n assert!(resolve_login_selection(\"google\", &providers).is_none());\n }\n\n #[test]\n fn matrix_cli_login_selection_preserves_existing_order() {\n let providers = cli_login_providers();\n assert_eq!(\n resolve_login_selection(\"1\", &providers).map(|provider| provider.id),\n Some(\"auto-import\")\n );\n assert_eq!(\n resolve_login_selection(\"4\", &providers).map(|provider| provider.id),\n Some(\"gcode\")\n );\n assert_eq!(\n resolve_login_selection(\"5\", &providers).map(|provider| provider.id),\n Some(\"copilot\")\n );\n assert_eq!(\n resolve_login_selection(\"6\", &providers).map(|provider| provider.id),\n Some(\"openrouter\")\n );\n assert_eq!(\n resolve_login_selection(\"7\", &providers).map(|provider| provider.id),\n Some(\"azure\")\n );\n assert_eq!(\n resolve_login_selection(\"17\", &providers).map(|provider| provider.id),\n Some(\"gemini\")\n );\n assert_eq!(\n resolve_login_selection(\"18\", &providers).map(|provider| provider.id),\n Some(\"google\")\n );\n }\n}\n"}, "b9d671f95571e3f7d7117d53745232afcd358d58": {"t": 1785756943.706569, "n": 50284, "h": "d2b2d0f67f0ac2e4", "c": "#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderAuthKind {\n OAuth,\n ApiKey,\n DeviceCode,\n Cli,\n Hybrid,\n Local,\n}\n\nimpl LoginProviderAuthKind {\n pub fn label(self) -> &'static str {\n match self {\n Self::OAuth => \"OAuth\",\n Self::ApiKey => \"API key\",\n Self::DeviceCode => \"device code\",\n Self::Cli => \"CLI\",\n Self::Hybrid => \"API key / CLI\",\n Self::Local => \"local endpoint\",\n }\n }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderTarget {\n AutoImport,\n Jcode,\n Claude,\n OpenAi,\n OpenAiApiKey,\n OpenRouter,\n Azure,\n OpenAiCompatible(OpenAiCompatibleProfile),\n Cursor,\n Copilot,\n Gemini,\n Antigravity,\n Google,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderAuthStateKey {\n ExternalImport,\n Jcode,\n Anthropic,\n OpenAi,\n Azure,\n OpenRouterLike,\n Copilot,\n Gemini,\n Antigravity,\n Cursor,\n Google,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum LoginProviderSurface {\n CliLogin,\n TuiLogin,\n ServerBootstrap,\n AutoInit,\n AuthStatus,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct LoginProviderSurfaceOrder {\n pub cli_login: Option,\n pub tui_login: Option,\n pub server_bootstrap: Option,\n pub auto_init: Option,\n pub auth_status: Option,\n}\n\nimpl LoginProviderSurfaceOrder {\n pub const fn new(\n cli_login: Option,\n tui_login: Option,\n server_bootstrap: Option,\n auto_init: Option,\n auth_status: Option,\n ) -> Self {\n Self {\n cli_login,\n tui_login,\n server_bootstrap,\n auto_init,\n auth_status,\n }\n }\n\n pub const fn for_surface(self, surface: LoginProviderSurface) -> Option {\n match surface {\n LoginProviderSurface::CliLogin => self.cli_login,\n LoginProviderSurface::TuiLogin => self.tui_login,\n LoginProviderSurface::ServerBootstrap => self.server_bootstrap,\n LoginProviderSurface::AutoInit => self.auto_init,\n LoginProviderSurface::AuthStatus => self.auth_status,\n }\n }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct LoginProviderDescriptor {\n pub id: &'static str,\n pub display_name: &'static str,\n pub auth_kind: LoginProviderAuthKind,\n pub auth_state_key: LoginProviderAuthStateKey,\n pub auth_status_method: &'static str,\n pub aliases: &'static [&'static str],\n pub menu_detail: &'static str,\n pub recommended: bool,\n pub target: LoginProviderTarget,\n pub order: LoginProviderSurfaceOrder,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct OpenAiCompatibleProfile {\n pub id: &'static str,\n pub display_name: &'static str,\n pub api_base: &'static str,\n pub api_key_env: &'static str,\n pub env_file: &'static str,\n pub setup_url: &'static str,\n pub default_model: Option<&'static str>,\n pub requires_api_key: bool,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ResolvedOpenAiCompatibleProfile {\n pub id: String,\n pub display_name: String,\n pub api_base: String,\n pub api_key_env: String,\n pub env_file: String,\n pub setup_url: String,\n pub default_model: Option,\n pub requires_api_key: bool,\n}\n\npub const OPENCODE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"opencode\",\n display_name: \"OpenCode Zen\",\n api_base: \"https://opencode.ai/zen/v1\",\n api_key_env: \"OPENCODE_API_KEY\",\n env_file: \"opencode.env\",\n setup_url: \"https://opencode.ai/docs/providers#opencode-zen\",\n default_model: Some(\"qwen/qwen3-coder-plus\"),\n requires_api_key: true,\n};\n\npub const OPENCODE_GO_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"opencode-go\",\n display_name: \"OpenCode Go\",\n api_base: \"https://opencode.ai/zen/go/v1\",\n api_key_env: \"OPENCODE_GO_API_KEY\",\n env_file: \"opencode-go.env\",\n setup_url: \"https://opencode.ai/docs/providers#opencode-go\",\n default_model: Some(\"THUDM/GLM-4.5\"),\n requires_api_key: true,\n};\n\npub const ZAI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"zai\",\n display_name: \"Z.AI\",\n api_base: \"https://api.z.ai/api/coding/paas/v4\",\n api_key_env: \"ZHIPU_API_KEY\",\n env_file: \"zai.env\",\n setup_url: \"https://docs.z.ai/guides/develop/openai/introduction\",\n default_model: Some(\"glm-4.5\"),\n requires_api_key: true,\n};\n\npub const KIMI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"kimi\",\n display_name: \"Kimi Code\",\n api_base: \"https://api.kimi.com/coding/v1\",\n api_key_env: \"KIMI_API_KEY\",\n env_file: \"kimi.env\",\n setup_url: \"https://www.kimi.com/coding/docs/en/more/third-party-agents.html\",\n default_model: Some(\"kimi-for-coding\"),\n requires_api_key: true,\n};\n\npub const AI302_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"302ai\",\n display_name: \"302.AI\",\n api_base: \"https://api.302.ai/v1\",\n api_key_env: \"302AI_API_KEY\",\n env_file: \"302ai.env\",\n setup_url: \"https://opencode.ai/docs/providers#302ai\",\n default_model: Some(\"qwen3-235b-a22b-instruct-2507\"),\n requires_api_key: true,\n};\n\npub const BASETEN_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"baseten\",\n display_name: \"Baseten\",\n api_base: \"https://inference.baseten.co/v1\",\n api_key_env: \"BASETEN_API_KEY\",\n env_file: \"baseten.env\",\n setup_url: \"https://opencode.ai/docs/providers#baseten\",\n default_model: Some(\"zai-org/GLM-4.7\"),\n requires_api_key: true,\n};\n\npub const CORTECS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"cortecs\",\n display_name: \"Cortecs\",\n api_base: \"https://api.cortecs.ai/v1\",\n api_key_env: \"CORTECS_API_KEY\",\n env_file: \"cortecs.env\",\n setup_url: \"https://opencode.ai/docs/providers#cortecs\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const DEEPSEEK_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"deepseek\",\n display_name: \"DeepSeek\",\n api_base: \"https://api.deepseek.com\",\n api_key_env: \"DEEPSEEK_API_KEY\",\n env_file: \"deepseek.env\",\n setup_url: \"https://api-docs.deepseek.com/\",\n default_model: Some(\"deepseek-v4-flash\"),\n requires_api_key: true,\n};\n\npub const COMTEGRA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"comtegra\",\n display_name: \"Comtegra GPU Cloud\",\n api_base: \"https://llm.comtegra.cloud/v1\",\n api_key_env: \"COMTEGRA_API_KEY\",\n env_file: \"comtegra.env\",\n setup_url: \"https://docs.cgc.comtegra.cloud/llm-api\",\n default_model: Some(\"glm-51-nvfp4\"),\n requires_api_key: true,\n};\n\npub const FIRMWARE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"firmware\",\n display_name: \"Firmware\",\n api_base: \"https://app.frogbot.ai/api/v1\",\n api_key_env: \"FIRMWARE_API_KEY\",\n env_file: \"firmware.env\",\n setup_url: \"https://opencode.ai/docs/providers#firmware\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const HUGGING_FACE_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"huggingface\",\n display_name: \"Hugging Face\",\n api_base: \"https://router.huggingface.co/v1\",\n api_key_env: \"HF_TOKEN\",\n env_file: \"huggingface.env\",\n setup_url: \"https://opencode.ai/docs/providers#hugging-face\",\n default_model: Some(\"zai-org/GLM-4.7\"),\n requires_api_key: true,\n};\n\npub const MOONSHOT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"moonshotai\",\n display_name: \"Moonshot AI\",\n api_base: \"https://api.moonshot.ai/v1\",\n api_key_env: \"MOONSHOT_API_KEY\",\n env_file: \"moonshotai.env\",\n setup_url: \"https://opencode.ai/docs/providers#moonshot-ai\",\n default_model: Some(\"kimi-k2.5\"),\n requires_api_key: true,\n};\n\npub const NEBIUS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"nebius\",\n display_name: \"Nebius Token Factory\",\n api_base: \"https://api.tokenfactory.nebius.com/v1\",\n api_key_env: \"NEBIUS_API_KEY\",\n env_file: \"nebius.env\",\n setup_url: \"https://opencode.ai/docs/providers#nebius-token-factory\",\n default_model: Some(\"openai/gpt-oss-120b\"),\n requires_api_key: true,\n};\n\npub const SCALEWAY_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"scaleway\",\n display_name: \"Scaleway\",\n api_base: \"https://api.scaleway.ai/v1\",\n api_key_env: \"SCALEWAY_API_KEY\",\n env_file: \"scaleway.env\",\n setup_url: \"https://opencode.ai/docs/providers#scaleway\",\n default_model: Some(\"qwen3-coder-30b-a3b-instruct\"),\n requires_api_key: true,\n};\n\npub const STACKIT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"stackit\",\n display_name: \"STACKIT\",\n api_base: \"https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1\",\n api_key_env: \"STACKIT_API_KEY\",\n env_file: \"stackit.env\",\n setup_url: \"https://opencode.ai/docs/providers#stackit\",\n default_model: Some(\"openai/gpt-oss-120b\"),\n requires_api_key: true,\n};\n\npub const GROQ_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"groq\",\n display_name: \"Groq\",\n api_base: \"https://api.groq.com/openai/v1\",\n api_key_env: \"GROQ_API_KEY\",\n env_file: \"groq.env\",\n setup_url: \"https://console.groq.com/docs/openai\",\n default_model: Some(\"llama-3.1-8b-instant\"),\n requires_api_key: true,\n};\n\npub const MISTRAL_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"mistral\",\n display_name: \"Mistral\",\n api_base: \"https://api.mistral.ai/v1\",\n api_key_env: \"MISTRAL_API_KEY\",\n env_file: \"mistral.env\",\n setup_url: \"https://docs.mistral.ai/getting-started/models/\",\n default_model: Some(\"devstral-medium-2507\"),\n requires_api_key: true,\n};\n\npub const PERPLEXITY_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"perplexity\",\n display_name: \"Perplexity\",\n api_base: \"https://api.perplexity.ai\",\n api_key_env: \"PERPLEXITY_API_KEY\",\n env_file: \"perplexity.env\",\n setup_url: \"https://docs.perplexity.ai/docs/agent-api/openai-compatibility\",\n default_model: Some(\"sonar\"),\n requires_api_key: true,\n};\n\npub const TOGETHER_AI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"togetherai\",\n display_name: \"Together AI\",\n api_base: \"https://api.together.xyz/v1\",\n api_key_env: \"TOGETHER_API_KEY\",\n env_file: \"togetherai.env\",\n setup_url: \"https://docs.together.ai/docs/openai-api-compatibility\",\n default_model: Some(\"moonshotai/Kimi-K2-Instruct\"),\n requires_api_key: true,\n};\n\npub const DEEPINFRA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"deepinfra\",\n display_name: \"Deep Infra\",\n api_base: \"https://api.deepinfra.com/v1/openai\",\n api_key_env: \"DEEPINFRA_API_KEY\",\n env_file: \"deepinfra.env\",\n setup_url: \"https://deepinfra.com/docs/api-reference\",\n default_model: Some(\"moonshotai/Kimi-K2-Instruct\"),\n requires_api_key: true,\n};\n\npub const FIREWORKS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"fireworks\",\n display_name: \"Fireworks\",\n api_base: \"https://api.fireworks.ai/inference/v1\",\n api_key_env: \"FIREWORKS_API_KEY\",\n env_file: \"fireworks.env\",\n setup_url: \"https://docs.fireworks.ai/tools-sdks/openai-compatibility\",\n default_model: Some(\"accounts/fireworks/routers/kimi-k2p5-turbo\"),\n requires_api_key: true,\n};\n\npub const MINIMAX_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"minimax\",\n display_name: \"MiniMax\",\n api_base: \"https://api.minimaxi.com/v1\",\n api_key_env: \"OPENAI_API_KEY\",\n env_file: \"minimax.env\",\n setup_url: \"https://platform.minimax.io/docs/api-reference/text-openai-api\",\n default_model: Some(\"MiniMax-M2.7\"),\n requires_api_key: true,\n};\n\npub const XAI_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"xai\",\n display_name: \"xAI\",\n api_base: \"https://api.x.ai/v1\",\n api_key_env: \"XAI_API_KEY\",\n env_file: \"xai.env\",\n setup_url: \"https://docs.x.ai/developers/quickstart\",\n default_model: Some(\"grok-code-fast-1\"),\n requires_api_key: true,\n};\n\npub const LMSTUDIO_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"lmstudio\",\n display_name: \"LM Studio\",\n api_base: \"http://localhost:1234/v1\",\n api_key_env: \"LMSTUDIO_API_KEY\",\n env_file: \"lmstudio.env\",\n setup_url: \"https://lmstudio.ai/docs/app/api/endpoints/openai\",\n default_model: None,\n requires_api_key: false,\n};\n\npub const OLLAMA_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"ollama\",\n display_name: \"Ollama\",\n api_base: \"http://localhost:11434/v1\",\n api_key_env: \"OLLAMA_API_KEY\",\n env_file: \"ollama.env\",\n setup_url: \"https://ollama.com/blog/openai-compatibility\",\n default_model: None,\n requires_api_key: false,\n};\n\npub const CHUTES_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"chutes\",\n display_name: \"Chutes\",\n api_base: \"https://llm.chutes.ai/v1\",\n api_key_env: \"CHUTES_API_KEY\",\n env_file: \"chutes.env\",\n setup_url: \"https://chutes.ai\",\n default_model: Some(\"Qwen/Qwen3-Coder-480B-A35B-Instruct\"),\n requires_api_key: true,\n};\n\npub const CEREBRAS_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"cerebras\",\n display_name: \"Cerebras\",\n api_base: \"https://api.cerebras.ai/v1\",\n api_key_env: \"CEREBRAS_API_KEY\",\n env_file: \"cerebras.env\",\n setup_url: \"https://inference-docs.cerebras.ai/introduction\",\n default_model: Some(\"qwen-3-coder-480b\"),\n requires_api_key: true,\n};\n\npub const ALIBABA_CODING_PLAN_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"alibaba-coding-plan\",\n display_name: \"Alibaba Cloud Coding Plan\",\n api_base: \"https://coding-intl.dashscope.aliyuncs.com/v1\",\n api_key_env: \"BAILIAN_CODING_PLAN_API_KEY\",\n env_file: \"alibaba-coding-plan.env\",\n setup_url: \"https://www.alibabacloud.com/help/en/model-studio/coding-plan-quickstart\",\n default_model: Some(\"qwen3-coder-plus\"),\n requires_api_key: true,\n};\n\npub const OPENAI_COMPAT_PROFILE: OpenAiCompatibleProfile = OpenAiCompatibleProfile {\n id: \"openai-compatible\",\n display_name: \"OpenAI-compatible\",\n api_base: \"https://api.openai.com/v1\",\n api_key_env: \"OPENAI_COMPAT_API_KEY\",\n env_file: \"openai-compatible.env\",\n setup_url: \"https://github.com/bitan-del/gcode-harness#openai-compatible-providers\",\n default_model: None,\n requires_api_key: true,\n};\n\nconst OPENAI_COMPAT_PROFILES: [OpenAiCompatibleProfile; 29] = [\n OPENCODE_PROFILE,\n OPENCODE_GO_PROFILE,\n ZAI_PROFILE,\n KIMI_PROFILE,\n CHUTES_PROFILE,\n CEREBRAS_PROFILE,\n ALIBABA_CODING_PLAN_PROFILE,\n AI302_PROFILE,\n BASETEN_PROFILE,\n CORTECS_PROFILE,\n DEEPSEEK_PROFILE,\n COMTEGRA_PROFILE,\n FIRMWARE_PROFILE,\n HUGGING_FACE_PROFILE,\n MOONSHOT_PROFILE,\n NEBIUS_PROFILE,\n SCALEWAY_PROFILE,\n STACKIT_PROFILE,\n GROQ_PROFILE,\n MISTRAL_PROFILE,\n PERPLEXITY_PROFILE,\n TOGETHER_AI_PROFILE,\n DEEPINFRA_PROFILE,\n FIREWORKS_PROFILE,\n MINIMAX_PROFILE,\n XAI_PROFILE,\n LMSTUDIO_PROFILE,\n OLLAMA_PROFILE,\n OPENAI_COMPAT_PROFILE,\n];\n\npub const CLAUDE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"claude\",\n display_name: \"Anthropic/Claude\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Anthropic,\n auth_status_method: \"OAuth / API key\",\n aliases: &[\"anthropic\"],\n menu_detail: \"requires Claude Pro or Max subscription\",\n recommended: true,\n target: LoginProviderTarget::Claude,\n order: LoginProviderSurfaceOrder::new(Some(1), Some(1), Some(1), Some(1), Some(1)),\n};\n\npub const AUTO_IMPORT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"auto-import\",\n display_name: \"Auto Import\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::ExternalImport,\n auth_status_method: \"Reuse detected logins\",\n aliases: &[\"import\", \"reuse\", \"autoimport\"],\n menu_detail: \"review and reuse logins from other tools\",\n recommended: false,\n target: LoginProviderTarget::AutoImport,\n order: LoginProviderSurfaceOrder::new(Some(1), Some(1), None, None, None),\n};\n\npub const GCODE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"gcode\",\n display_name: \"Jcode Subscription\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::Jcode,\n auth_status_method: \"API key\",\n aliases: &[\"subscription\", \"gcode-subscription\"],\n menu_detail: \"curated gcode subscription models\",\n recommended: false,\n target: LoginProviderTarget::Jcode,\n order: LoginProviderSurfaceOrder::new(Some(3), Some(3), Some(3), Some(3), Some(3)),\n};\n\npub const OPENAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai\",\n display_name: \"OpenAI\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::OpenAi,\n auth_status_method: \"OAuth / API key\",\n aliases: &[],\n menu_detail: \"requires ChatGPT Plus or Pro subscription\",\n recommended: true,\n target: LoginProviderTarget::OpenAi,\n order: LoginProviderSurfaceOrder::new(Some(2), Some(2), Some(2), Some(2), Some(2)),\n};\n\npub const OPENAI_API_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai-api\",\n display_name: \"OpenAI API\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenAi,\n auth_status_method: \"API key\",\n aliases: &[\n \"openai-key\",\n \"openai-apikey\",\n \"openai-platform\",\n \"platform-openai\",\n ],\n menu_detail: \"native OpenAI API key, pay-per-token\",\n recommended: false,\n target: LoginProviderTarget::OpenAiApiKey,\n order: LoginProviderSurfaceOrder::new(Some(99), Some(99), Some(99), Some(99), Some(99)),\n};\n\npub const OPENROUTER_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openrouter\",\n display_name: \"OpenRouter\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key, pay-per-token, 200+ models\",\n recommended: false,\n target: LoginProviderTarget::OpenRouter,\n order: LoginProviderSurfaceOrder::new(Some(4), Some(3), Some(4), Some(3), Some(3)),\n};\n\npub const AZURE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"azure\",\n display_name: \"Azure OpenAI\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::Azure,\n auth_status_method: \"Entra ID / API key\",\n aliases: &[\"azure-openai\", \"azure_openai\", \"aoai\"],\n menu_detail: \"Microsoft Entra ID or Azure OpenAI API key\",\n recommended: false,\n target: LoginProviderTarget::Azure,\n order: LoginProviderSurfaceOrder::new(Some(5), None, None, None, Some(4)),\n};\n\npub const OPENCODE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"opencode\",\n display_name: \"OpenCode Zen\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"opencode-zen\", \"zen\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENCODE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(5), Some(4), Some(5), Some(4), Some(4)),\n};\n\npub const OPENCODE_GO_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"opencode-go\",\n display_name: \"OpenCode Go\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"opencodego\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENCODE_GO_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(6), Some(5), Some(6), Some(5), Some(5)),\n};\n\npub const ZAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"zai\",\n display_name: \"Z.AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"z.ai\", \"z-ai\", \"zai-coding\", \"zhipu\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(ZAI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(7), Some(6), Some(7), Some(6), Some(6)),\n};\n\npub const KIMI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"kimi\",\n display_name: \"Kimi Code\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\n \"kimi-code\",\n \"kimi-coding\",\n \"kimi-coding-plan\",\n \"kimi-for-coding\",\n \"moonshot-coding\",\n ],\n menu_detail: \"API key, dedicated Kimi coding endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(KIMI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(36), Some(36), Some(36), Some(36), Some(36)),\n};\n\npub const CHUTES_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"chutes\",\n display_name: \"Chutes\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CHUTES_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(8), Some(7), Some(8), Some(7), Some(7)),\n};\n\npub const CEREBRAS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cerebras\",\n display_name: \"Cerebras\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"cerebrascode\", \"cerberascode\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CEREBRAS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(9), Some(8), Some(9), Some(8), Some(8)),\n};\n\npub const ALIBABA_CODING_PLAN_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"alibaba-coding-plan\",\n display_name: \"Alibaba Cloud Coding Plan\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"bailian\", \"aliyun-bailian\", \"coding-plan\", \"alibaba-coding\"],\n menu_detail: \"API key, dedicated Alibaba coding endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(ALIBABA_CODING_PLAN_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(10), Some(9), Some(10), Some(9), Some(9)),\n};\n\npub const AI302_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"302ai\",\n display_name: \"302.AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"302.ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(AI302_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(18), Some(18), Some(18), Some(18), Some(18)),\n};\n\npub const BASETEN_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"baseten\",\n display_name: \"Baseten\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(BASETEN_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(19), Some(19), Some(19), Some(19), Some(19)),\n};\n\npub const CORTECS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cortecs\",\n display_name: \"Cortecs\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(CORTECS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(20), Some(20), Some(20), Some(20), Some(20)),\n};\n\npub const DEEPSEEK_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"deepseek\",\n display_name: \"DeepSeek\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(DEEPSEEK_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(21), Some(21), Some(21), Some(21), Some(21)),\n};\n\npub const COMTEGRA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"comtegra\",\n display_name: \"Comtegra GPU Cloud\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"cgc\", \"comtegra-gpu-cloud\"],\n menu_detail: \"OpenAI-compatible LLM API\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(COMTEGRA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(22), Some(22), Some(22), Some(22), Some(22)),\n};\n\npub const FIRMWARE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"firmware\",\n display_name: \"Firmware\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(FIRMWARE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(22), Some(22), Some(22), Some(22), Some(22)),\n};\n\npub const HUGGING_FACE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"huggingface\",\n display_name: \"Hugging Face\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"hugging-face\", \"hf\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(HUGGING_FACE_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(23), Some(23), Some(23), Some(23), Some(23)),\n};\n\npub const MOONSHOT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"moonshotai\",\n display_name: \"Moonshot AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"moonshot\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MOONSHOT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(24), Some(24), Some(24), Some(24), Some(24)),\n};\n\npub const NEBIUS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"nebius\",\n display_name: \"Nebius Token Factory\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(NEBIUS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(25), Some(25), Some(25), Some(25), Some(25)),\n};\n\npub const SCALEWAY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"scaleway\",\n display_name: \"Scaleway\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(SCALEWAY_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(26), Some(26), Some(26), Some(26), Some(26)),\n};\n\npub const STACKIT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"stackit\",\n display_name: \"STACKIT\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(STACKIT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(27), Some(27), Some(27), Some(27), Some(27)),\n};\n\npub const GROQ_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"groq\",\n display_name: \"Groq\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(GROQ_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(28), Some(28), Some(28), Some(28), Some(28)),\n};\n\npub const MISTRAL_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"mistral\",\n display_name: \"Mistral\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"mistralai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MISTRAL_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(29), Some(29), Some(29), Some(29), Some(29)),\n};\n\npub const PERPLEXITY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"perplexity\",\n display_name: \"Perplexity\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"pplx\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(PERPLEXITY_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(30), Some(30), Some(30), Some(30), Some(30)),\n};\n\npub const TOGETHER_AI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"togetherai\",\n display_name: \"Together AI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"together\", \"together-ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(TOGETHER_AI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(31), Some(31), Some(31), Some(31), Some(31)),\n};\n\npub const DEEPINFRA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"deepinfra\",\n display_name: \"Deep Infra\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"deep-infra\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(DEEPINFRA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(32), Some(32), Some(32), Some(32), Some(32)),\n};\n\npub const FIREWORKS_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"fireworks\",\n display_name: \"Fireworks\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"fireworks-ai\", \"fireworks.ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(FIREWORKS_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(37), Some(37), Some(37), Some(37), Some(37)),\n};\n\npub const MINIMAX_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"minimax\",\n display_name: \"MiniMax\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"minimaxi\", \"minimax-ai\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(MINIMAX_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(38), Some(38), Some(38), Some(38), Some(38)),\n};\n\npub const XAI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"xai\",\n display_name: \"xAI\",\n auth_kind: LoginProviderAuthKind::ApiKey,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key\",\n aliases: &[\"x.ai\", \"x-ai\", \"grok\"],\n menu_detail: \"API key\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(XAI_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(33), Some(33), Some(33), Some(33), Some(33)),\n};\n\npub const LMSTUDIO_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"lmstudio\",\n display_name: \"LM Studio\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"local endpoint\",\n aliases: &[\"lm-studio\"],\n menu_detail: \"local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(LMSTUDIO_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(34), Some(34), Some(34), Some(34), Some(34)),\n};\n\npub const OLLAMA_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"ollama\",\n display_name: \"Ollama\",\n auth_kind: LoginProviderAuthKind::Local,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"local endpoint\",\n aliases: &[],\n menu_detail: \"local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OLLAMA_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(35), Some(35), Some(35), Some(35), Some(35)),\n};\n\npub const OPENAI_COMPAT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"openai-compatible\",\n display_name: \"OpenAI-compatible\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::OpenRouterLike,\n auth_status_method: \"API key / local endpoint\",\n aliases: &[\"openai_compatible\", \"compat\", \"custom\"],\n menu_detail: \"custom hosted or local OpenAI-compatible endpoint\",\n recommended: false,\n target: LoginProviderTarget::OpenAiCompatible(OPENAI_COMPAT_PROFILE),\n order: LoginProviderSurfaceOrder::new(Some(10), Some(9), None, None, Some(9)),\n};\n\npub const CURSOR_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"cursor\",\n display_name: \"Cursor\",\n auth_kind: LoginProviderAuthKind::Hybrid,\n auth_state_key: LoginProviderAuthStateKey::Cursor,\n auth_status_method: \"API key / CLI\",\n aliases: &[],\n menu_detail: \"browser login or API key\",\n recommended: false,\n target: LoginProviderTarget::Cursor,\n order: LoginProviderSurfaceOrder::new(Some(11), Some(12), None, Some(9), Some(12)),\n};\n\npub const COPILOT_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"copilot\",\n display_name: \"GitHub Copilot\",\n auth_kind: LoginProviderAuthKind::DeviceCode,\n auth_state_key: LoginProviderAuthStateKey::Copilot,\n auth_status_method: \"device code\",\n aliases: &[],\n menu_detail: \"GitHub device flow\",\n recommended: false,\n target: LoginProviderTarget::Copilot,\n order: LoginProviderSurfaceOrder::new(Some(3), Some(10), Some(3), Some(10), Some(10)),\n};\n\npub const GEMINI_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"gemini\",\n display_name: \"Google Gemini\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Gemini,\n auth_status_method: \"OAuth\",\n aliases: &[],\n menu_detail: \"Google Gemini Code Assist OAuth login\",\n recommended: false,\n target: LoginProviderTarget::Gemini,\n order: LoginProviderSurfaceOrder::new(Some(13), Some(11), Some(4), Some(11), Some(13)),\n};\n\npub const ANTIGRAVITY_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"antigravity\",\n display_name: \"Antigravity\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Antigravity,\n auth_status_method: \"OAuth\",\n aliases: &[],\n menu_detail: \"Google Antigravity OAuth login\",\n recommended: false,\n target: LoginProviderTarget::Antigravity,\n order: LoginProviderSurfaceOrder::new(Some(12), Some(12), None, Some(12), Some(12)),\n};\n\npub const GOOGLE_LOGIN_PROVIDER: LoginProviderDescriptor = LoginProviderDescriptor {\n id: \"google\",\n display_name: \"Google/Gmail\",\n auth_kind: LoginProviderAuthKind::OAuth,\n auth_state_key: LoginProviderAuthStateKey::Google,\n auth_status_method: \"OAuth\",\n aliases: &[\"gmail\"],\n menu_detail: \"read, draft, and send emails\",\n recommended: false,\n target: LoginProviderTarget::Google,\n order: LoginProviderSurfaceOrder::new(Some(13), None, None, None, None),\n};\n\nconst LOGIN_PROVIDERS: [LoginProviderDescriptor; 41] = [\n AUTO_IMPORT_LOGIN_PROVIDER,\n CLAUDE_LOGIN_PROVIDER,\n OPENAI_LOGIN_PROVIDER,\n OPENAI_API_LOGIN_PROVIDER,\n GCODE_LOGIN_PROVIDER,\n OPENROUTER_LOGIN_PROVIDER,\n AZURE_LOGIN_PROVIDER,\n OPENCODE_LOGIN_PROVIDER,\n OPENCODE_GO_LOGIN_PROVIDER,\n ZAI_LOGIN_PROVIDER,\n KIMI_LOGIN_PROVIDER,\n CHUTES_LOGIN_PROVIDER,\n CEREBRAS_LOGIN_PROVIDER,\n ALIBABA_CODING_PLAN_LOGIN_PROVIDER,\n AI302_LOGIN_PROVIDER,\n BASETEN_LOGIN_PROVIDER,\n CORTECS_LOGIN_PROVIDER,\n DEEPSEEK_LOGIN_PROVIDER,\n COMTEGRA_LOGIN_PROVIDER,\n FIRMWARE_LOGIN_PROVIDER,\n HUGGING_FACE_LOGIN_PROVIDER,\n MOONSHOT_LOGIN_PROVIDER,\n NEBIUS_LOGIN_PROVIDER,\n SCALEWAY_LOGIN_PROVIDER,\n STACKIT_LOGIN_PROVIDER,\n GROQ_LOGIN_PROVIDER,\n MISTRAL_LOGIN_PROVIDER,\n PERPLEXITY_LOGIN_PROVIDER,\n TOGETHER_AI_LOGIN_PROVIDER,\n DEEPINFRA_LOGIN_PROVIDER,\n FIREWORKS_LOGIN_PROVIDER,\n MINIMAX_LOGIN_PROVIDER,\n XAI_LOGIN_PROVIDER,\n LMSTUDIO_LOGIN_PROVIDER,\n OLLAMA_LOGIN_PROVIDER,\n OPENAI_COMPAT_LOGIN_PROVIDER,\n CURSOR_LOGIN_PROVIDER,\n COPILOT_LOGIN_PROVIDER,\n GEMINI_LOGIN_PROVIDER,\n ANTIGRAVITY_LOGIN_PROVIDER,\n GOOGLE_LOGIN_PROVIDER,\n];\n\npub fn openai_compatible_profiles() -> &'static [OpenAiCompatibleProfile] {\n &OPENAI_COMPAT_PROFILES\n}\n\npub fn login_providers() -> &'static [LoginProviderDescriptor] {\n &LOGIN_PROVIDERS\n}\n\nfn login_providers_for_surface(surface: LoginProviderSurface) -> Vec {\n let mut providers = login_providers()\n .iter()\n .copied()\n .filter(|provider| provider.order.for_surface(surface).is_some())\n .collect::>();\n providers.sort_by_key(|provider| provider.order.for_surface(surface).unwrap_or(u8::MAX));\n providers\n}\n\npub fn cli_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::CliLogin)\n}\n\npub fn tui_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::TuiLogin)\n}\n\npub fn server_bootstrap_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::ServerBootstrap)\n}\n\npub fn auto_init_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::AutoInit)\n}\n\npub fn auth_status_login_providers() -> Vec {\n login_providers_for_surface(LoginProviderSurface::AuthStatus)\n}\n\npub fn resolve_login_provider(input: &str) -> Option {\n let normalized = normalize_provider_input(input)?;\n login_providers().iter().copied().find(|provider| {\n provider.id == normalized || provider.aliases.iter().any(|alias| *alias == normalized)\n })\n}\n\npub fn resolve_login_selection(\n input: &str,\n providers: &[LoginProviderDescriptor],\n) -> Option {\n let trimmed = input.trim();\n if let Ok(index) = trimmed.parse::() {\n return index\n .checked_sub(1)\n .and_then(|idx| providers.get(idx))\n .copied();\n }\n\n let provider = resolve_login_provider(trimmed)?;\n providers\n .iter()\n .copied()\n .find(|candidate| candidate.id == provider.id)\n}\n\npub fn is_safe_env_key_name(name: &str) -> bool {\n !name.is_empty()\n && name\n .chars()\n .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')\n}\n\npub fn is_safe_env_file_name(name: &str) -> bool {\n !name.is_empty()\n && !name.contains('/')\n && !name.contains('\\\\')\n && name\n .chars()\n .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')\n}\n\npub fn normalize_api_base(raw: &str) -> Option {\n let trimmed = raw.trim();\n if trimmed.is_empty() {\n return None;\n }\n\n let parsed = url::Url::parse(trimmed).ok()?;\n let scheme = parsed.scheme();\n if scheme != \"https\" && scheme != \"http\" {\n return None;\n }\n\n if scheme == \"http\" {\n let host = parsed.host_str()?;\n if !allows_insecure_http_host(host) {\n return None;\n }\n }\n\n Some(trimmed.trim_end_matches('/').to_string())\n}\n\nfn allows_insecure_http_host(host: &str) -> bool {\n let host = host.trim();\n let host = host\n .strip_prefix('[')\n .and_then(|s| s.strip_suffix(']'))\n .unwrap_or(host);\n if host.eq_ignore_ascii_case(\"localhost\") {\n return true;\n }\n\n if let Ok(ip) = host.parse::() {\n return match ip {\n std::net::IpAddr::V4(v4) => {\n v4.is_loopback() || v4.is_private() || v4.is_link_local() || v4.is_unspecified()\n }\n std::net::IpAddr::V6(v6) => {\n v6.is_loopback()\n || v6.is_unique_local()\n || v6.is_unicast_link_local()\n || v6.is_unspecified()\n }\n };\n }\n\n false\n}\n\nfn normalize_provider_input(input: &str) -> Option {\n let trimmed = input.trim();\n if trimmed.is_empty() {\n return None;\n }\n Some(trimmed.to_ascii_lowercase())\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n use std::collections::HashSet;\n\n #[test]\n fn matrix_profiles_have_unique_ids_and_safe_metadata() {\n let mut ids = HashSet::new();\n for profile in openai_compatible_profiles() {\n assert!(\n ids.insert(profile.id),\n \"duplicate provider profile id: {}\",\n profile.id\n );\n assert!(is_safe_env_key_name(profile.api_key_env));\n assert!(is_safe_env_file_name(profile.env_file));\n assert_eq!(\n normalize_api_base(profile.api_base).as_deref(),\n Some(profile.api_base)\n );\n }\n }\n\n #[test]\n fn normalize_api_base_accepts_private_http_hosts() {\n assert_eq!(\n normalize_api_base(\"http://192.168.1.25:8000/v1/\").as_deref(),\n Some(\"http://192.168.1.25:8000/v1\")\n );\n assert_eq!(\n normalize_api_base(\"http://10.0.0.8:11434/v1\").as_deref(),\n Some(\"http://10.0.0.8:11434/v1\")\n );\n assert_eq!(\n normalize_api_base(\"http://[fd00::1]:8080/v1\").as_deref(),\n Some(\"http://[fd00::1]:8080/v1\")\n );\n }\n\n #[test]\n fn normalize_api_base_rejects_public_http_hosts() {\n assert_eq!(normalize_api_base(\"http://example.com/v1\"), None);\n assert_eq!(normalize_api_base(\"http://8.8.8.8/v1\"), None);\n }\n\n #[test]\n fn alibaba_coding_plan_uses_current_international_endpoint() {\n assert_eq!(\n ALIBABA_CODING_PLAN_PROFILE.api_base,\n \"https://coding-intl.dashscope.aliyuncs.com/v1\"\n );\n }\n\n #[test]\n fn minimax_profile_uses_official_openai_compatible_configuration() {\n assert_eq!(MINIMAX_PROFILE.api_base, \"https://api.minimaxi.com/v1\");\n assert_eq!(MINIMAX_PROFILE.api_key_env, \"OPENAI_API_KEY\");\n }\n\n #[test]\n fn matrix_login_provider_aliases_resolve_to_canonical_ids() {\n assert_eq!(\n resolve_login_provider(\"subscription\").map(|provider| provider.id),\n Some(\"gcode\")\n );\n assert_eq!(\n resolve_login_provider(\"anthropic\").map(|provider| provider.id),\n Some(\"claude\")\n );\n assert_eq!(\n resolve_login_provider(\"opencodego\").map(|provider| provider.id),\n Some(\"opencode-go\")\n );\n assert_eq!(\n resolve_login_provider(\"z.ai\").map(|provider| provider.id),\n Some(\"zai\")\n );\n assert_eq!(\n resolve_login_provider(\"zhipu\").map(|provider| provider.id),\n Some(\"zai\")\n );\n assert_eq!(\n resolve_login_provider(\"kimi\").map(|provider| provider.id),\n Some(\"kimi\")\n );\n assert_eq!(\n resolve_login_provider(\"kimi-for-coding\").map(|provider| provider.id),\n Some(\"kimi\")\n );\n assert_eq!(\n resolve_login_provider(\"compat\").map(|provider| provider.id),\n Some(\"openai-compatible\")\n );\n assert_eq!(\n resolve_login_provider(\"aoai\").map(|provider| provider.id),\n Some(\"azure\")\n );\n assert_eq!(\n resolve_login_provider(\"cerberascode\").map(|provider| provider.id),\n Some(\"cerebras\")\n );\n assert_eq!(\n resolve_login_provider(\"bailian\").map(|provider| provider.id),\n Some(\"alibaba-coding-plan\")\n );\n assert_eq!(\n resolve_login_provider(\"302.ai\").map(|provider| provider.id),\n Some(\"302ai\")\n );\n assert_eq!(\n resolve_login_provider(\"hf\").map(|provider| provider.id),\n Some(\"huggingface\")\n );\n assert_eq!(\n resolve_login_provider(\"moonshot\").map(|provider| provider.id),\n Some(\"moonshotai\")\n );\n assert_eq!(\n resolve_login_provider(\"mistralai\").map(|provider| provider.id),\n Some(\"mistral\")\n );\n assert_eq!(\n resolve_login_provider(\"pplx\").map(|provider| provider.id),\n Some(\"perplexity\")\n );\n assert_eq!(\n resolve_login_provider(\"together\").map(|provider| provider.id),\n Some(\"togetherai\")\n );\n assert_eq!(\n resolve_login_provider(\"deep-infra\").map(|provider| provider.id),\n Some(\"deepinfra\")\n );\n assert_eq!(\n resolve_login_provider(\"fireworks.ai\").map(|provider| provider.id),\n Some(\"fireworks\")\n );\n assert_eq!(\n resolve_login_provider(\"minimax-ai\").map(|provider| provider.id),\n Some(\"minimax\")\n );\n assert_eq!(\n resolve_login_provider(\"grok\").map(|provider| provider.id),\n Some(\"xai\")\n );\n assert_eq!(\n resolve_login_provider(\"lm-studio\").map(|provider| provider.id),\n Some(\"lmstudio\")\n );\n assert_eq!(\n resolve_login_provider(\"gmail\").map(|provider| provider.id),\n Some(\"google\")\n );\n }\n\n #[test]\n fn matrix_login_provider_ids_and_aliases_are_unique() {\n let mut seen = HashSet::new();\n for provider in login_providers() {\n assert!(\n seen.insert(provider.id),\n \"duplicate login provider identifier: {}\",\n provider.id\n );\n for alias in provider.aliases {\n assert!(\n seen.insert(*alias),\n \"duplicate login provider alias: {}\",\n alias\n );\n }\n }\n }\n\n #[test]\n fn matrix_tui_login_selection_supports_numbers_and_names() {\n let providers = tui_login_providers();\n assert_eq!(\n resolve_login_selection(\"1\", &providers).map(|provider| provider.id),\n Some(\"auto-import\")\n );\n assert_eq!(\n resolve_login_selection(\"2\", &providers).map(|provider| provider.id),\n Some(\"claude\")\n );\n assert_eq!(\n resolve_login_selection(\"15\", &providers).map(|provider| provider.id),\n Some(\"cursor\")\n );\n assert_eq!(\n resolve_login_selection(\"compat\", &providers).map(|provider| provider.id),\n Some(\"openai-compatible\")\n );\n assert!(resolve_login_selection(\"google\", &providers).is_none());\n }\n\n #[test]\n fn matrix_cli_login_selection_preserves_existing_order() {\n let providers = cli_login_providers();\n assert_eq!(\n resolve_login_selection(\"1\", &providers).map(|provider| provider.id),\n Some(\"auto-import\")\n );\n assert_eq!(\n resolve_login_selection(\"4\", &providers).map(|provider| provider.id),\n Some(\"gcode\")\n );\n assert_eq!(\n resolve_login_selection(\"5\", &providers).map(|provider| provider.id),\n Some(\"copilot\")\n );\n assert_eq!(\n resolve_login_selection(\"6\", &providers).map(|provider| provider.id),\n Some(\"openrouter\")\n );\n assert_eq!(\n resolve_login_selection(\"7\", &providers).map(|provider| provider.id),\n Some(\"azure\")\n );\n assert_eq!(\n resolve_login_selection(\"17\", &providers).map(|provider| provider.id),\n Some(\"gemini\")\n );\n assert_eq!(\n resolve_login_selection(\"18\", &providers).map(|provider| provider.id),\n Some(\"google\")\n );\n }\n}\n"}}, "src/provider/tests.rs": {"009beba7598882f4ba1e167a9b4ed98882579a7f": {"t": 1786068335.0382807, "n": 3683, "h": "b171c34524654d13", "c": "use super::*;\nuse crate::provider::models::{ensure_model_allowed_for_subscription, filtered_display_models};\n\nfn with_clean_provider_test_env(f: impl FnOnce() -> T) -> T {\n let _guard = crate::storage::lock_test_env();\n let temp = tempfile::tempdir().expect(\"tempdir\");\n let prev_home = std::env::var_os(\"GCODE_HOME\");\n let prev_subscription =\n std::env::var_os(crate::subscription_catalog::GCODE_SUBSCRIPTION_ACTIVE_ENV);\n let saved_profile_env = [\n \"OPENROUTER_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"KIMI_API_KEY\",\n \"GCODE_OPENROUTER_API_BASE\",\n \"GCODE_OPENROUTER_API_KEY_NAME\",\n \"GCODE_OPENROUTER_ENV_FILE\",\n \"GCODE_OPENROUTER_CACHE_NAMESPACE\",\n \"GCODE_OPENROUTER_PROVIDER_FEATURES\",\n \"GCODE_OPENROUTER_ALLOW_NO_AUTH\",\n \"GCODE_OPENROUTER_MODEL_CATALOG\",\n \"GCODE_OPENROUTER_MODEL\",\n \"GCODE_OPENROUTER_STATIC_MODELS\",\n \"GCODE_OPENAI_COMPAT_API_BASE\",\n \"GCODE_OPENAI_COMPAT_API_KEY_NAME\",\n \"GCODE_OPENAI_COMPAT_DEFAULT_MODEL\",\n \"OPENAI_API_KEY\",\n \"GCODE_NAMED_PROVIDER_PROFILE\",\n \"GCODE_PROVIDER_PROFILE_ACTIVE\",\n \"GCODE_PROVIDER_PROFILE_NAME\",\n ]\n .map(|key| (key, std::env::var_os(key)));\n crate::env::set_var(\"GCODE_HOME\", temp.path());\n for (key, _) in &saved_profile_env {\n crate::env::remove_var(key);\n }\n crate::subscription_catalog::clear_runtime_env();\n crate::auth::claude::set_active_account_override(None);\n crate::auth::codex::set_active_account_override(None);\n\n let result = f();\n\n crate::auth::claude::set_active_account_override(None);\n crate::auth::codex::set_active_account_override(None);\n if let Some(prev_home) = prev_home {\n crate::env::set_var(\"GCODE_HOME\", prev_home);\n } else {\n crate::env::remove_var(\"GCODE_HOME\");\n }\n if let Some(prev_subscription) = prev_subscription {\n crate::env::set_var(\n crate::subscription_catalog::GCODE_SUBSCRIPTION_ACTIVE_ENV,\n prev_subscription,\n );\n } else {\n crate::env::remove_var(crate::subscription_catalog::GCODE_SUBSCRIPTION_ACTIVE_ENV);\n }\n for (key, value) in saved_profile_env {\n if let Some(value) = value {\n crate::env::set_var(key, value);\n } else {\n crate::env::remove_var(key);\n }\n }\n crate::subscription_catalog::clear_runtime_env();\n result\n}\n\nfn enter_test_runtime() -> tokio::runtime::Runtime {\n tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()\n .expect(\"build tokio runtime\")\n}\n\nfn with_env_var(key: &str, value: &str, f: impl FnOnce() -> T) -> T {\n let prev = std::env::var_os(key);\n crate::env::set_var(key, value);\n let result = f();\n if let Some(prev) = prev {\n crate::env::set_var(key, prev);\n } else {\n crate::env::remove_var(key);\n }\n result\n}\n\nfn test_multi_provider_with_cursor() -> MultiProvider {\n MultiProvider {\n claude: RwLock::new(None),\n anthropic: RwLock::new(None),\n openai: RwLock::new(None),\n copilot_api: RwLock::new(None),\n antigravity: RwLock::new(None),\n gemini: RwLock::new(None),\n cursor: RwLock::new(Some(Arc::new(cursor::CursorCliProvider::new()))),\n openrouter: RwLock::new(None),\n active: RwLock::new(ActiveProvider::Cursor),\n use_claude_cli: false,\n startup_notices: RwLock::new(Vec::new()),\n forced_provider: None,\n }\n}\n\ninclude!(\"tests/auth_refresh.rs\");\ninclude!(\"tests/model_resolution.rs\");\ninclude!(\"tests/fallback_failover.rs\");\ninclude!(\"tests/catalog_subscription.rs\");\n"}}}}