Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/apts-1547_gcop-rs.json
T

1 line
53 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "apts-1547/gcop-rs", "files": {"src/llm/provider/utils.rs": {"1b7d1085f02154bf170c4e8d36d713df9e92d606": {"t": 1785746222.602999, "n": 7969, "h": "d47eeca572f6248c", "c": "//! Provider utility functions\n//!\n//! Contains common functions such as URL processing and endpoint completion\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// OpenAI Responses API endpoint suffix\npub const OPENAI_RESPONSES_API_SUFFIX: &str = \"/v1/responses\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude default base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI default base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama default base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// Gemini default base URL\npub const DEFAULT_GEMINI_BASE: &str = \"https://generativelanguage.googleapis.com\";\n\n/// Smart completion API endpoint\n///\n/// # Behavior\n/// 1. Remove trailing slashes\n/// 2. Check whether the URL contains the full path\n/// 3. If incomplete, automatically complete suffix\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. Clean URLs: Remove trailing slashes\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. If the expected suffix is ​​already included, return directly\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. Check whether the URL contains the partial prefix of suffix\n // For example: url is \"https://api.com/v1\", suffix is ​​\"v1/chat/completions\"\n // Then we should only complete \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // Check from back to front to see if the URL already contains the suffix prefix\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // The URL already contains part of the suffix, only the remaining part is completed.\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. Check whether it is a customized complete API path\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. Complete the complete suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// Check if the URL is already a full API path\n///\n/// Heuristic rules:\n/// - Path depth >= 2 is considered a complete path (such as /v1/chat, /api/generate)\n/// - This allows users to use fully customized endpoints\nfn is_complete_api_path(url: &str) -> bool {\n // Extract the path part (remove the protocol and domain name)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // Count non-empty path segments\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // Path depth >= 2 is considered a user-defined complete path\n segment_count >= 2\n}\n\n/// Mask API key to prevent log leaks\n///\n/// # rule\n/// - length > 8: display first 4 characters + `...` + last 4 characters\n/// - length <= 8: display `****`\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::mask_api_key;\n///\n/// assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n/// assert_eq!(mask_api_key(\"short\"), \"****\");\n/// assert_eq!(mask_api_key(\"\"), \"****\");\n/// ```\npub fn mask_api_key(key: &str) -> String {\n if key.len() > 8 {\n format!(\"{}...{}\", &key[..4], &key[key.len() - 4..])\n } else {\n \"****\".to_string()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_mask_api_key() {\n // Long key: first 4 + ... + last 4\n assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n assert_eq!(mask_api_key(\"AIzaSyD-1234567890abcdef\"), \"AIza...cdef\");\n\n // short key\n assert_eq!(mask_api_key(\"12345678\"), \"****\");\n assert_eq!(mask_api_key(\"short\"), \"****\");\n\n // null\n assert_eq!(mask_api_key(\"\"), \"****\");\n\n // Exactly 9 characters\n assert_eq!(mask_api_key(\"123456789\"), \"1234...6789\");\n }\n\n #[test]\n fn test_complete_endpoint_basic() {\n // Basic completion\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // with trailing slash\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // Already complete\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // Only the version number needs to be completed\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // Customize the full path and keep it as is\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // full path\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // Incomplete path\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama local address\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude Agent\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix with leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix without leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "a053707a1a5dfd9231583f8f6b2f8f5dcff7c4f3": {"t": 1785760744.7968214, "n": 7969, "h": "d47eeca572f6248c", "c": "//! Provider utility functions\n//!\n//! Contains common functions such as URL processing and endpoint completion\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// OpenAI Responses API endpoint suffix\npub const OPENAI_RESPONSES_API_SUFFIX: &str = \"/v1/responses\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude default base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI default base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama default base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// Gemini default base URL\npub const DEFAULT_GEMINI_BASE: &str = \"https://generativelanguage.googleapis.com\";\n\n/// Smart completion API endpoint\n///\n/// # Behavior\n/// 1. Remove trailing slashes\n/// 2. Check whether the URL contains the full path\n/// 3. If incomplete, automatically complete suffix\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. Clean URLs: Remove trailing slashes\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. If the expected suffix is ​​already included, return directly\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. Check whether the URL contains the partial prefix of suffix\n // For example: url is \"https://api.com/v1\", suffix is ​​\"v1/chat/completions\"\n // Then we should only complete \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // Check from back to front to see if the URL already contains the suffix prefix\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // The URL already contains part of the suffix, only the remaining part is completed.\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. Check whether it is a customized complete API path\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. Complete the complete suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// Check if the URL is already a full API path\n///\n/// Heuristic rules:\n/// - Path depth >= 2 is considered a complete path (such as /v1/chat, /api/generate)\n/// - This allows users to use fully customized endpoints\nfn is_complete_api_path(url: &str) -> bool {\n // Extract the path part (remove the protocol and domain name)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // Count non-empty path segments\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // Path depth >= 2 is considered a user-defined complete path\n segment_count >= 2\n}\n\n/// Mask API key to prevent log leaks\n///\n/// # rule\n/// - length > 8: display first 4 characters + `...` + last 4 characters\n/// - length <= 8: display `****`\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::mask_api_key;\n///\n/// assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n/// assert_eq!(mask_api_key(\"short\"), \"****\");\n/// assert_eq!(mask_api_key(\"\"), \"****\");\n/// ```\npub fn mask_api_key(key: &str) -> String {\n if key.len() > 8 {\n format!(\"{}...{}\", &key[..4], &key[key.len() - 4..])\n } else {\n \"****\".to_string()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_mask_api_key() {\n // Long key: first 4 + ... + last 4\n assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n assert_eq!(mask_api_key(\"AIzaSyD-1234567890abcdef\"), \"AIza...cdef\");\n\n // short key\n assert_eq!(mask_api_key(\"12345678\"), \"****\");\n assert_eq!(mask_api_key(\"short\"), \"****\");\n\n // null\n assert_eq!(mask_api_key(\"\"), \"****\");\n\n // Exactly 9 characters\n assert_eq!(mask_api_key(\"123456789\"), \"1234...6789\");\n }\n\n #[test]\n fn test_complete_endpoint_basic() {\n // Basic completion\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // with trailing slash\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // Already complete\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // Only the version number needs to be completed\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // Customize the full path and keep it as is\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // full path\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // Incomplete path\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama local address\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude Agent\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix with leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix without leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "0014608ec4caad037bd987487e55929b1217a715": {"t": 1785760746.0359905, "n": 7864, "h": "a6ec09106b0e0a9a", "c": "//! Provider utility functions\n//!\n//! Contains common functions such as URL processing and endpoint completion\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude default base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI default base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama default base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// Gemini default base URL\npub const DEFAULT_GEMINI_BASE: &str = \"https://generativelanguage.googleapis.com\";\n\n/// Smart completion API endpoint\n///\n/// # Behavior\n/// 1. Remove trailing slashes\n/// 2. Check whether the URL contains the full path\n/// 3. If incomplete, automatically complete suffix\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. Clean URLs: Remove trailing slashes\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. If the expected suffix is ​​already included, return directly\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. Check whether the URL contains the partial prefix of suffix\n // For example: url is \"https://api.com/v1\", suffix is ​​\"v1/chat/completions\"\n // Then we should only complete \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // Check from back to front to see if the URL already contains the suffix prefix\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // The URL already contains part of the suffix, only the remaining part is completed.\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. Check whether it is a customized complete API path\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. Complete the complete suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// Check if the URL is already a full API path\n///\n/// Heuristic rules:\n/// - Path depth >= 2 is considered a complete path (such as /v1/chat, /api/generate)\n/// - This allows users to use fully customized endpoints\nfn is_complete_api_path(url: &str) -> bool {\n // Extract the path part (remove the protocol and domain name)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // Count non-empty path segments\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // Path depth >= 2 is considered a user-defined complete path\n segment_count >= 2\n}\n\n/// Mask API key to prevent log leaks\n///\n/// # rule\n/// - length > 8: display first 4 characters + `...` + last 4 characters\n/// - length <= 8: display `****`\n///\n/// # Example\n/// ```\n/// use gcop_rs::llm::provider::utils::mask_api_key;\n///\n/// assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n/// assert_eq!(mask_api_key(\"short\"), \"****\");\n/// assert_eq!(mask_api_key(\"\"), \"****\");\n/// ```\npub fn mask_api_key(key: &str) -> String {\n if key.len() > 8 {\n format!(\"{}...{}\", &key[..4], &key[key.len() - 4..])\n } else {\n \"****\".to_string()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_mask_api_key() {\n // Long key: first 4 + ... + last 4\n assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n assert_eq!(mask_api_key(\"AIzaSyD-1234567890abcdef\"), \"AIza...cdef\");\n\n // short key\n assert_eq!(mask_api_key(\"12345678\"), \"****\");\n assert_eq!(mask_api_key(\"short\"), \"****\");\n\n // null\n assert_eq!(mask_api_key(\"\"), \"****\");\n\n // Exactly 9 characters\n assert_eq!(mask_api_key(\"123456789\"), \"1234...6789\");\n }\n\n #[test]\n fn test_complete_endpoint_basic() {\n // Basic completion\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // with trailing slash\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // Already complete\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // Only the version number needs to be completed\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // Customize the full path and keep it as is\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // full path\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // Incomplete path\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama local address\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude Agent\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix with leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix without leading slash\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "53a08c4cf7c2cf60245183ed43664da44a261245": {"t": 1785760748.4164808, "n": 6972, "h": "7420017de3f554cb", "c": "//! Provider 工具函数\n//!\n//! 包含 URL 处理、endpoint 补全等通用功能\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude 默认 base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI 默认 base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama 默认 base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// Gemini 默认 base URL\npub const DEFAULT_GEMINI_BASE: &str = \"https://generativelanguage.googleapis.com\";\n\n/// 智能补全 API endpoint\n///\n/// # 行为\n/// 1. 移除 trailing slashes\n/// 2. 检测 URL 是否已包含完整路径\n/// 3. 如果不完整,自动补全 suffix\n///\n/// # 示例\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. 清理 URL: 移除尾部斜杠\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. 如果已经包含期望的 suffix,直接返回\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. 检测 URL 是否包含 suffix 的部分前缀\n // 例如: url 是 \"https://api.com/v1\", suffix 是 \"v1/chat/completions\"\n // 那么我们应该只补全 \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // 从后往前检查,看 URL 是否已经包含 suffix 的前缀\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // URL 已经包含了部分 suffix,只补全剩余部分\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. 检测是否是自定义的完整 API 路径\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. 补全完整 suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// 检测 URL 是否已经是完整的 API 路径\n///\n/// 启发式规则:\n/// - 路径深度 >= 2 认为是完整路径 (如 /v1/chat, /api/generate)\n/// - 这允许用户使用完全自定义的 endpoint\nfn is_complete_api_path(url: &str) -> bool {\n // 提取路径部分 (去掉协议和域名)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // 统计非空路径段\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // 路径深度 >= 2 认为是用户自定义的完整路径\n segment_count >= 2\n}\n\n/// 掩码 API key,防止日志泄露\n///\n/// # 规则\n/// - 长度 > 8:显示前 4 字符 + `...` + 后 4 字符\n/// - 长度 <= 8:显示 `****`\n///\n/// # 示例\n/// ```\n/// use gcop_rs::llm::provider::utils::mask_api_key;\n///\n/// assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n/// assert_eq!(mask_api_key(\"short\"), \"****\");\n/// assert_eq!(mask_api_key(\"\"), \"****\");\n/// ```\npub fn mask_api_key(key: &str) -> String {\n if key.len() > 8 {\n format!(\"{}...{}\", &key[..4], &key[key.len() - 4..])\n } else {\n \"****\".to_string()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_mask_api_key() {\n // 长 key:前4 + ... + 后4\n assert_eq!(mask_api_key(\"sk-ant-api03-abcdefgh\"), \"sk-a...efgh\");\n assert_eq!(mask_api_key(\"AIzaSyD-1234567890abcdef\"), \"AIza...cdef\");\n\n // 短 key\n assert_eq!(mask_api_key(\"12345678\"), \"****\");\n assert_eq!(mask_api_key(\"short\"), \"****\");\n\n // 空\n assert_eq!(mask_api_key(\"\"), \"****\");\n\n // 刚好 9 字符\n assert_eq!(mask_api_key(\"123456789\"), \"1234...6789\");\n }\n\n #[test]\n fn test_complete_endpoint_basic() {\n // 基本补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // 带尾部斜杠\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // 已经完整\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // 只有版本号,需要补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // 自定义完整路径,保持原样\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // 完整路径\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // 不完整路径\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama 本地地址\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude 代理\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix 带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix 不带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "dd5254b686e455f068d817f11e208a57e94704a8": {"t": 1785760749.8167741, "n": 5958, "h": "888065d1398b4afe", "c": "//! Provider 工具函数\n//!\n//! 包含 URL 处理、endpoint 补全等通用功能\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude 默认 base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI 默认 base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama 默认 base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// Gemini 默认 base URL\npub const DEFAULT_GEMINI_BASE: &str = \"https://generativelanguage.googleapis.com\";\n\n/// 智能补全 API endpoint\n///\n/// # 行为\n/// 1. 移除 trailing slashes\n/// 2. 检测 URL 是否已包含完整路径\n/// 3. 如果不完整,自动补全 suffix\n///\n/// # 示例\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. 清理 URL: 移除尾部斜杠\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. 如果已经包含期望的 suffix,直接返回\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. 检测 URL 是否包含 suffix 的部分前缀\n // 例如: url 是 \"https://api.com/v1\", suffix 是 \"v1/chat/completions\"\n // 那么我们应该只补全 \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // 从后往前检查,看 URL 是否已经包含 suffix 的前缀\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // URL 已经包含了部分 suffix,只补全剩余部分\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. 检测是否是自定义的完整 API 路径\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. 补全完整 suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// 检测 URL 是否已经是完整的 API 路径\n///\n/// 启发式规则:\n/// - 路径深度 >= 2 认为是完整路径 (如 /v1/chat, /api/generate)\n/// - 这允许用户使用完全自定义的 endpoint\nfn is_complete_api_path(url: &str) -> bool {\n // 提取路径部分 (去掉协议和域名)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // 统计非空路径段\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // 路径深度 >= 2 认为是用户自定义的完整路径\n segment_count >= 2\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_complete_endpoint_basic() {\n // 基本补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // 带尾部斜杠\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // 已经完整\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // 只有版本号,需要补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // 自定义完整路径,保持原样\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // 完整路径\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // 不完整路径\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama 本地地址\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude 代理\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix 带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix 不带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "2838a6a189d3be21e58c33bb1adfa82e49b09117": {"t": 1785760751.0537198, "n": 5851, "h": "ed06a3a40a68495b", "c": "//! Provider 工具函数\n//!\n//! 包含 URL 处理、endpoint 补全等通用功能\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude 默认 base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI 默认 base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama 默认 base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// 智能补全 API endpoint\n///\n/// # 行为\n/// 1. 移除 trailing slashes\n/// 2. 检测 URL 是否已包含完整路径\n/// 3. 如果不完整,自动补全 suffix\n///\n/// # 示例\n/// ```\n/// use gcop_rs::llm::provider::utils::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. 清理 URL: 移除尾部斜杠\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. 如果已经包含期望的 suffix,直接返回\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. 检测 URL 是否包含 suffix 的部分前缀\n // 例如: url 是 \"https://api.com/v1\", suffix 是 \"v1/chat/completions\"\n // 那么我们应该只补全 \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // 从后往前检查,看 URL 是否已经包含 suffix 的前缀\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // URL 已经包含了部分 suffix,只补全剩余部分\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. 检测是否是自定义的完整 API 路径\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. 补全完整 suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// 检测 URL 是否已经是完整的 API 路径\n///\n/// 启发式规则:\n/// - 路径深度 >= 2 认为是完整路径 (如 /v1/chat, /api/generate)\n/// - 这允许用户使用完全自定义的 endpoint\nfn is_complete_api_path(url: &str) -> bool {\n // 提取路径部分 (去掉协议和域名)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // 统计非空路径段\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // 路径深度 >= 2 认为是用户自定义的完整路径\n segment_count >= 2\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_complete_endpoint_basic() {\n // 基本补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // 带尾部斜杠\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // 已经完整\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // 只有版本号,需要补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // 自定义完整路径,保持原样\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // 完整路径\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // 不完整路径\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama 本地地址\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude 代理\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix 带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix 不带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}, "22e958e272a9eeedaad96bbb6fe75d40990ade24": {"t": 1785760752.4464233, "n": 5844, "h": "4efbf2854b3e6789", "c": "//! Provider 工具函数\n//!\n//! 包含 URL 处理、endpoint 补全等通用功能\n\n/// Claude API endpoint suffix\npub const CLAUDE_API_SUFFIX: &str = \"/v1/messages\";\n\n/// OpenAI API endpoint suffix\npub const OPENAI_API_SUFFIX: &str = \"/v1/chat/completions\";\n\n/// Ollama API endpoint suffix\npub const OLLAMA_API_SUFFIX: &str = \"/api/generate\";\n\n/// Claude 默认 base URL\npub const DEFAULT_CLAUDE_BASE: &str = \"https://api.anthropic.com\";\n\n/// OpenAI 默认 base URL\npub const DEFAULT_OPENAI_BASE: &str = \"https://api.openai.com\";\n\n/// Ollama 默认 base URL\npub const DEFAULT_OLLAMA_BASE: &str = \"http://localhost:11434\";\n\n/// 智能补全 API endpoint\n///\n/// # 行为\n/// 1. 移除 trailing slashes\n/// 2. 检测 URL 是否已包含完整路径\n/// 3. 如果不完整,自动补全 suffix\n///\n/// # 示例\n/// ```\n/// use gcop_rs::llm::provider::complete_endpoint;\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/v1/chat/completions\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n///\n/// assert_eq!(\n/// complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n/// \"https://api.deepseek.com/v1/chat/completions\"\n/// );\n/// ```\npub fn complete_endpoint(base_url: &str, expected_suffix: &str) -> String {\n // 1. 清理 URL: 移除尾部斜杠\n let url = base_url.trim_end_matches('/');\n let suffix = expected_suffix.trim_start_matches('/');\n\n // 2. 如果已经包含期望的 suffix,直接返回\n if url.ends_with(suffix) {\n return url.to_string();\n }\n\n // 3. 检测 URL 是否包含 suffix 的部分前缀\n // 例如: url 是 \"https://api.com/v1\", suffix 是 \"v1/chat/completions\"\n // 那么我们应该只补全 \"/chat/completions\"\n let suffix_parts: Vec<&str> = suffix.split('/').collect();\n\n // 从后往前检查,看 URL 是否已经包含 suffix 的前缀\n for i in 0..suffix_parts.len() {\n let partial_suffix = suffix_parts[..=i].join(\"/\");\n if url.ends_with(&partial_suffix) {\n // URL 已经包含了部分 suffix,只补全剩余部分\n let remaining_suffix = &suffix_parts[i + 1..].join(\"/\");\n if remaining_suffix.is_empty() {\n return url.to_string();\n }\n return format!(\"{}/{}\", url, remaining_suffix);\n }\n }\n\n // 4. 检测是否是自定义的完整 API 路径\n if is_complete_api_path(url) {\n return url.to_string();\n }\n\n // 5. 补全完整 suffix\n format!(\"{}/{}\", url, suffix)\n}\n\n/// 检测 URL 是否已经是完整的 API 路径\n///\n/// 启发式规则:\n/// - 路径深度 >= 2 认为是完整路径 (如 /v1/chat, /api/generate)\n/// - 这允许用户使用完全自定义的 endpoint\nfn is_complete_api_path(url: &str) -> bool {\n // 提取路径部分 (去掉协议和域名)\n let path = url\n .strip_prefix(\"http://\")\n .or_else(|| url.strip_prefix(\"https://\"))\n .and_then(|rest| rest.split_once('/'))\n .map(|(_, path)| path)\n .unwrap_or(\"\");\n\n if path.is_empty() {\n return false;\n }\n\n // 统计非空路径段\n let segment_count = path.split('/').filter(|s| !s.is_empty()).count();\n\n // 路径深度 >= 2 认为是用户自定义的完整路径\n segment_count >= 2\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_complete_endpoint_basic() {\n // 基本补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_trailing_slash() {\n // 带尾部斜杠\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_already_complete() {\n // 已经完整\n assert_eq!(\n complete_endpoint(\n \"https://api.deepseek.com/v1/chat/completions\",\n \"/v1/chat/completions\"\n ),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_with_version_only() {\n // 只有版本号,需要补全\n assert_eq!(\n complete_endpoint(\"https://api.deepseek.com/v1\", \"/v1/chat/completions\"),\n \"https://api.deepseek.com/v1/chat/completions\"\n );\n }\n\n #[test]\n fn test_complete_endpoint_custom_path() {\n // 自定义完整路径,保持原样\n assert_eq!(\n complete_endpoint(\"https://custom.com/my/custom/path\", \"/v1/chat/completions\"),\n \"https://custom.com/my/custom/path\"\n );\n }\n\n #[test]\n fn test_is_complete_api_path() {\n // 完整路径\n assert!(is_complete_api_path(\"https://api.com/v1/chat\"));\n assert!(is_complete_api_path(\"http://localhost:11434/api/generate\"));\n\n // 不完整路径\n assert!(!is_complete_api_path(\"https://api.com\"));\n assert!(!is_complete_api_path(\"https://api.com/\"));\n assert!(!is_complete_api_path(\"https://api.com/v1\"));\n }\n\n #[test]\n fn test_ollama_localhost() {\n // Ollama 本地地址\n assert_eq!(\n complete_endpoint(\"http://localhost:11434\", \"/api/generate\"),\n \"http://localhost:11434/api/generate\"\n );\n }\n\n #[test]\n fn test_claude_endpoint() {\n // Claude API\n assert_eq!(\n complete_endpoint(\"https://api.anthropic.com\", \"/v1/messages\"),\n \"https://api.anthropic.com/v1/messages\"\n );\n\n // Claude 代理\n assert_eq!(\n complete_endpoint(\"https://cc.autobits.cc\", \"/v1/messages\"),\n \"https://cc.autobits.cc/v1/messages\"\n );\n }\n\n #[test]\n fn test_suffix_variations() {\n // suffix 带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"/v1/test\"),\n \"https://api.com/v1/test\"\n );\n\n // suffix 不带前导斜杠\n assert_eq!(\n complete_endpoint(\"https://api.com\", \"v1/test\"),\n \"https://api.com/v1/test\"\n );\n }\n}\n"}}}}