diff --git a/lib/apps/fabro-server/src/diagnostics.rs b/lib/apps/fabro-server/src/diagnostics.rs index ac161a1b0..ab1a87bd0 100644 --- a/lib/apps/fabro-server/src/diagnostics.rs +++ b/lib/apps/fabro-server/src/diagnostics.rs @@ -12,9 +12,8 @@ use fabro_model::{Catalog, ProviderId}; use fabro_redact::redact_string; use fabro_sandbox::{DockerSandboxProvider, daytona}; use fabro_static::EnvVars; -use fabro_types::settings::SearchProvider; -use fabro_types::settings::ServerAuthMethod; use fabro_types::settings::server::GithubIntegrationStrategy; +use fabro_types::settings::{SearchProvider, ServerAuthMethod}; use fabro_util::check_report::{CheckDetail, CheckResult, CheckSection, CheckStatus}; use fabro_util::dev_token::validate_dev_token_format; use fabro_util::session_secret; @@ -44,21 +43,21 @@ fn http_client_or_check( #[derive(Debug, Serialize)] pub struct DiagnosticsReport { - pub version: String, + pub version: String, pub sections: Vec, } #[derive(Debug, Clone, Serialize)] pub(crate) struct ProviderProbeReport { - pub data: Vec, + pub data: Vec, pub summary: ProviderProbeSummary, } #[derive(Debug, Clone, Serialize)] pub(crate) struct ProviderProbeResult { - pub provider: ProviderId, - pub model_id: Option, - pub status: ProviderProbeStatus, + pub provider: ProviderId, + pub model_id: Option, + pub status: ProviderProbeStatus, pub error_message: Option, #[serde(skip)] diagnostic_detail: Option, @@ -67,7 +66,7 @@ pub(crate) struct ProviderProbeResult { #[derive(Debug, Clone, Serialize)] pub(crate) struct ProviderProbeSummary { pub status: ProviderProbeStatus, - pub total: u32, + pub total: u32, pub passed: u32, pub failed: u32, } @@ -105,14 +104,14 @@ pub async fn run_all(state: &AppState) -> DiagnosticsReport { ); DiagnosticsReport { - version: FABRO_VERSION.to_string(), + version: FABRO_VERSION.to_string(), sections: vec![ CheckSection { - title: "Credentials".to_string(), + title: "Credentials".to_string(), checks: vec![llm, github, docker_sandbox, cloud_sandbox, web_search], }, CheckSection { - title: "Configuration".to_string(), + title: "Configuration".to_string(), checks: vec![crypto, check_storage_dir(state)], }, ], @@ -124,20 +123,20 @@ async fn check_llm_providers(state: &AppState) -> CheckResult { Ok(report) => report, Err(err) => { return CheckResult { - name: "LLM Providers".to_string(), - status: CheckStatus::Error, - summary: "failed to initialize".to_string(), - details: vec![CheckDetail::new(format!("{err:#}"))], + name: "LLM Providers".to_string(), + status: CheckStatus::Error, + summary: "failed to initialize".to_string(), + details: vec![CheckDetail::new(format!("{err:#}"))], remediation: Some("Check configured provider credentials".to_string()), }; } }; if report.data.is_empty() { return CheckResult { - name: "LLM Providers".to_string(), - status: CheckStatus::Error, - summary: "none configured".to_string(), - details: Vec::new(), + name: "LLM Providers".to_string(), + status: CheckStatus::Error, + summary: "none configured".to_string(), + details: Vec::new(), remediation: Some("Set at least one provider API key".to_string()), }; } @@ -159,7 +158,7 @@ async fn check_llm_providers(state: &AppState) -> CheckResult { .clone() .unwrap_or_else(|| format!("{}: {message}", result.provider)); failures.push(ProviderFailure { - provider: result.provider.to_string(), + provider: result.provider.to_string(), summary_line: short_error_line(message), }); details.push(CheckDetail::new(detail)); @@ -198,7 +197,7 @@ async fn check_llm_providers(state: &AppState) -> CheckResult { } struct ProviderFailure { - provider: String, + provider: String, summary_line: String, } @@ -355,10 +354,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { Ok(token) => token.to_string(), Err(err) => { return CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "token expired".to_string(), - details: vec![CheckDetail::new(err.to_string())], + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "token expired".to_string(), + details: vec![CheckDetail::new(err.to_string())], remediation: Some( "Run fabro install or run `fabro secret set GITHUB_TOKEN`" .to_string(), @@ -370,10 +369,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { Ok(Some(_)) => unreachable!("token strategy should not return app credentials"), Ok(None) => { return CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Warning, - summary: "not configured".to_string(), - details: Vec::new(), + name: "GitHub Token".to_string(), + status: CheckStatus::Warning, + summary: "not configured".to_string(), + details: Vec::new(), remediation: Some( "Run fabro install or run `fabro secret set GITHUB_TOKEN`".to_string(), ), @@ -382,10 +381,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { Err(err) => { let rendered = format!("{err:#}"); return CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "missing token".to_string(), - details: vec![CheckDetail::new(rendered.clone())], + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "missing token".to_string(), + details: vec![CheckDetail::new(rendered.clone())], remediation: Some(rendered), }; } @@ -407,18 +406,18 @@ async fn check_github_app(state: &AppState) -> CheckResult { return match probe { Ok(Ok(response)) if response.status().is_success() => CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Pass, - summary: "configured".to_string(), - details: Vec::new(), + name: "GitHub Token".to_string(), + status: CheckStatus::Pass, + summary: "configured".to_string(), + details: Vec::new(), remediation: None, }, Ok(Ok(response)) if response.status() == fabro_http::StatusCode::UNAUTHORIZED => { CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "token invalid".to_string(), - details: vec![CheckDetail::new(format!( + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "token invalid".to_string(), + details: vec![CheckDetail::new(format!( "GitHub returned {}", response.status() ))], @@ -428,10 +427,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { } } Ok(Ok(response)) => CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "connectivity error".to_string(), - details: vec![CheckDetail::new(format!( + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "connectivity error".to_string(), + details: vec![CheckDetail::new(format!( "GitHub returned {}", response.status() ))], @@ -440,19 +439,19 @@ async fn check_github_app(state: &AppState) -> CheckResult { ), }, Ok(Err(err)) => CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "connectivity error".to_string(), - details: vec![CheckDetail::new(err.to_string())], + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "connectivity error".to_string(), + details: vec![CheckDetail::new(err.to_string())], remediation: Some( "Check GitHub connectivity and the vault GITHUB_TOKEN".to_string(), ), }, Err(_) => CheckResult { - name: "GitHub Token".to_string(), - status: CheckStatus::Error, - summary: "timeout".to_string(), - details: vec![CheckDetail::new("GitHub probe timed out".to_string())], + name: "GitHub Token".to_string(), + status: CheckStatus::Error, + summary: "timeout".to_string(), + details: vec![CheckDetail::new("GitHub probe timed out".to_string())], remediation: Some( "Check GitHub connectivity and the vault GITHUB_TOKEN".to_string(), ), @@ -486,20 +485,20 @@ async fn check_github_app(state: &AppState) -> CheckResult { && !webhook_secret { return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Warning, - summary: "not configured".to_string(), - details: Vec::new(), + name: "GitHub App".to_string(), + status: CheckStatus::Warning, + summary: "not configured".to_string(), + details: Vec::new(), remediation: Some("Configure GitHub App settings and secrets".to_string()), }; } let Some(app_id) = app_id else { return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "missing app_id".to_string(), - details: Vec::new(), + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "missing app_id".to_string(), + details: Vec::new(), remediation: Some( "Set [server.integrations.github].app_id in settings.toml".to_string(), ), @@ -507,10 +506,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { }; let Some(private_key_raw) = private_key_raw else { return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "missing private key".to_string(), - details: Vec::new(), + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "missing private key".to_string(), + details: Vec::new(), remediation: Some("Run `fabro secret set GITHUB_APP_PRIVATE_KEY`".to_string()), }; }; @@ -519,10 +518,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { Ok(value) => value, Err(err) => { return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "private key invalid".to_string(), - details: vec![CheckDetail::new(err.clone())], + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "private key invalid".to_string(), + details: vec![CheckDetail::new(err.clone())], remediation: Some(err), }; } @@ -532,10 +531,10 @@ async fn check_github_app(state: &AppState) -> CheckResult { Ok(jwt) => jwt, Err(err) => { return CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "JWT signing failed".to_string(), - details: vec![CheckDetail::new(format!("{err:#}"))], + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "JWT signing failed".to_string(), + details: vec![CheckDetail::new(format!("{err:#}"))], remediation: Some(err.to_string()), }; } @@ -552,24 +551,24 @@ async fn check_github_app(state: &AppState) -> CheckResult { .await; match auth_result { Ok(Ok(_app)) => CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Pass, - summary: slug.unwrap_or_else(|| "configured".to_string()), - details: Vec::new(), + name: "GitHub App".to_string(), + status: CheckStatus::Pass, + summary: slug.unwrap_or_else(|| "configured".to_string()), + details: Vec::new(), remediation: None, }, Ok(Err(err)) => CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "connectivity error".to_string(), - details: vec![CheckDetail::new(format!("{err:#}"))], + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "connectivity error".to_string(), + details: vec![CheckDetail::new(format!("{err:#}"))], remediation: Some("Check GitHub App credentials and network connectivity".to_string()), }, Err(_) => CheckResult { - name: "GitHub App".to_string(), - status: CheckStatus::Error, - summary: "timeout".to_string(), - details: vec![CheckDetail::new("GitHub probe timed out".to_string())], + name: "GitHub App".to_string(), + status: CheckStatus::Error, + summary: "timeout".to_string(), + details: vec![CheckDetail::new("GitHub probe timed out".to_string())], remediation: Some("Check GitHub connectivity and credentials".to_string()), }, } @@ -605,10 +604,10 @@ where { if !enabled { return CheckResult { - name: "Docker Sandbox".to_string(), - status: CheckStatus::Pass, - summary: "disabled".to_string(), - details: vec![CheckDetail::new( + name: "Docker Sandbox".to_string(), + status: CheckStatus::Pass, + summary: "disabled".to_string(), + details: vec![CheckDetail::new( "server.sandbox.providers.docker.enabled = false".to_string(), )], remediation: None, @@ -653,10 +652,10 @@ async fn check_cloud_sandbox(state: &AppState) -> CheckResult { }; let Some(api_key) = api_key else { return CheckResult { - name: "Cloud Sandbox".to_string(), - status: CheckStatus::Warning, - summary: "recommended, not configured".to_string(), - details: Vec::new(), + name: "Cloud Sandbox".to_string(), + status: CheckStatus::Warning, + summary: "recommended, not configured".to_string(), + details: Vec::new(), remediation: Some( "Run `fabro secret set DAYTONA_API_KEY` to enable cloud sandbox execution" .to_string(), @@ -673,17 +672,17 @@ async fn check_cloud_sandbox(state: &AppState) -> CheckResult { fn cloud_sandbox_probe_check(probe: anyhow::Result) -> CheckResult { match probe { Ok(check) if check.ok() => CheckResult { - name: "Cloud Sandbox".to_string(), - status: CheckStatus::Pass, - summary: format!("Daytona configured ({})", check.key_name), - details: Vec::new(), + name: "Cloud Sandbox".to_string(), + status: CheckStatus::Pass, + summary: format!("Daytona configured ({})", check.key_name), + details: Vec::new(), remediation: None, }, Ok(check) => CheckResult { - name: "Cloud Sandbox".to_string(), - status: CheckStatus::Error, - summary: "Daytona API key is missing required scopes".to_string(), - details: vec![CheckDetail::new(format!( + name: "Cloud Sandbox".to_string(), + status: CheckStatus::Error, + summary: "Daytona API key is missing required scopes".to_string(), + details: vec![CheckDetail::new(format!( "missing: {}", check.missing_display() ))], @@ -696,10 +695,10 @@ fn cloud_sandbox_probe_check(probe: anyhow::Result) -> Err(err) => { if let Some(timeout) = err.downcast_ref::() { return CheckResult { - name: "Cloud Sandbox".to_string(), - status: CheckStatus::Error, - summary: format!("timeout ({:?})", timeout.timeout()), - details: vec![CheckDetail::new("Daytona probe timed out".to_string())], + name: "Cloud Sandbox".to_string(), + status: CheckStatus::Error, + summary: format!("timeout ({:?})", timeout.timeout()), + details: vec![CheckDetail::new("Daytona probe timed out".to_string())], remediation: Some( "Verify DAYTONA_API_KEY value and Daytona reachability".to_string(), ), @@ -707,10 +706,10 @@ fn cloud_sandbox_probe_check(probe: anyhow::Result) -> } CheckResult { - name: "Cloud Sandbox".to_string(), - status: CheckStatus::Error, - summary: "Daytona credential rejected".to_string(), - details: vec![CheckDetail::new(format!("{err:#}"))], + name: "Cloud Sandbox".to_string(), + status: CheckStatus::Error, + summary: "Daytona credential rejected".to_string(), + details: vec![CheckDetail::new(format!("{err:#}"))], remediation: Some( "Verify DAYTONA_API_KEY value and Daytona reachability".to_string(), ), @@ -781,10 +780,10 @@ async fn check_brave_search(state: &AppState) -> CheckResult { }; let Some(api_key) = api_key else { return CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: "brave: optional, not configured".to_string(), - details: Vec::new(), + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: "brave: optional, not configured".to_string(), + details: Vec::new(), remediation: Some( "Run `fabro secret set BRAVE_SEARCH_API_KEY` to enable web search".to_string(), ), @@ -816,10 +815,10 @@ async fn check_venice_search(state: &AppState) -> CheckResult { }; let Some(api_key) = api_key else { return CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: "venice: optional, not configured".to_string(), - details: Vec::new(), + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: "venice: optional, not configured".to_string(), + details: Vec::new(), remediation: Some( "Run `fabro secret set VENICE_API_KEY` to enable web search".to_string(), ), @@ -851,31 +850,31 @@ fn match_web_search_probe( ) -> CheckResult { match probe { Ok(Ok(response)) if response.status().is_success() => CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Pass, - summary: format!("{provider}: configured and reachable"), - details: Vec::new(), + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Pass, + summary: format!("{provider}: configured and reachable"), + details: Vec::new(), remediation: None, }, Ok(Ok(response)) => CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: format!("{provider}: HTTP {}", response.status()), - details: Vec::new(), + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: format!("{provider}: HTTP {}", response.status()), + details: Vec::new(), remediation: Some(format!("Check {secret_name} and network connectivity")), }, Ok(Err(err)) => CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: format!("{provider}: connectivity error"), - details: vec![CheckDetail::new(format!("{err:#}"))], + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: format!("{provider}: connectivity error"), + details: vec![CheckDetail::new(format!("{err:#}"))], remediation: Some(format!("Check {secret_name} and network connectivity")), }, Err(_) => CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: format!("{provider}: timeout"), - details: vec![CheckDetail::new(format!( + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: format!("{provider}: timeout"), + details: vec![CheckDetail::new(format!( "Web Search ({provider}) probe timed out" ))], remediation: Some(format!("Check {secret_name} and network connectivity")), @@ -956,10 +955,10 @@ async fn diagnostic_secret( name: &str, ) -> Result, CheckResult> { state.vault_secret(name).await.map_err(|err| CheckResult { - name: check_name.to_string(), - status: CheckStatus::Error, - summary: "secret store unavailable".to_string(), - details: vec![CheckDetail::new(err.to_string())], + name: check_name.to_string(), + status: CheckStatus::Error, + summary: "secret store unavailable".to_string(), + details: vec![CheckDetail::new(err.to_string())], remediation: Some("Check the Fabro database and retry".to_string()), }) } diff --git a/lib/components/fabro-agent/src/cli.rs b/lib/components/fabro-agent/src/cli.rs index e16d8fa2f..63c485f20 100644 --- a/lib/components/fabro-agent/src/cli.rs +++ b/lib/components/fabro-agent/src/cli.rs @@ -44,8 +44,8 @@ use crate::{ fn cli_tool_secrets() -> ToolSecrets { ToolSecrets { brave_search_api_key: std::env::var(EnvVars::BRAVE_SEARCH_API_KEY).ok(), - venice_api_key: std::env::var(EnvVars::VENICE_API_KEY).ok(), - search: search_settings_from_disk(), + venice_api_key: std::env::var(EnvVars::VENICE_API_KEY).ok(), + search: search_settings_from_disk(), } } diff --git a/lib/components/fabro-agent/src/config.rs b/lib/components/fabro-agent/src/config.rs index 44dce5af0..44a9db968 100644 --- a/lib/components/fabro-agent/src/config.rs +++ b/lib/components/fabro-agent/src/config.rs @@ -104,8 +104,8 @@ impl ToolHookCallback for ToolApprovalAdapter { #[derive(Clone, Default, PartialEq, Eq)] pub struct ToolSecrets { pub brave_search_api_key: Option, - pub venice_api_key: Option, - pub search: SearchIntegrationSettings, + pub venice_api_key: Option, + pub search: SearchIntegrationSettings, } impl std::fmt::Debug for ToolSecrets { @@ -125,8 +125,8 @@ impl std::fmt::Debug for ToolSecrets { #[derive(Clone, Debug, PartialEq, Eq)] pub struct NativeToolOptions { pub default_command_timeout_ms: u64, - pub max_command_timeout_ms: u64, - pub secrets: ToolSecrets, + pub max_command_timeout_ms: u64, + pub secrets: ToolSecrets, } impl NativeToolOptions { @@ -157,8 +157,8 @@ impl Default for NativeToolOptions { fn default() -> Self { Self { default_command_timeout_ms: 10_000, - max_command_timeout_ms: 600_000, - secrets: ToolSecrets::default(), + max_command_timeout_ms: 600_000, + secrets: ToolSecrets::default(), } } } @@ -447,12 +447,9 @@ mod tests { let approval: ToolApprovalFn = Arc::new(|_name, _args| Err("denied".to_string())); let adapter = ToolApprovalAdapter(approval); let decision = adapter.pre_tool_use("shell", &serde_json::json!({})).await; - assert_eq!( - decision, - ToolHookDecision::Block { - reason: "denied".to_string(), - } - ); + assert_eq!(decision, ToolHookDecision::Block { + reason: "denied".to_string(), + }); } #[tokio::test] diff --git a/lib/components/fabro-agent/src/profiles/claude5.rs b/lib/components/fabro-agent/src/profiles/claude5.rs index 97d35a187..97cdeff1d 100644 --- a/lib/components/fabro-agent/src/profiles/claude5.rs +++ b/lib/components/fabro-agent/src/profiles/claude5.rs @@ -174,20 +174,17 @@ mod tests { let profile = Claude5Profile::new("claude-sonnet-5"); let mut names = profile.tool_registry().names(); names.sort(); - assert_eq!( - names, - vec![ - "Bash", - "Edit", - "Read", - "TaskCreate", - "TaskGet", - "TaskList", - "TaskUpdate", - "WebFetch", - "Write", - ] - ); + assert_eq!(names, vec![ + "Bash", + "Edit", + "Read", + "TaskCreate", + "TaskGet", + "TaskList", + "TaskUpdate", + "WebFetch", + "Write", + ]); assert!(!names.iter().any(|name| name == "Grep" || name == "Glob")); } diff --git a/lib/components/fabro-agent/src/profiles/claude5_tools.rs b/lib/components/fabro-agent/src/profiles/claude5_tools.rs index 9366194cd..140e8502a 100644 --- a/lib/components/fabro-agent/src/profiles/claude5_tools.rs +++ b/lib/components/fabro-agent/src/profiles/claude5_tools.rs @@ -99,7 +99,7 @@ pub(crate) fn make_bash_tool(options: &NativeToolOptions) -> RegisteredTool { "additionalProperties": false }), ), - executor: Arc::new(move |args, ctx| { + executor: Arc::new(move |args, ctx| { Box::pin(async move { let command = tools::required_str(&args, "command")?; let timeout_ms = args @@ -110,7 +110,7 @@ pub(crate) fn make_bash_tool(options: &NativeToolOptions) -> RegisteredTool { tools::run_shell_command(&ctx, command, timeout_ms, None).await }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -221,7 +221,7 @@ pub(crate) fn make_agent_tool( "additionalProperties": false }), ), - executor: Arc::new(move |args, ctx| { + executor: Arc::new(move |args, ctx| { let supervisor = supervisor.clone(); let session_factory = session_factory.clone(); Box::pin(async move { @@ -259,7 +259,7 @@ pub(crate) fn make_agent_tool( } }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -330,7 +330,7 @@ pub(crate) fn make_task_output_tool(supervisor: SubAgentSupervisor) -> Registere "additionalProperties": false }), ), - executor: Arc::new(move |args, ctx| { + executor: Arc::new(move |args, ctx| { let supervisor = supervisor.clone(); Box::pin(async move { let task_id = tools::required_str(&args, "task_id")?; @@ -384,7 +384,7 @@ pub(crate) fn make_task_output_tool(supervisor: SubAgentSupervisor) -> Registere } }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -406,7 +406,7 @@ pub(crate) fn make_task_stop_tool(supervisor: SubAgentSupervisor) -> RegisteredT "additionalProperties": false }), ), - executor: Arc::new(move |args, _ctx| { + executor: Arc::new(move |args, _ctx| { let supervisor = supervisor.clone(); Box::pin(async move { let task_id = tools::required_str(&args, "task_id")?; @@ -417,7 +417,7 @@ pub(crate) fn make_task_stop_tool(supervisor: SubAgentSupervisor) -> RegisteredT Ok(format!("Agent {task_id} stopped.")) }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -448,7 +448,7 @@ pub(crate) fn make_send_message_tool(supervisor: SubAgentSupervisor) -> Register "additionalProperties": false }), ), - executor: Arc::new(move |args, _ctx| { + executor: Arc::new(move |args, _ctx| { let supervisor = supervisor.clone(); Box::pin(async move { let recipient = tools::required_str(&args, "to")?; @@ -459,7 +459,7 @@ pub(crate) fn make_send_message_tool(supervisor: SubAgentSupervisor) -> Register Ok(format!("Message sent to agent {recipient}.")) }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -468,6 +468,7 @@ mod tests { use std::collections::BTreeSet; use std::sync::Mutex; + use fabro_types::settings::VeniceSearchEngine; use serde_json::json; use tokio_util::sync::CancellationToken; @@ -478,7 +479,6 @@ mod tests { use crate::todo_tools::{ make_task_create_tool, make_task_get_tool, make_task_list_tool, make_task_update_tool, }; - use fabro_types::settings::VeniceSearchEngine; fn property_names(tool: &RegisteredTool) -> BTreeSet<&str> { tool.definition.parameters["properties"] @@ -513,12 +513,12 @@ mod tests { fn context() -> ToolContext { ToolContext { - env: Arc::new(MockSandbox::default()) as Arc, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: Some("root".to_string()), - root_session_id: Some("root".to_string()), - tool_call_id: Some("call".to_string()), + env: Arc::new(MockSandbox::default()) as Arc, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: Some("root".to_string()), + root_session_id: Some("root".to_string()), + tool_call_id: Some("call".to_string()), agent_event_emitter: None, } } @@ -526,16 +526,13 @@ mod tests { #[test] fn core_adapter_schemas_match_the_claude5_contract() { let options = NativeToolOptions::for_profile(fabro_model::AgentProfileKind::Claude5); - assert_schema( - &make_read_tool(), - &["file_path", "limit", "offset"], - &["file_path"], - ); - assert_schema( - &make_write_tool(), - &["content", "file_path"], - &["content", "file_path"], - ); + assert_schema(&make_read_tool(), &["file_path", "limit", "offset"], &[ + "file_path", + ]); + assert_schema(&make_write_tool(), &["content", "file_path"], &[ + "content", + "file_path", + ]); assert_schema( &make_edit_tool(), &["file_path", "new_string", "old_string", "replace_all"], @@ -547,11 +544,9 @@ mod tests { bash.definition.parameters["properties"]["timeout"]["maximum"], 600_000 ); - assert_schema( - &make_web_fetch_tool(None), - &["prompt", "url"], - &["prompt", "url"], - ); + assert_schema(&make_web_fetch_tool(None), &["prompt", "url"], &[ + "prompt", "url", + ]); assert_schema( &make_web_search_tool(SearchBackend::brave("key".to_string())), &["query"], @@ -613,17 +608,13 @@ mod tests { &["block", "task_id", "timeout"], &["block", "task_id", "timeout"], ); - assert_schema( - &make_task_stop_tool(supervisor.clone()), - &["task_id"], - &["task_id"], - ); + assert_schema(&make_task_stop_tool(supervisor.clone()), &["task_id"], &[ + "task_id", + ]); let send_message = make_send_message_tool(supervisor); - assert_schema( - &send_message, - &["message", "summary", "to"], - &["message", "to"], - ); + assert_schema(&send_message, &["message", "summary", "to"], &[ + "message", "to", + ]); assert!( send_message .definition diff --git a/lib/components/fabro-agent/src/tools.rs b/lib/components/fabro-agent/src/tools.rs index b6515a4a5..9cbeb571b 100644 --- a/lib/components/fabro-agent/src/tools.rs +++ b/lib/components/fabro-agent/src/tools.rs @@ -12,9 +12,9 @@ use tokio::task; use crate::config::NativeToolOptions; use crate::sandbox::{ExecStreamingResult, GrepOptions}; -use crate::web_search::{SearchBackend, make_web_search_tool}; use crate::tool_registry::{RegisteredTool, ToolContext, ToolRegistry, ToolSource}; use crate::types::AgentEvent; +use crate::web_search::{SearchBackend, make_web_search_tool}; const MAX_WEB_FETCH_BYTES: usize = 100 * 1024; const MAX_READ_MANY_FILES_CONCURRENCY: usize = 8; @@ -23,7 +23,7 @@ pub(crate) const DEFAULT_READ_LINES: usize = 2000; /// Configuration for the optional LLM-based summarizer used by `web_fetch`. #[derive(Clone)] pub struct WebFetchSummarizer { - pub client: Client, + pub client: Client, pub model_id: ModelHandle, } @@ -512,9 +512,9 @@ pub fn make_glob_tool() -> RegisteredTool { pub(crate) fn make_read_many_files_tool() -> RegisteredTool { RegisteredTool { definition: ToolDefinition { - name: "read_many_files".into(), + name: "read_many_files".into(), description: "Read multiple files at once".into(), - parameters: serde_json::json!({ + parameters: serde_json::json!({ "type": "object", "properties": { "paths": { @@ -526,7 +526,7 @@ pub(crate) fn make_read_many_files_tool() -> RegisteredTool { "required": ["paths"] }), }, - executor: Arc::new(|args, ctx| { + executor: Arc::new(|args, ctx| { Box::pin(async move { let paths: Vec = args["paths"] .as_array() @@ -565,7 +565,7 @@ pub(crate) fn make_read_many_files_tool() -> RegisteredTool { Ok(output) }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -573,9 +573,9 @@ pub(crate) fn make_read_many_files_tool() -> RegisteredTool { pub(crate) fn make_list_dir_tool() -> RegisteredTool { RegisteredTool { definition: ToolDefinition { - name: "list_dir".into(), + name: "list_dir".into(), description: "List directory contents with depth control".into(), - parameters: serde_json::json!({ + parameters: serde_json::json!({ "type": "object", "properties": { "path": {"type": "string", "description": "Directory path to list"}, @@ -584,7 +584,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool { "required": ["path"] }), }, - executor: Arc::new(|args, ctx| { + executor: Arc::new(|args, ctx| { Box::pin(async move { let path = required_str(&args, "path")?; let depth = optional_usize_arg(&args, "depth")?; @@ -607,7 +607,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool { Ok(lines.join("\n")) }) }), - source: ToolSource::Native, + source: ToolSource::Native, } } @@ -725,7 +725,6 @@ mod tests { use super::*; use crate::config::{NativeToolOptions, SessionOptions, ToolSecrets}; - use crate::web_search::make_web_search_tool_with_api_key; use crate::event::{Emitter, SessionBoundEmitter}; use crate::local_sandbox::LocalSandbox; use crate::sandbox::*; @@ -733,6 +732,7 @@ mod tests { use crate::tool_registry::ToolContext; use crate::truncation; use crate::types::SessionEvent; + use crate::web_search::make_web_search_tool_with_api_key; #[test] fn core_tool_descriptions_include_actionable_guidance() { @@ -823,18 +823,15 @@ mod tests { files, ..Default::default() }); - let result = (tool.executor)( - serde_json::json!({"file_path": "/test.txt"}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({"file_path": "/test.txt"}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; assert_eq!(result.unwrap(), "1 | hello\n2 | world\n"); } @@ -851,18 +848,15 @@ mod tests { ..Default::default() }); - let result = (tool.executor)( - serde_json::json!({"file_path": "/test.txt"}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({"file_path": "/test.txt"}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await .unwrap(); @@ -903,12 +897,12 @@ mod tests { let result = (tool.executor)( serde_json::json!({"file_path": "/out.txt", "content": "hello"}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -938,12 +932,12 @@ mod tests { "new_string": "goodbye" }), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1033,12 +1027,12 @@ mod tests { "replace_all": true }), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1066,12 +1060,12 @@ mod tests { "new_string": "goodbye" }), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1100,8 +1094,8 @@ mod tests { root_session_id: Some("test-session".to_string()), tool_call_id: Some("call_1".to_string()), agent_event_emitter: Some(Arc::new(SessionBoundEmitter { - emitter: emitter.clone(), - session_id: "test-session".to_string(), + emitter: emitter.clone(), + session_id: "test-session".to_string(), tool_call_id: Some("call_1".to_string()), })), ..shell_context(env) @@ -1130,9 +1124,9 @@ mod tests { async fn shell_success_returns_ok_with_metadata_and_separate_streams() { let tool = make_shell_tool(); let env: Arc = mock_sandbox_with(ExecResult { - stdout: "hello".into(), - stderr: "a warning".into(), - exit_code: Some(0), + stdout: "hello".into(), + stderr: "a warning".into(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 10, }); @@ -1154,9 +1148,9 @@ mod tests { async fn shell_forwards_command_without_stream_redirection_wrapper() { let tool = make_shell_tool(); let env = mock_sandbox_with(ExecResult { - stdout: String::new(), - stderr: String::new(), - exit_code: Some(0), + stdout: String::new(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 1, }); @@ -1182,12 +1176,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"command": "sleep 1", "timeout_ms": 5000}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1200,9 +1194,9 @@ mod tests { let tool = make_shell_tool(); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "error".into(), - stderr: String::new(), - exit_code: Some(1), + stdout: "error".into(), + stderr: String::new(), + exit_code: Some(1), termination: CommandTermination::Exited, duration_ms: 10, }, @@ -1221,9 +1215,9 @@ mod tests { async fn shell_timeout_returns_error_with_partial_output() { let tool = make_shell_tool(); let env: Arc = mock_sandbox_with(ExecResult { - stdout: "partial".into(), - stderr: String::new(), - exit_code: None, + stdout: "partial".into(), + stderr: String::new(), + exit_code: None, termination: CommandTermination::TimedOut, duration_ms: 10000, }); @@ -1243,9 +1237,9 @@ mod tests { async fn shell_cancellation_returns_error_with_partial_output() { let tool = make_shell_tool(); let env: Arc = mock_sandbox_with(ExecResult { - stdout: "partial".into(), - stderr: String::new(), - exit_code: None, + stdout: "partial".into(), + stderr: String::new(), + exit_code: None, termination: CommandTermination::Cancelled, duration_ms: 42, }); @@ -1297,9 +1291,9 @@ mod tests { async fn shell_emits_process_event_with_typed_outcome_and_redacted_tails() { let tool = make_shell_tool(); let env: Arc = mock_sandbox_with(ExecResult { - stdout: "out".into(), - stderr: "boom key=AKIAYRWQG5EJLPZLBYNP".into(), - exit_code: Some(7), + stdout: "out".into(), + stderr: "boom key=AKIAYRWQG5EJLPZLBYNP".into(), + exit_code: Some(7), termination: CommandTermination::Exited, duration_ms: 12, }); @@ -1339,9 +1333,9 @@ mod tests { let tool = make_shell_tool(); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "interleaved".into(), - stderr: String::new(), - exit_code: Some(0), + stdout: "interleaved".into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 5, }, @@ -1457,12 +1451,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"command": "echo $MY_KEY"}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: Some(Arc::new(crate::StaticEnvProvider(tool_env.clone()))), - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: Some(Arc::new(crate::StaticEnvProvider(tool_env.clone()))), + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1505,12 +1499,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"command": "echo $GITHUB_TOKEN"}), ToolContext { - env: env.clone(), - cancel: CancellationToken::new(), - tool_env_provider: Some(provider.clone()), - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env.clone(), + cancel: CancellationToken::new(), + tool_env_provider: Some(provider.clone()), + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1526,12 +1520,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"command": "echo $GITHUB_TOKEN"}), ToolContext { - env: env.clone(), - cancel: CancellationToken::new(), - tool_env_provider: Some(provider), - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env.clone(), + cancel: CancellationToken::new(), + tool_env_provider: Some(provider), + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1581,18 +1575,15 @@ mod tests { ..Default::default() }); - let result = (tool.executor)( - serde_json::json!({"file_path": "/test.txt"}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: Some(Arc::new(FailingToolEnvProvider)), - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({"file_path": "/test.txt"}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: Some(Arc::new(FailingToolEnvProvider)), + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; assert_eq!(result.unwrap(), "1 | hello\n"); @@ -1603,18 +1594,15 @@ mod tests { let tool = make_shell_tool(); let env = Arc::new(MockSandbox::default()); let env_clone: Arc = env.clone(); - let _result = (tool.executor)( - serde_json::json!({"command": "echo hello"}), - ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let _result = (tool.executor)(serde_json::json!({"command": "echo hello"}), ToolContext { + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; let captured = env.captured_env_vars.lock().unwrap().clone(); assert_eq!(captured, None); @@ -1625,9 +1613,9 @@ mod tests { let tool = make_web_fetch_tool(None); let env = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "fetched content".into(), - stderr: String::new(), - exit_code: Some(0), + stdout: "fetched content".into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -1639,12 +1627,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"url": "https://example.com"}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: Some(Arc::new(crate::StaticEnvProvider(tool_env.clone()))), - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: Some(Arc::new(crate::StaticEnvProvider(tool_env.clone()))), + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1663,18 +1651,15 @@ mod tests { ], ..Default::default() }); - let result = (tool.executor)( - serde_json::json!({"pattern": "fn"}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({"pattern": "fn"}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; let output = result.unwrap(); assert!(output.contains("src/main.rs:10:fn main()")); @@ -1688,18 +1673,15 @@ mod tests { glob_results: vec!["src/main.rs".into(), "src/lib.rs".into()], ..Default::default() }); - let result = (tool.executor)( - serde_json::json!({"pattern": "src/**/*.rs"}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({"pattern": "src/**/*.rs"}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; let output = result.unwrap(); assert!(output.contains("src/main.rs")); @@ -1719,18 +1701,15 @@ mod tests { async fn web_search_missing_query_returns_error() { let tool = make_web_search_tool_with_api_key("fake-key".into()); let env: Arc = Arc::new(MockSandbox::default()); - let result = (tool.executor)( - serde_json::json!({}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; let err = result.unwrap_err(); assert!( @@ -1756,18 +1735,15 @@ mod tests { .get("web_search") .expect("web_search should be registered"); let env: Arc = Arc::new(MockSandbox::default()); - let result = (tool.executor)( - serde_json::json!({}), - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + let result = (tool.executor)(serde_json::json!({}), ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await; let err = result.unwrap_err(); @@ -1782,9 +1758,9 @@ mod tests { let tool = make_web_fetch_tool(None); let env = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "

hello

".into(), - stderr: String::new(), - exit_code: Some(0), + stdout: "

hello

".into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -1794,12 +1770,12 @@ mod tests { let result = (tool.executor)( serde_json::json!({"url": "https://example.com"}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1860,12 +1836,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"url": "https://example.com", "timeout_ms": 15000}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1886,12 +1862,12 @@ mod tests { let _result = (tool.executor)( serde_json::json!({"url": "https://example.com", "timeout_ms": 120_000}), ToolContext { - env: env_clone, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, + env: env_clone, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, agent_event_emitter: None, }, ) @@ -1910,9 +1886,9 @@ mod tests { let tool = make_web_fetch_tool(None); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: large_content, - stderr: String::new(), - exit_code: Some(0), + stdout: large_content, + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -1941,9 +1917,9 @@ mod tests { let tool = make_web_fetch_tool(None); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: String::new(), - stderr: "curl: (6) Could not resolve host".into(), - exit_code: Some(6), + stdout: String::new(), + stderr: "curl: (6) Could not resolve host".into(), + exit_code: Some(6), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -1985,16 +1961,17 @@ mod tests { client, model_id: ModelHandle::ByName { provider: ProviderId::anthropic(), - model: "mock-model".to_string(), + model: "mock-model".to_string(), }, }; let tool = make_web_fetch_tool(Some(summarizer)); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "

Lots of content about Rust...

".into(), - stderr: String::new(), - exit_code: Some(0), + stdout: "

Lots of content about Rust...

" + .into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -2025,10 +2002,11 @@ mod tests { let tool = make_web_fetch_tool(None); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "

Rust is a systems programming language.

" - .into(), - stderr: String::new(), - exit_code: Some(0), + stdout: + "

Rust is a systems programming language.

" + .into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, @@ -2068,7 +2046,7 @@ mod tests { // "other_provider" is the default — it rejects all requests. let default_provider: Arc = Arc::new(MockErrorProvider { error: LlmError::Provider { - kind: ProviderErrorKind::NotFound, + kind: ProviderErrorKind::NotFound, detail: Box::new(ProviderErrorDetail::new( "model not found", "other_provider", @@ -2092,16 +2070,16 @@ mod tests { client, model_id: ModelHandle::ByName { provider: ProviderId::anthropic(), - model: "target-model".to_string(), + model: "target-model".to_string(), }, }; let tool = make_web_fetch_tool(Some(summarizer)); let env: Arc = Arc::new(MockSandbox { exec_result: ExecResult { - stdout: "

Page content

".into(), - stderr: String::new(), - exit_code: Some(0), + stdout: "

Page content

".into(), + stderr: String::new(), + exit_code: Some(0), termination: CommandTermination::Exited, duration_ms: 100, }, diff --git a/lib/components/fabro-agent/src/web_search.rs b/lib/components/fabro-agent/src/web_search.rs index 9be79c811..11fe4c7bf 100644 --- a/lib/components/fabro-agent/src/web_search.rs +++ b/lib/components/fabro-agent/src/web_search.rs @@ -24,12 +24,12 @@ const MAX_RESULTS: u64 = 20; #[derive(Clone, Debug)] pub(crate) enum SearchBackend { Brave { - api_key: String, + api_key: String, search_url: String, }, Venice { - api_key: String, - engine: VeniceSearchEngine, + api_key: String, + engine: VeniceSearchEngine, search_url: String, }, } @@ -207,10 +207,10 @@ fn format_brave_results(body: &serde_json::Value) -> String { results .iter() .map(|result| SearchHit { - title: json_str(result, "title"), - url: json_str(result, "url"), + title: json_str(result, "title"), + url: json_str(result, "url"), description: json_str(result, "description"), - date: None, + date: None, }) .collect() })) @@ -222,20 +222,20 @@ fn format_venice_results(body: &serde_json::Value) -> String { results .iter() .map(|result| SearchHit { - title: json_str(result, "title"), - url: json_str(result, "url"), + title: json_str(result, "title"), + url: json_str(result, "url"), description: json_str(result, "content"), - date: optional_json_str(result, "date"), + date: optional_json_str(result, "date"), }) .collect() })) } struct SearchHit { - title: String, - url: String, + title: String, + url: String, description: String, - date: Option, + date: Option, } fn format_hits(hits: Option>) -> String { @@ -364,8 +364,8 @@ mod tests { fn secrets(brave: Option<&str>, venice: Option<&str>, provider: SearchProvider) -> ToolSecrets { ToolSecrets { brave_search_api_key: brave.map(str::to_string), - venice_api_key: venice.map(str::to_string), - search: SearchIntegrationSettings { + venice_api_key: venice.map(str::to_string), + search: SearchIntegrationSettings { provider, venice_engine: VeniceSearchEngine::Brave, }, @@ -374,18 +374,15 @@ mod tests { async fn execute(tool: &RegisteredTool, args: serde_json::Value) -> Result { let env: Arc = Arc::new(MockSandbox::default()); - (tool.executor)( - args, - ToolContext { - env, - cancel: CancellationToken::new(), - tool_env_provider: None, - session_id: None, - root_session_id: None, - tool_call_id: None, - agent_event_emitter: None, - }, - ) + (tool.executor)(args, ToolContext { + env, + cancel: CancellationToken::new(), + tool_env_provider: None, + session_id: None, + root_session_id: None, + tool_call_id: None, + agent_event_emitter: None, + }) .await } diff --git a/lib/components/fabro-agent/tests/it/parity_matrix.rs b/lib/components/fabro-agent/tests/it/parity_matrix.rs index 486526f07..3f8f9e55e 100644 --- a/lib/components/fabro-agent/tests/it/parity_matrix.rs +++ b/lib/components/fabro-agent/tests/it/parity_matrix.rs @@ -26,22 +26,22 @@ type Provider = ProviderId; #[derive(Clone)] struct OpenAiTwinOptions { base_url: String, - api_key: String, + api_key: String, } fn summarizer_model_id(provider: &Provider) -> ModelHandle { match provider.as_str() { ProviderId::OPENAI | "moonshot" | "zai" | "minimax" | "inception" => ModelHandle::ByName { provider: ProviderId::openai(), - model: "gpt-5.4-mini".to_string(), + model: "gpt-5.4-mini".to_string(), }, ProviderId::GEMINI => ModelHandle::ByName { provider: ProviderId::gemini(), - model: "gemini-3-flash-preview".to_string(), + model: "gemini-3-flash-preview".to_string(), }, ProviderId::ANTHROPIC => ModelHandle::ByName { provider: ProviderId::anthropic(), - model: "claude-haiku-4-5".to_string(), + model: "claude-haiku-4-5".to_string(), }, other => panic!("unexpected provider {other}"), } @@ -49,7 +49,7 @@ fn summarizer_model_id(provider: &Provider) -> ModelHandle { fn build_summarizer(provider: &Provider, client: &Client) -> WebFetchSummarizer { WebFetchSummarizer { - client: client.clone(), + client: client.clone(), model_id: summarizer_model_id(provider), } } @@ -170,13 +170,12 @@ fn make_openai_compatible_twin_session( // twin fixture so the profile can resolve the same OpenAI-compatible // codec that the manually registered adapter uses. let mut settings = LlmCatalogSettings::default(); - settings.providers.insert( - provider.to_string(), - ProviderCatalogSettings { + settings + .providers + .insert(provider.to_string(), ProviderCatalogSettings { enabled: Some(true), ..ProviderCatalogSettings::default() - }, - ); + }); let catalog = Arc::new( Catalog::from_builtin_with_overrides(&settings) .expect("OpenAI-compatible twin catalog should build"), diff --git a/lib/components/fabro-workflow/src/pipeline/initialize.rs b/lib/components/fabro-workflow/src/pipeline/initialize.rs index cdb8a6520..275e20527 100644 --- a/lib/components/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/components/fabro-workflow/src/pipeline/initialize.rs @@ -238,8 +238,8 @@ async fn tool_secrets_from_configured_sources(vault: &Arc>) - let vault = vault.read().await; ToolSecrets { brave_search_api_key: vault.get(EnvVars::BRAVE_SEARCH_API_KEY).map(str::to_string), - venice_api_key: vault.get(EnvVars::VENICE_API_KEY).map(str::to_string), - search: fabro_agent::search_settings_from_disk(), + venice_api_key: vault.get(EnvVars::VENICE_API_KEY).map(str::to_string), + search: fabro_agent::search_settings_from_disk(), } } diff --git a/lib/foundation/fabro-api/build.rs b/lib/foundation/fabro-api/build.rs index 5a3ff06ae..577746fa4 100644 --- a/lib/foundation/fabro-api/build.rs +++ b/lib/foundation/fabro-api/build.rs @@ -691,11 +691,8 @@ fn main() { ("AskFabro", "fabro_types::AskFabro", &[]), ("Automation", "fabro_automation::Automation", &[]), ("AutomationRef", "fabro_types::AutomationRef", &[]), - ( - "AutomationTarget", - "fabro_automation::AutomationTarget", - &[], - ), + ("AutomationTarget", "fabro_automation::AutomationTarget", &[ + ]), ( "AutomationTrigger", "fabro_automation::AutomationTrigger", diff --git a/lib/foundation/fabro-config/src/layers/server.rs b/lib/foundation/fabro-config/src/layers/server.rs index 7d4a96f5b..867a8da22 100644 --- a/lib/foundation/fabro-config/src/layers/server.rs +++ b/lib/foundation/fabro-config/src/layers/server.rs @@ -13,25 +13,25 @@ use super::LogFilter; #[serde(deny_unknown_fields)] pub struct ServerLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub listen: Option, + pub listen: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub api: Option, + pub api: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub web: Option, + pub web: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub auth: Option, + pub auth: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub sandbox: Option, + pub sandbox: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub storage: Option, + pub storage: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub artifacts: Option, + pub artifacts: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub slatedb: Option, + pub slatedb: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub scheduler: Option, + pub scheduler: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub logging: Option, + pub logging: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub integrations: Option, } @@ -67,7 +67,7 @@ pub struct ServerWebLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub url: Option, + pub url: Option, } /// `[server.auth]` — cohesive server auth surface. @@ -81,7 +81,7 @@ pub struct ServerAuthLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub methods: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] - pub github: Option, + pub github: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -103,9 +103,9 @@ pub struct ServerSandboxLayer { #[serde(deny_unknown_fields)] pub struct ServerSandboxProvidersLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub local: Option, + pub local: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub docker: Option, + pub docker: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub daytona: Option, } @@ -132,11 +132,11 @@ pub struct ServerArtifactsLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub provider: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub prefix: Option, + pub prefix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub local: Option, + pub local: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub s3: Option, + pub s3: Option, } /// `[server.slatedb]` — SlateDB bottomless storage plus tunables. @@ -144,17 +144,17 @@ pub struct ServerArtifactsLayer { #[serde(deny_unknown_fields)] pub struct ServerSlateDbLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub provider: Option, + pub provider: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub prefix: Option, + pub prefix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub flush_interval: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub local: Option, + pub local: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub s3: Option, + pub s3: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub disk_cache: Option, + pub disk_cache: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -170,11 +170,11 @@ pub struct ObjectStoreLocalLayer { #[serde(deny_unknown_fields)] pub struct ObjectStoreS3Layer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub bucket: Option, + pub bucket: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub region: Option, + pub region: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub endpoint: Option, + pub endpoint: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub path_style: Option, } @@ -192,7 +192,7 @@ pub struct ServerSchedulerLayer { #[serde(deny_unknown_fields)] pub struct ServerLoggingLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub level: Option, + pub level: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub destination: Option, } @@ -205,7 +205,7 @@ pub struct ServerIntegrationsLayer { #[serde(default, skip_serializing_if = "Option::is_none")] pub github: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub slack: Option, + pub slack: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub search: Option, } @@ -216,17 +216,17 @@ pub struct ServerIntegrationsLayer { #[serde(deny_unknown_fields)] pub struct GithubIntegrationLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub enabled: Option, + pub enabled: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub strategy: Option, + pub strategy: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub app_id: Option, + pub app_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub client_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub slug: Option, + pub slug: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub webhooks: Option, + pub webhooks: Option, } /// `[server.integrations.slack]` — Slack workspace credentials and defaults. @@ -234,7 +234,7 @@ pub struct GithubIntegrationLayer { #[serde(deny_unknown_fields)] pub struct SlackIntegrationLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub enabled: Option, + pub enabled: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub default_channel: Option, } @@ -244,7 +244,7 @@ pub struct SlackIntegrationLayer { #[serde(deny_unknown_fields)] pub struct SearchIntegrationLayer { #[serde(default, skip_serializing_if = "Option::is_none")] - pub provider: Option, + pub provider: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub venice_engine: Option, } diff --git a/lib/foundation/fabro-config/src/resolve/server.rs b/lib/foundation/fabro-config/src/resolve/server.rs index df6f1b76b..08a5900db 100644 --- a/lib/foundation/fabro-config/src/resolve/server.rs +++ b/lib/foundation/fabro-config/src/resolve/server.rs @@ -51,7 +51,7 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec) -> Se .expect("defaults.toml should provide server.scheduler.max_concurrent_runs"), }, logging: ServerLoggingSettings { - level: layer + level: layer .logging .as_ref() .and_then(|logging| logging.level.as_ref()) @@ -70,10 +70,10 @@ fn resolve_sandbox(layer: Option<&ServerSandboxLayer>) -> ServerSandboxSettings let providers = layer.and_then(|sandbox| sandbox.providers.as_ref()); ServerSandboxSettings { providers: ServerSandboxProvidersSettings { - local: resolve_sandbox_provider( + local: resolve_sandbox_provider( providers.and_then(|providers| providers.local.as_ref()), ), - docker: resolve_sandbox_provider( + docker: resolve_sandbox_provider( providers.and_then(|providers| providers.docker.as_ref()), ), daytona: resolve_sandbox_provider( @@ -150,7 +150,7 @@ fn resolve_auth( let methods = if let Some(mut methods) = layer.and_then(|auth| auth.methods.clone()) { if methods.is_empty() { errors.push(ResolveError::Invalid { - path: "server.auth.methods".to_string(), + path: "server.auth.methods".to_string(), reason: "must not be empty".to_string(), }); } @@ -169,7 +169,7 @@ fn resolve_auth( .unwrap_or_default(); if methods.contains(&ServerAuthMethod::Github) && github.allowed_usernames.is_empty() { errors.push(ResolveError::Invalid { - path: "server.auth.github.allowed_usernames".to_string(), + path: "server.auth.github.allowed_usernames".to_string(), reason: "must not be empty when github auth is enabled".to_string(), }); } @@ -198,7 +198,7 @@ fn validate_github_webhook_strategy( && github.app_id.is_none() { errors.push(ResolveError::Invalid { - path: "server.integrations.github.app_id".to_string(), + path: "server.integrations.github.app_id".to_string(), reason: "must be set when server.integrations.github.webhooks.strategy is configured" .to_string(), }); @@ -208,7 +208,7 @@ fn validate_github_webhook_strategy( && api_layer.and_then(|api| api.url.as_ref()).is_none() { errors.push(ResolveError::Invalid { - path: "server.api.url".to_string(), + path: "server.api.url".to_string(), reason: "must be set when server.integrations.github.webhooks.strategy = \"server_url\"" .to_string(), @@ -348,20 +348,20 @@ fn resolve_integrations(layer: Option<&ServerIntegrationsLayer>) -> ServerIntegr ); warn_if_demoted_template("server.integrations.github.slug", github.slug.as_deref()); GithubIntegrationSettings { - enabled: github.enabled.unwrap_or(true), - strategy: github.strategy.unwrap_or_default(), - app_id: github.app_id.clone(), + enabled: github.enabled.unwrap_or(true), + strategy: github.strategy.unwrap_or_default(), + app_id: github.app_id.clone(), client_id: github.client_id.clone(), - slug: github.slug.clone(), - webhooks: github.webhooks.as_ref().map(resolve_github_webhooks), + slug: github.slug.clone(), + webhooks: github.webhooks.as_ref().map(resolve_github_webhooks), } }) .unwrap_or_default(), - slack: layer + slack: layer .and_then(|integrations| integrations.slack.as_ref()) .map_or( SlackIntegrationSettings { - enabled: false, + enabled: false, default_channel: None, }, |slack| { @@ -370,7 +370,7 @@ fn resolve_integrations(layer: Option<&ServerIntegrationsLayer>) -> ServerIntegr slack.default_channel.as_deref(), ); SlackIntegrationSettings { - enabled: slack.enabled.unwrap_or(true), + enabled: slack.enabled.unwrap_or(true), default_channel: slack.default_channel.clone(), } }, @@ -378,7 +378,7 @@ fn resolve_integrations(layer: Option<&ServerIntegrationsLayer>) -> ServerIntegr search: layer .and_then(|integrations| integrations.search.as_ref()) .map(|search| SearchIntegrationSettings { - provider: search.provider.unwrap_or_default(), + provider: search.provider.unwrap_or_default(), venice_engine: search.venice_engine.unwrap_or_default(), }) .unwrap_or_default(), diff --git a/lib/foundation/fabro-types/src/settings/server.rs b/lib/foundation/fabro-types/src/settings/server.rs index 044c1776b..4af44d9f1 100644 --- a/lib/foundation/fabro-types/src/settings/server.rs +++ b/lib/foundation/fabro-types/src/settings/server.rs @@ -22,16 +22,16 @@ use super::duration::Duration; /// (tests). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerNamespace { - pub listen: ServerListenSettings, - pub api: ServerApiSettings, - pub web: ServerWebSettings, - pub auth: ServerAuthSettings, - pub sandbox: ServerSandboxSettings, - pub storage: ServerStorageSettings, - pub artifacts: ServerArtifactsSettings, - pub slatedb: ServerSlateDbSettings, - pub scheduler: ServerSchedulerSettings, - pub logging: ServerLoggingSettings, + pub listen: ServerListenSettings, + pub api: ServerApiSettings, + pub web: ServerWebSettings, + pub auth: ServerAuthSettings, + pub sandbox: ServerSandboxSettings, + pub storage: ServerStorageSettings, + pub artifacts: ServerArtifactsSettings, + pub slatedb: ServerSlateDbSettings, + pub scheduler: ServerSchedulerSettings, + pub logging: ServerLoggingSettings, pub integrations: ServerIntegrationsSettings, } @@ -43,16 +43,16 @@ impl ServerNamespace { #[must_use] pub fn test_default() -> Self { Self { - listen: ServerListenSettings::default(), - api: ServerApiSettings::default(), - web: ServerWebSettings::default(), - auth: ServerAuthSettings::default(), - sandbox: ServerSandboxSettings::default(), - storage: ServerStorageSettings::default(), - artifacts: ServerArtifactsSettings::default(), - slatedb: ServerSlateDbSettings::default(), - scheduler: ServerSchedulerSettings::default(), - logging: ServerLoggingSettings::default(), + listen: ServerListenSettings::default(), + api: ServerApiSettings::default(), + web: ServerWebSettings::default(), + auth: ServerAuthSettings::default(), + sandbox: ServerSandboxSettings::default(), + storage: ServerStorageSettings::default(), + artifacts: ServerArtifactsSettings::default(), + slatedb: ServerSlateDbSettings::default(), + scheduler: ServerSchedulerSettings::default(), + logging: ServerLoggingSettings::default(), integrations: ServerIntegrationsSettings::default(), } } @@ -89,13 +89,13 @@ pub struct ServerApiSettings { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerWebSettings { pub enabled: bool, - pub url: String, + pub url: String, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerAuthSettings { pub methods: Vec, - pub github: ServerAuthGithubSettings, + pub github: ServerAuthGithubSettings, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -117,8 +117,8 @@ pub struct ServerSandboxSettings { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerSandboxProvidersSettings { - pub local: ServerSandboxProviderSettings, - pub docker: ServerSandboxProviderSettings, + pub local: ServerSandboxProviderSettings, + pub docker: ServerSandboxProviderSettings, pub daytona: ServerSandboxProviderSettings, } @@ -158,28 +158,28 @@ pub struct ServerStorageSettings { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerArtifactsSettings { pub prefix: String, - pub store: ObjectStoreSettings, + pub store: ObjectStoreSettings, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerSlateDbSettings { - pub prefix: String, - pub store: ObjectStoreSettings, + pub prefix: String, + pub store: ObjectStoreSettings, #[serde( serialize_with = "serialize_std_duration", deserialize_with = "deserialize_std_duration" )] pub flush_interval: StdDuration, - pub disk_cache: bool, + pub disk_cache: bool, } impl Default for ServerSlateDbSettings { fn default() -> Self { Self { - prefix: String::new(), - store: ObjectStoreSettings::default(), + prefix: String::new(), + store: ObjectStoreSettings::default(), flush_interval: StdDuration::ZERO, - disk_cache: false, + disk_cache: false, } } } @@ -191,9 +191,9 @@ pub enum ObjectStoreSettings { root: String, }, S3 { - bucket: String, - region: String, - endpoint: Option, + bucket: String, + region: String, + endpoint: Option, path_style: bool, }, } @@ -233,7 +233,7 @@ pub enum LogDestination { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerLoggingSettings { - pub level: Option, + pub level: Option, #[serde(default)] pub destination: LogDestination, } @@ -241,30 +241,30 @@ pub struct ServerLoggingSettings { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ServerIntegrationsSettings { pub github: GithubIntegrationSettings, - pub slack: SlackIntegrationSettings, + pub slack: SlackIntegrationSettings, pub search: SearchIntegrationSettings, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct GithubIntegrationSettings { - pub enabled: bool, - pub strategy: GithubIntegrationStrategy, - pub app_id: Option, + pub enabled: bool, + pub strategy: GithubIntegrationStrategy, + pub app_id: Option, pub client_id: Option, - pub slug: Option, - pub webhooks: Option, + pub slug: Option, + pub webhooks: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SlackIntegrationSettings { - pub enabled: bool, + pub enabled: bool, pub default_channel: Option, } impl Default for SlackIntegrationSettings { fn default() -> Self { Self { - enabled: true, + enabled: true, default_channel: None, } } @@ -335,7 +335,7 @@ impl VeniceSearchEngine { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct SearchIntegrationSettings { - pub provider: SearchProvider, + pub provider: SearchProvider, pub venice_engine: VeniceSearchEngine, }