From c4f9d0f14321a322c67f4d26e335a2cea9028d81 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:18:53 +0100 Subject: [PATCH 1/6] docs(config): update WebToolsConfig provider names - Update doc comments to match actual implementation - Document env var fallback behavior - Change provider names from brave/searxng/google to exa/kimi_search Part of: #589 --- crates/terraphim_tinyclaw/src/config.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/terraphim_tinyclaw/src/config.rs b/crates/terraphim_tinyclaw/src/config.rs index 2b494780..1ae64f33 100644 --- a/crates/terraphim_tinyclaw/src/config.rs +++ b/crates/terraphim_tinyclaw/src/config.rs @@ -414,10 +414,15 @@ fn default_shell_timeout() -> u64 { /// Web tools configuration. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct WebToolsConfig { - /// Web search provider ("brave", "searxng", "google"). + /// Web search provider ("exa", "kimi_search"). + /// + /// If not specified, falls back to environment variables + /// (EXA_API_KEY or KIMI_API_KEY). pub search_provider: Option, - /// Web fetch mode ("readability", "raw"). + /// Web fetch mode ("raw", "readability"). + /// + /// Defaults to "raw" if not specified. pub fetch_mode: Option, } From aa3bea12f5fc34beab37c957d6a8c7fc7d9b950a Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:19:53 +0100 Subject: [PATCH 2/6] feat(tools): add from_config() to WebSearchTool - Add from_config() method that accepts Option<&WebToolsConfig> - Support provider selection via config.search_provider - Fall back to environment variables when config not provided - Extract from_env_inner() helper for code reuse - Maintain backward compatibility with existing new() and from_env() Part of: #589 --- crates/terraphim_tinyclaw/src/tools/web.rs | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/terraphim_tinyclaw/src/tools/web.rs b/crates/terraphim_tinyclaw/src/tools/web.rs index 3d1a39fe..d03e5436 100644 --- a/crates/terraphim_tinyclaw/src/tools/web.rs +++ b/crates/terraphim_tinyclaw/src/tools/web.rs @@ -248,6 +248,39 @@ impl WebSearchTool { /// Create from environment variables. pub fn from_env() -> Self { + Self::from_env_inner() + } + + /// Create from configuration. + /// + /// If config specifies a search provider, uses it. + /// Otherwise falls back to environment variables (same as `new()`). + /// + /// # Arguments + /// * `config` - Optional web tools configuration + /// + /// # Supported Providers + /// - "exa" - Exa search API + /// - "kimi_search" - Kimi search API + pub fn from_config(config: Option<&crate::config::WebToolsConfig>) -> Self { + match config { + Some(cfg) => match cfg.search_provider.as_deref() { + Some("exa") => { + let api_key = std::env::var("EXA_API_KEY").ok().filter(|k| !k.is_empty()); + Self::with_provider(Box::new(ExaProvider::new(api_key))) + } + Some("kimi_search") => { + let api_key = std::env::var("KIMI_API_KEY").ok().filter(|k| !k.is_empty()); + Self::with_provider(Box::new(KimiSearchProvider::new(api_key))) + } + Some(_) | None => Self::from_env_inner(), + }, + None => Self::from_env_inner(), + } + } + + /// Internal helper to create from environment variables. + fn from_env_inner() -> Self { // Check for Exa API key if let Ok(api_key) = std::env::var("EXA_API_KEY") { if !api_key.is_empty() { From df3fee84c02218f2a446e23f63e6f1e8e32cb455 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:20:45 +0100 Subject: [PATCH 3/6] feat(tools): add from_config() to WebFetchTool - Add from_config() method that accepts Option<&WebToolsConfig> - Support fetch mode selection via config.fetch_mode - Defaults to raw when config not provided or mode not specified - Maintain backward compatibility with existing new() and with_mode() Part of: #589 --- crates/terraphim_tinyclaw/src/tools/web.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/terraphim_tinyclaw/src/tools/web.rs b/crates/terraphim_tinyclaw/src/tools/web.rs index d03e5436..ddb11434 100644 --- a/crates/terraphim_tinyclaw/src/tools/web.rs +++ b/crates/terraphim_tinyclaw/src/tools/web.rs @@ -378,6 +378,28 @@ impl WebFetchTool { } } + /// Create from configuration. + /// + /// If config specifies a fetch mode, uses it. + /// Otherwise defaults to "raw". + /// + /// # Arguments + /// * `config` - Optional web tools configuration + /// + /// # Supported Modes + /// - "raw" - Fetch raw HTML + /// - "readability" - Extract readable content + pub fn from_config(config: Option<&crate::config::WebToolsConfig>) -> Self { + let mode = config + .and_then(|c| c.fetch_mode.clone()) + .unwrap_or_else(|| "raw".to_string()); + + Self { + client: Client::new(), + mode, + } + } + /// Fetch content from a URL. async fn fetch(&self, url: &str) -> Result { log::info!("Fetching URL: {} (mode: {})", url, self.mode); From ab228c01011dd140d719a528ab32221eaa581e27 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:23:07 +0100 Subject: [PATCH 4/6] feat(tools): wire config through to web tools - Update create_default_registry signature to accept Option<&WebToolsConfig> - Use from_config() instead of new() for web tools in registry - Update main.rs to pass config.tools.web to registry creation - Both agent mode and gateway mode now respect web_tools config Part of: #589 --- crates/terraphim_tinyclaw/src/main.rs | 12 ++++++++++-- crates/terraphim_tinyclaw/src/tools/mod.rs | 9 +++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/terraphim_tinyclaw/src/main.rs b/crates/terraphim_tinyclaw/src/main.rs index a33c8b37..950e1af4 100644 --- a/crates/terraphim_tinyclaw/src/main.rs +++ b/crates/terraphim_tinyclaw/src/main.rs @@ -169,7 +169,11 @@ async fn run_agent_mode(config: Config, system_prompt_path: Option) -> let sessions = Arc::new(tokio::sync::Mutex::new(SessionManager::new(sessions_dir))); // Create tool registry with session manager - let tools = Arc::new(create_default_registry(Some(sessions.clone()))); + let web_tools_config = config.tools.web.as_ref(); + let tools = Arc::new(create_default_registry( + Some(sessions.clone()), + web_tools_config, + )); // Create hybrid LLM router let proxy_config = ProxyClientConfig { @@ -222,7 +226,11 @@ async fn run_gateway_mode(config: Config) -> anyhow::Result<()> { let sessions = Arc::new(tokio::sync::Mutex::new(SessionManager::new(sessions_dir))); // Create tool registry with session manager - let tools = Arc::new(create_default_registry(Some(sessions.clone()))); + let web_tools_config = config.tools.web.as_ref(); + let tools = Arc::new(create_default_registry( + Some(sessions.clone()), + web_tools_config, + )); // Create hybrid LLM router let proxy_config = ProxyClientConfig { diff --git a/crates/terraphim_tinyclaw/src/tools/mod.rs b/crates/terraphim_tinyclaw/src/tools/mod.rs index c2ca1d58..3add5473 100644 --- a/crates/terraphim_tinyclaw/src/tools/mod.rs +++ b/crates/terraphim_tinyclaw/src/tools/mod.rs @@ -149,8 +149,13 @@ impl Default for ToolRegistry { } /// Create a standard tool registry with all default tools. +/// +/// # Arguments +/// * `sessions` - Optional session manager for session-aware tools +/// * `web_tools_config` - Optional web tools configuration pub fn create_default_registry( sessions: Option>>, + web_tools_config: Option<&crate::config::WebToolsConfig>, ) -> ToolRegistry { use crate::tools::edit::EditTool; use crate::tools::filesystem::FilesystemTool; @@ -163,8 +168,8 @@ pub fn create_default_registry( registry.register(Box::new(FilesystemTool::new())); registry.register(Box::new(EditTool::new())); registry.register(Box::new(ShellTool::new())); - registry.register(Box::new(WebSearchTool::new())); - registry.register(Box::new(WebFetchTool::new())); + registry.register(Box::new(WebSearchTool::from_config(web_tools_config))); + registry.register(Box::new(WebFetchTool::from_config(web_tools_config))); registry.register(Box::new(VoiceTranscribeTool::new())); // Register session tools if SessionManager is provided From 5a72d88ef97503e75eba8970e4863c89ae509748 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:24:39 +0100 Subject: [PATCH 5/6] test(tools): add unit tests for from_config methods - Add provider_name() test helper to WebSearchTool - Add tests for WebSearchTool::from_config(): - test_web_search_from_config_exa - test_web_search_from_config_kimi - test_web_search_from_config_fallback - test_web_search_from_config_unknown_provider - Add tests for WebFetchTool::from_config(): - test_web_fetch_from_config_raw - test_web_fetch_from_config_readability - test_web_fetch_from_config_fallback - test_web_fetch_from_config_none_mode Part of: #589 --- crates/terraphim_tinyclaw/src/tools/web.rs | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/crates/terraphim_tinyclaw/src/tools/web.rs b/crates/terraphim_tinyclaw/src/tools/web.rs index ddb11434..342f78fe 100644 --- a/crates/terraphim_tinyclaw/src/tools/web.rs +++ b/crates/terraphim_tinyclaw/src/tools/web.rs @@ -246,6 +246,12 @@ impl WebSearchTool { Self { provider } } + /// Get the provider name. + #[cfg(test)] + fn provider_name(&self) -> &str { + self.provider.name() + } + /// Create from environment variables. pub fn from_env() -> Self { Self::from_env_inner() @@ -547,4 +553,92 @@ mod tests { let provider = PlaceholderProvider; assert_eq!(provider.name(), "placeholder"); } + + #[test] + fn test_web_search_from_config_exa() { + let config = crate::config::WebToolsConfig { + search_provider: Some("exa".to_string()), + fetch_mode: None, + }; + + let tool = WebSearchTool::from_config(Some(&config)); + // The provider name should reflect the configured provider + assert_eq!(tool.provider_name(), "exa"); + } + + #[test] + fn test_web_search_from_config_kimi() { + let config = crate::config::WebToolsConfig { + search_provider: Some("kimi_search".to_string()), + fetch_mode: None, + }; + + let tool = WebSearchTool::from_config(Some(&config)); + assert_eq!(tool.provider_name(), "kimi_search"); + } + + #[test] + fn test_web_search_from_config_fallback() { + // When config is None, should fall back to env-based selection + // or placeholder if no env vars are set + let tool = WebSearchTool::from_config(None); + // Provider name should be one of the valid options + let name = tool.provider_name(); + assert!(name == "exa" || name == "kimi_search" || name == "placeholder"); + } + + #[test] + fn test_web_search_from_config_unknown_provider() { + // Unknown provider should fall back to env-based selection + let config = crate::config::WebToolsConfig { + search_provider: Some("unknown_provider".to_string()), + fetch_mode: None, + }; + + let tool = WebSearchTool::from_config(Some(&config)); + // Should fall back to env-based or placeholder + let name = tool.provider_name(); + assert!(name == "exa" || name == "kimi_search" || name == "placeholder"); + } + + #[test] + fn test_web_fetch_from_config_raw() { + let config = crate::config::WebToolsConfig { + search_provider: None, + fetch_mode: Some("raw".to_string()), + }; + + let tool = WebFetchTool::from_config(Some(&config)); + assert_eq!(tool.mode, "raw"); + } + + #[test] + fn test_web_fetch_from_config_readability() { + let config = crate::config::WebToolsConfig { + search_provider: None, + fetch_mode: Some("readability".to_string()), + }; + + let tool = WebFetchTool::from_config(Some(&config)); + assert_eq!(tool.mode, "readability"); + } + + #[test] + fn test_web_fetch_from_config_fallback() { + // When config is None, should default to "raw" + let tool = WebFetchTool::from_config(None); + assert_eq!(tool.mode, "raw"); + } + + #[test] + fn test_web_fetch_from_config_none_mode() { + // When config has None for fetch_mode, should default to "raw" + let config = crate::config::WebToolsConfig { + search_provider: Some("exa".to_string()), + fetch_mode: None, + }; + + let tool = WebFetchTool::from_config(Some(&config)); + assert_eq!(tool.mode, "raw"); + } } From 9dcb574b439423c694da55ef5817854e94a99c83 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Wed, 11 Mar 2026 13:25:40 +0100 Subject: [PATCH 6/6] test(tools): add integration tests for config wiring - Add test_web_tools_config_wired_to_registry - Add test_registry_without_web_tools_config - Add test_all_expected_tools_registered - Verify config values flow through to tool registry Part of: #589 --- .../terraphim_tinyclaw/tests/config_wiring.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 crates/terraphim_tinyclaw/tests/config_wiring.rs diff --git a/crates/terraphim_tinyclaw/tests/config_wiring.rs b/crates/terraphim_tinyclaw/tests/config_wiring.rs new file mode 100644 index 00000000..9ddcadba --- /dev/null +++ b/crates/terraphim_tinyclaw/tests/config_wiring.rs @@ -0,0 +1,78 @@ +//! Integration tests for config wiring to tools. +//! +//! Tests that configuration values from files are properly passed to tools. + +use terraphim_tinyclaw::config::{Config, ToolsConfig, WebToolsConfig}; +use terraphim_tinyclaw::tools::create_default_registry; + +/// Test that web tools configuration is wired through to the registry. +#[test] +fn test_web_tools_config_wired_to_registry() { + // Create a config with specific web tools settings + let config = Config { + tools: ToolsConfig { + web: Some(WebToolsConfig { + search_provider: Some("exa".to_string()), + fetch_mode: Some("readability".to_string()), + }), + ..Default::default() + }, + ..Default::default() + }; + + // Create registry with the web tools config + let web_tools_config = config.tools.web.as_ref(); + let registry = create_default_registry(None, web_tools_config); + + // Verify web_search tool is present + let web_search = registry.get("web_search"); + assert!(web_search.is_some(), "web_search tool should be registered"); + + // Verify web_fetch tool is present + let web_fetch = registry.get("web_fetch"); + assert!(web_fetch.is_some(), "web_fetch tool should be registered"); +} + +/// Test that registry works with no web tools config. +#[test] +fn test_registry_without_web_tools_config() { + // Create registry without web tools config + let registry = create_default_registry(None, None); + + // Verify web_search tool is still present (with defaults) + let web_search = registry.get("web_search"); + assert!( + web_search.is_some(), + "web_search tool should be registered even without config" + ); + + // Verify web_fetch tool is still present (with defaults) + let web_fetch = registry.get("web_fetch"); + assert!( + web_fetch.is_some(), + "web_fetch tool should be registered even without config" + ); +} + +/// Test that all expected tools are registered. +#[test] +fn test_all_expected_tools_registered() { + let registry = create_default_registry(None, None); + + let expected_tools = [ + "filesystem", + "edit", + "shell", + "web_search", + "web_fetch", + "voice_transcribe", + ]; + + for tool_name in &expected_tools { + assert!( + registry.get(tool_name).is_some(), + "Tool '{}' should be registered", + tool_name + ); + } +}