diff --git a/crates/forge_api/src/api.rs b/crates/forge_api/src/api.rs index dfd144cac5..ceef7975d9 100644 --- a/crates/forge_api/src/api.rs +++ b/crates/forge_api/src/api.rs @@ -32,6 +32,11 @@ pub trait API: Sync + Send { /// Provides a list of agents available in the current environment async fn get_agents(&self) -> Result>; + + /// Provides lightweight metadata for all agents without requiring a + /// configured provider or model + async fn get_agent_infos(&self) -> Result>; + /// Provides a list of providers available in the current environment async fn get_providers(&self) -> Result>; diff --git a/crates/forge_api/src/forge_api.rs b/crates/forge_api/src/forge_api.rs index d53ce3cd59..8569b21d6b 100644 --- a/crates/forge_api/src/forge_api.rs +++ b/crates/forge_api/src/forge_api.rs @@ -48,8 +48,8 @@ impl ForgeAPI>, ForgeRepo> { /// * `cwd` - The working directory path for environment and file resolution /// * `config` - Pre-read application configuration (from startup) /// * `services_url` - Pre-validated URL for the gRPC workspace server - pub fn init(cwd: PathBuf, config: ForgeConfig, services_url: Url) -> Self { - let infra = Arc::new(ForgeInfra::new(cwd, config, services_url)); + pub fn init(cwd: PathBuf, config: ForgeConfig) -> Self { + let infra = Arc::new(ForgeInfra::new(cwd, config)); let repo = Arc::new(ForgeRepo::new(infra.clone())); let app = Arc::new(ForgeServices::new(repo.clone())); ForgeAPI::new(app, repo) @@ -92,6 +92,10 @@ impl< self.services.get_agents().await } + async fn get_agent_infos(&self) -> Result> { + self.services.get_agent_infos().await + } + async fn get_providers(&self) -> Result> { Ok(self.services.get_all_providers().await?) } diff --git a/crates/forge_app/src/infra.rs b/crates/forge_app/src/infra.rs index 9659c3eb4c..e4b49bfb66 100644 --- a/crates/forge_app/src/infra.rs +++ b/crates/forge_app/src/infra.rs @@ -397,11 +397,11 @@ pub trait AgentRepository: Send + Sync { /// * `provider_id` - Default provider applied to agents that do not specify /// one /// * `model_id` - Default model applied to agents that do not specify one - async fn get_agents( - &self, - provider_id: forge_domain::ProviderId, - model_id: forge_domain::ModelId, - ) -> anyhow::Result>; + async fn get_agents(&self) -> anyhow::Result>; + + /// Load lightweight metadata for all agents without requiring a configured + /// provider or model. + async fn get_agent_infos(&self) -> anyhow::Result>; } /// Infrastructure trait for providing shared gRPC channel @@ -411,7 +411,7 @@ pub trait AgentRepository: Send + Sync { /// cheaply across multiple clients. pub trait GrpcInfra: Send + Sync { /// Returns a cloned gRPC channel for the workspace server - fn channel(&self) -> tonic::transport::Channel; + fn channel(&self) -> anyhow::Result; /// Hydrates the gRPC channel by establishing and then dropping the /// connection diff --git a/crates/forge_app/src/services.rs b/crates/forge_app/src/services.rs index ee1919b6d9..4ec2c49809 100644 --- a/crates/forge_app/src/services.rs +++ b/crates/forge_app/src/services.rs @@ -469,6 +469,10 @@ pub trait AgentRegistry: Send + Sync { /// Get all agents from the registry store async fn get_agents(&self) -> anyhow::Result>; + /// Get lightweight metadata for all agents without requiring a configured + /// provider or model + async fn get_agent_infos(&self) -> anyhow::Result>; + /// Get agent by ID (from registry store) async fn get_agent(&self, agent_id: &AgentId) -> anyhow::Result>; @@ -918,6 +922,10 @@ impl AgentRegistry for I { self.agent_registry().get_agents().await } + async fn get_agent_infos(&self) -> anyhow::Result> { + self.agent_registry().get_agent_infos().await + } + async fn get_agent(&self, agent_id: &AgentId) -> anyhow::Result> { self.agent_registry().get_agent(agent_id).await } diff --git a/crates/forge_domain/src/agent.rs b/crates/forge_domain/src/agent.rs index 7def44bc97..86c9a3a3e8 100644 --- a/crates/forge_domain/src/agent.rs +++ b/crates/forge_domain/src/agent.rs @@ -106,31 +106,31 @@ pub fn estimate_token_count(count: usize) -> usize { #[derive(Debug, Clone, PartialEq, Setters, Serialize, Deserialize, JsonSchema)] #[setters(strip_option, into)] pub struct Agent { + /// Unique identifier for the agent + pub id: AgentId, + + /// Human-readable title for the agent + pub title: Option, + + /// Human-readable description of the agent's purpose + pub description: Option, + /// Flag to enable/disable tool support for this agent. pub tool_supported: Option, - // Unique identifier for the agent - pub id: AgentId, - /// Path to the agent definition file, if loaded from a file pub path: Option, - /// Human-readable title for the agent - pub title: Option, - - // Required provider for the agent + /// Required provider for the agent pub provider: ProviderId, - // Required language model ID to be used by this agent + /// Required language model ID to be used by this agent pub model: ModelId, - // Human-readable description of the agent's purpose - pub description: Option, - - // Template for the system prompt provided to the agent + /// Template for the system prompt provided to the agent pub system_prompt: Option>, - // Template for the user prompt provided to the agent + /// Template for the user prompt provided to the agent pub user_prompt: Option>, /// Tools that the agent can use @@ -168,16 +168,31 @@ pub struct Agent { pub max_requests_per_turn: Option, } +/// Lightweight metadata about an agent, used for listing without requiring a +/// configured provider or model. +#[derive(Debug, Default, Clone, PartialEq, Setters, Serialize, Deserialize, JsonSchema)] +#[setters(strip_option, into)] +pub struct AgentInfo { + /// Unique identifier for the agent + pub id: AgentId, + + /// Human-readable title for the agent + pub title: Option, + + /// Human-readable description of the agent's purpose + pub description: Option, +} + impl Agent { /// Create a new Agent with required provider and model pub fn new(id: impl Into, provider: ProviderId, model: ModelId) -> Self { Self { id: id.into(), + title: Default::default(), + description: Default::default(), provider, model, - title: Default::default(), tool_supported: Default::default(), - description: Default::default(), system_prompt: Default::default(), user_prompt: Default::default(), tools: Default::default(), diff --git a/crates/forge_infra/src/forge_infra.rs b/crates/forge_infra/src/forge_infra.rs index 0815066da0..3a3e602d17 100644 --- a/crates/forge_infra/src/forge_infra.rs +++ b/crates/forge_infra/src/forge_infra.rs @@ -64,13 +64,14 @@ impl ForgeInfra { /// * `config` - Pre-read application configuration; used only at /// construction time to initialize infrastructure services /// * `services_url` - Pre-validated URL for the gRPC workspace server - pub fn new(cwd: PathBuf, config: forge_config::ForgeConfig, services_url: Url) -> Self { + pub fn new(cwd: PathBuf, config: forge_config::ForgeConfig) -> Self { let env = to_environment(cwd.clone()); let config_infra = Arc::new(ForgeEnvironmentInfra::new(cwd, config.clone())); - let file_write_service = Arc::new(ForgeFileWriteService::new()); + let config = config_infra.cached_config().unwrap_or(config); + let http_service = Arc::new(ForgeHttpInfra::new( - config_infra.cached_config().unwrap_or(config), + config.clone(), file_write_service.clone(), )); let file_read_service = Arc::new(ForgeFileReadService::new()); @@ -81,7 +82,7 @@ impl ForgeInfra { .map(|c| c.max_parallel_file_reads) .unwrap_or(4), )); - let grpc_client = Arc::new(ForgeGrpcClient::new(services_url)); + let grpc_client = Arc::new(ForgeGrpcClient::new(config.services_url.clone())); let output_printer = Arc::new(StdConsoleWriter::default()); Self { @@ -359,7 +360,7 @@ impl StrategyFactory for ForgeInfra { } impl GrpcInfra for ForgeInfra { - fn channel(&self) -> tonic::transport::Channel { + fn channel(&self) -> anyhow::Result { self.grpc_client.channel() } diff --git a/crates/forge_infra/src/grpc.rs b/crates/forge_infra/src/grpc.rs index 9b5b873992..c669222221 100644 --- a/crates/forge_infra/src/grpc.rs +++ b/crates/forge_infra/src/grpc.rs @@ -10,7 +10,7 @@ use url::Url; /// on first access. #[derive(Clone)] pub struct ForgeGrpcClient { - server_url: Arc, + server_url: String, channel: Arc>>, } @@ -19,22 +19,19 @@ impl ForgeGrpcClient { /// /// # Arguments /// * `server_url` - The URL of the gRPC server - pub fn new(server_url: Url) -> Self { - Self { - server_url: Arc::new(server_url), - channel: Arc::new(Mutex::new(None)), - } + pub fn new(server_url: String) -> Self { + Self { server_url, channel: Arc::new(Mutex::new(None)) } } /// Returns a clone of the underlying gRPC channel /// /// Channels are cheap to clone and can be shared across multiple clients. /// The channel is created on first call and cached for subsequent calls. - pub fn channel(&self) -> Channel { + pub fn channel(&self) -> anyhow::Result { let mut guard = self.channel.lock().unwrap(); if let Some(channel) = guard.as_ref() { - return channel.clone(); + return Ok(channel.clone()); } let mut channel = Channel::from_shared(self.server_url.to_string()) @@ -42,7 +39,7 @@ impl ForgeGrpcClient { .concurrency_limit(256); // Enable TLS for https URLs (webpki-roots is faster than native-roots) - if self.server_url.scheme().contains("https") { + if Url::parse(&self.server_url)?.scheme().contains("https") { let tls_config = tonic::transport::ClientTlsConfig::new().with_webpki_roots(); channel = channel .tls_config(tls_config) @@ -51,7 +48,7 @@ impl ForgeGrpcClient { let new_channel = channel.connect_lazy(); *guard = Some(new_channel.clone()); - new_channel + Ok(new_channel) } /// Hydrates the gRPC channel by forcing its initialization diff --git a/crates/forge_main/src/main.rs b/crates/forge_main/src/main.rs index b5d4748100..0b68b7e30b 100644 --- a/crates/forge_main/src/main.rs +++ b/crates/forge_main/src/main.rs @@ -8,7 +8,6 @@ use forge_api::ForgeAPI; use forge_config::ForgeConfig; use forge_domain::TitleFormat; use forge_main::{Cli, Sandbox, TitleDisplayExt, UI, tracker}; -use url::Url; /// Enables ENABLE_VIRTUAL_TERMINAL_PROCESSING on the stdout console handle. /// @@ -106,13 +105,6 @@ async fn run() -> Result<()> { let config = ForgeConfig::read().context("Failed to read Forge configuration from .forge.toml")?; - // Pre-validate services_url so a malformed URL produces a clear error - // message at startup instead of panicking inside the constructor. - let services_url: Url = config - .services_url - .parse() - .context("services_url in configuration must be a valid URL")?; - // Handle worktree creation if specified let cwd: PathBuf = match (&cli.sandbox, &cli.directory) { (Some(sandbox), Some(cli)) => { @@ -129,7 +121,7 @@ async fn run() -> Result<()> { }; let mut ui = UI::init(cli, config, move |config| { - ForgeAPI::init(cwd.clone(), config, services_url.clone()) + ForgeAPI::init(cwd.clone(), config) })?; ui.run().await; diff --git a/crates/forge_main/src/model.rs b/crates/forge_main/src/model.rs index f81e11bd95..f88cf4715b 100644 --- a/crates/forge_main/src/model.rs +++ b/crates/forge_main/src/model.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex}; -use forge_api::{Agent, Model, Template}; +use forge_api::{AgentInfo, Model, Template}; use forge_domain::UserCommand; use strum::{EnumProperty, IntoEnumIterator}; use strum_macros::{EnumIter, EnumProperty}; @@ -143,7 +143,10 @@ impl ForgeCommandManager { /// Registers agent commands to the manager. /// Returns information about the registration process. - pub fn register_agent_commands(&self, agents: Vec) -> AgentCommandRegistrationResult { + pub fn register_agent_commands( + &self, + agents: Vec, + ) -> AgentCommandRegistrationResult { let mut guard = self.commands.lock().unwrap(); let mut result = AgentCommandRegistrationResult { registered_count: 0, skipped_conflicts: Vec::new() }; @@ -840,24 +843,15 @@ mod tests { #[test] fn test_register_agent_commands() { - use forge_api::Agent; - use forge_domain::{ModelId, ProviderId}; - // Setup let fixture = ForgeCommandManager::default(); let agents = vec![ - Agent::new( - "test-agent", - ProviderId::ANTHROPIC, - ModelId::new("claude-3-5-sonnet-20241022"), - ) - .title("Test Agent".to_string()), - Agent::new( - "another", - ProviderId::ANTHROPIC, - ModelId::new("claude-3-5-sonnet-20241022"), - ) - .title("Another Agent".to_string()), + forge_domain::AgentInfo::default() + .id("test-agent") + .title("Test Agent".to_string()), + forge_domain::AgentInfo::default() + .id("another") + .title("Another Agent".to_string()), ]; // Execute @@ -885,18 +879,12 @@ mod tests { #[test] fn test_parse_agent_switch_command() { - use forge_api::Agent; - use forge_domain::{ModelId, ProviderId}; - // Setup let fixture = ForgeCommandManager::default(); let agents = vec![ - Agent::new( - "test-agent", - ProviderId::ANTHROPIC, - ModelId::new("claude-3-5-sonnet-20241022"), - ) - .title("Test Agent".to_string()), + forge_domain::AgentInfo::default() + .id("test-agent") + .title("Test Agent".to_string()), ]; let _result = fixture.register_agent_commands(agents); diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index 5ba5de69e4..ca01a0b657 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -183,11 +183,10 @@ impl A + Send + Sync> UI // Convert string to AgentId for validation let agent = self .api - .get_agents() + .get_agent_infos() .await? - .iter() - .find(|agent| agent.id == agent_id) - .cloned() + .into_iter() + .find(|info| info.id == agent_id) .ok_or(anyhow::anyhow!("Undefined agent: {agent_id}"))?; // Update the app config with the new operating agent. @@ -374,7 +373,7 @@ impl A + Send + Sync> UI let api = self.api.clone(); tokio::spawn(async move { api.get_tools().await }); let api = self.api.clone(); - tokio::spawn(async move { api.get_agents().await }); + tokio::spawn(async move { api.get_agent_infos().await }); let api = self.api.clone(); tokio::spawn(async move { let _ = api.hydrate_channel(); @@ -1145,7 +1144,7 @@ impl A + Send + Sync> UI } async fn on_show_agents(&mut self, porcelain: bool, custom: bool) -> anyhow::Result<()> { - let agents = self.api.get_agents().await?; + let agents = self.api.get_agent_infos().await?; if agents.is_empty() { return Ok(()); @@ -1336,14 +1335,15 @@ impl A + Send + Sync> UI "Planning and strategy agent [alias for: muse]", ); - // Fetch agents and add them to the commands list - let agents = self.api.get_agents().await?; - for agent in agents { - let title = agent + // Fetch agent infos and add them to the commands list. + // Uses get_agent_infos() so no provider/model is required for listing. + let agent_infos = self.api.get_agent_infos().await?; + for agent_info in agent_infos { + let title = agent_info .title .map(|title| title.lines().collect::>().join(" ")); info = info - .add_title(agent.id.to_string()) + .add_title(agent_info.id.to_string()) .add_key_value("type", CommandType::Agent) .add_key_value("description", title); } @@ -1999,7 +1999,7 @@ impl A + Send + Sync> UI } } - let agents = self.api.get_agents().await?; + let agents = self.api.get_agent_infos().await?; if agents.is_empty() { return Ok(false); @@ -2077,7 +2077,7 @@ impl A + Send + Sync> UI } SlashCommand::AgentSwitch(agent_id) => { // Validate that the agent exists by checking against loaded agents - let agents = self.api.get_agents().await?; + let agents = self.api.get_agent_infos().await?; let agent_exists = agents.iter().any(|agent| agent.id.as_str() == agent_id); if agent_exists { @@ -3096,7 +3096,7 @@ impl A + Send + Sync> UI // Execute independent operations in parallel to improve performance let (agents_result, commands_result) = - tokio::join!(self.api.get_agents(), self.api.get_commands()); + tokio::join!(self.api.get_agent_infos(), self.api.get_commands()); // Register agent commands with proper error handling and user feedback match agents_result { diff --git a/crates/forge_repo/src/agent.rs b/crates/forge_repo/src/agent.rs index f0a8f67368..2e225e8eb9 100644 --- a/crates/forge_repo/src/agent.rs +++ b/crates/forge_repo/src/agent.rs @@ -1,8 +1,9 @@ use std::sync::Arc; use anyhow::{Context, Result}; -use forge_app::{DirectoryReaderInfra, EnvironmentInfra, FileInfoInfra}; -use forge_domain::Template; +use forge_app::{AgentRepository, DirectoryReaderInfra, EnvironmentInfra, FileInfoInfra}; +use forge_config::ForgeConfig; +use forge_domain::{ModelId, ProviderId, Template}; use gray_matter::Matter; use gray_matter::engine::YAML; @@ -43,7 +44,7 @@ impl ForgeAgentRepository { impl ForgeAgentRepository { /// Load all agent definitions from all available sources with conflict /// resolution. - pub(crate) async fn load_agents(&self) -> anyhow::Result> { + async fn load_agents(&self) -> anyhow::Result> { self.load_all_agents().await } @@ -156,6 +157,43 @@ fn parse_agent_file(content: &str) -> Result { Ok(agent) } +#[async_trait::async_trait] +impl + DirectoryReaderInfra> + AgentRepository for ForgeAgentRepository +{ + async fn get_agents(&self) -> anyhow::Result> { + let agent_defs = self.load_agents().await?; + + let session = self + .infra + .get_config()? + .session + .ok_or(forge_domain::Error::NoDefaultSession)?; + + Ok(agent_defs + .into_iter() + .map(|def| { + def.into_agent( + ProviderId::from(session.provider_id.clone()), + ModelId::from(session.model_id.clone()), + ) + }) + .collect()) + } + + async fn get_agent_infos(&self) -> anyhow::Result> { + let agent_defs = self.load_agents().await?; + Ok(agent_defs + .into_iter() + .map(|def| forge_domain::AgentInfo { + id: def.id, + title: def.title, + description: def.description, + }) + .collect()) + } +} + #[cfg(test)] mod tests { use pretty_assertions::assert_eq; diff --git a/crates/forge_repo/src/context_engine.rs b/crates/forge_repo/src/context_engine.rs index ab9b34abd1..97f5bf68ef 100644 --- a/crates/forge_repo/src/context_engine.rs +++ b/crates/forge_repo/src/context_engine.rs @@ -116,7 +116,7 @@ impl ForgeContextEngineRepository { #[async_trait] impl WorkspaceIndexRepository for ForgeContextEngineRepository { async fn authenticate(&self) -> Result { - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let request = tonic::Request::new(CreateApiKeyRequest { user_id: None }); @@ -143,7 +143,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.create_workspace(request).await?.into_inner(); @@ -173,7 +173,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.upload_files(request).await?; @@ -212,7 +212,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.search(request).await?; @@ -283,7 +283,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = tonic::Request::new(ListWorkspacesRequest {}); let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.list_workspaces(request).await?; @@ -306,7 +306,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository }); let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.get_workspace_info(request).await?; @@ -328,7 +328,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client.list_files(request).await?; @@ -359,7 +359,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); client.delete_files(request).await?; @@ -377,7 +377,7 @@ impl WorkspaceIndexRepository for ForgeContextEngineRepository let request = self.with_auth(request, auth_token)?; - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); client.delete_workspace(request).await?; diff --git a/crates/forge_repo/src/forge_repo.rs b/crates/forge_repo/src/forge_repo.rs index 229989738d..34d1bb8498 100644 --- a/crates/forge_repo/src/forge_repo.rs +++ b/crates/forge_repo/src/forge_repo.rs @@ -8,6 +8,7 @@ use forge_app::{ FileInfoInfra, FileReaderInfra, FileRemoverInfra, FileWriterInfra, GrpcInfra, HttpInfra, KVStore, McpServerInfra, StrategyFactory, UserInfra, WalkedFile, Walker, WalkerInfra, }; +use forge_config::ForgeConfig; use forge_domain::{ AnyProvider, AuthCredential, ChatCompletionMessage, ChatRepository, CommandOutput, Context, Conversation, ConversationId, ConversationRepository, Environment, FileInfo, @@ -487,19 +488,15 @@ where } #[async_trait::async_trait] -impl AgentRepository - for ForgeRepo +impl + DirectoryReaderInfra + Send + Sync> + AgentRepository for ForgeRepo { - async fn get_agents( - &self, - provider_id: forge_domain::ProviderId, - model_id: forge_domain::ModelId, - ) -> anyhow::Result> { - let agent_defs = self.agent_repository.load_agents().await?; - Ok(agent_defs - .into_iter() - .map(|def| def.into_agent(provider_id.clone(), model_id.clone())) - .collect()) + async fn get_agents(&self) -> anyhow::Result> { + self.agent_repository.get_agents().await + } + + async fn get_agent_infos(&self) -> anyhow::Result> { + self.agent_repository.get_agent_infos().await } } @@ -632,7 +629,7 @@ impl FuzzySearchRepository for ForgeRepo { } impl GrpcInfra for ForgeRepo { - fn channel(&self) -> tonic::transport::Channel { + fn channel(&self) -> anyhow::Result { self.infra.channel() } diff --git a/crates/forge_repo/src/fuzzy_search.rs b/crates/forge_repo/src/fuzzy_search.rs index 6423abd8ca..5a85e8a1e9 100644 --- a/crates/forge_repo/src/fuzzy_search.rs +++ b/crates/forge_repo/src/fuzzy_search.rs @@ -39,7 +39,7 @@ impl FuzzySearchRepository for ForgeFuzzySearchRepository { }); // Call gRPC API - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client .fuzzy_search(request) diff --git a/crates/forge_repo/src/skill.rs b/crates/forge_repo/src/skill.rs index c1dbd77a29..776a438a18 100644 --- a/crates/forge_repo/src/skill.rs +++ b/crates/forge_repo/src/skill.rs @@ -313,12 +313,7 @@ mod tests { let skill_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("src/fixtures/skills_with_resources"); let config = ForgeConfig::read().unwrap_or_default(); - let services_url = config.services_url.parse().unwrap(); - let infra = Arc::new(ForgeInfra::new( - std::env::current_dir().unwrap(), - config, - services_url, - )); + let infra = Arc::new(ForgeInfra::new(std::env::current_dir().unwrap(), config)); let repo = ForgeSkillRepository::new(infra); (repo, skill_dir) } diff --git a/crates/forge_repo/src/validation.rs b/crates/forge_repo/src/validation.rs index d4382bdfc2..536a3ce847 100644 --- a/crates/forge_repo/src/validation.rs +++ b/crates/forge_repo/src/validation.rs @@ -42,7 +42,7 @@ impl ValidationRepository for ForgeValidationRepository { let request = tonic::Request::new(ValidateFilesRequest { files: vec![proto_file] }); // Call gRPC API - let channel = self.infra.channel(); + let channel = self.infra.channel()?; let mut client = ForgeServiceClient::new(channel); let response = client .validate_files(request) diff --git a/crates/forge_services/src/agent_registry.rs b/crates/forge_services/src/agent_registry.rs index b9df201ef1..5e547bd212 100644 --- a/crates/forge_services/src/agent_registry.rs +++ b/crates/forge_services/src/agent_registry.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use dashmap::DashMap; -use forge_app::domain::{AgentId, Error, ModelId, ProviderId}; +use forge_app::domain::AgentId; use forge_app::{AgentRepository, EnvironmentInfra}; -use forge_domain::Agent; +use forge_domain::{Agent, AgentInfo}; use tokio::sync::RwLock; /// AgentRegistryService manages the active-agent ID and a registry of runtime @@ -72,13 +72,7 @@ impl> /// do not specify their own provider/model receive the session-level /// defaults. async fn load_agents(&self) -> anyhow::Result> { - let config = self.repository.get_config()?; - let session = config.session.as_ref().ok_or(Error::NoDefaultSession)?; - let provider_id = ProviderId::from(session.provider_id.clone()); - let model_id = ModelId::new(session.model_id.clone()); - - let agents = self.repository.get_agents(provider_id, model_id).await?; - + let agents = self.repository.get_agents().await?; let agents_map = DashMap::new(); for agent in agents { agents_map.insert(agent.id.as_str().to_string(), agent); @@ -108,6 +102,10 @@ impl + Ok(agents.iter().map(|entry| entry.value().clone()).collect()) } + async fn get_agent_infos(&self) -> anyhow::Result> { + self.repository.get_agent_infos().await + } + async fn get_agent(&self, agent_id: &AgentId) -> anyhow::Result> { let agents = self.ensure_agents_loaded().await?; Ok(agents.get(agent_id.as_str()).map(|v| v.value().clone()))