Skip to content

Commit dc67ca7

Browse files
fix: colon command autocomplete not working (#2910)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 26badba commit dc67ca7

File tree

17 files changed

+167
-129
lines changed

17 files changed

+167
-129
lines changed

crates/forge_api/src/api.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ pub trait API: Sync + Send {
3232

3333
/// Provides a list of agents available in the current environment
3434
async fn get_agents(&self) -> Result<Vec<Agent>>;
35+
36+
/// Provides lightweight metadata for all agents without requiring a
37+
/// configured provider or model
38+
async fn get_agent_infos(&self) -> Result<Vec<AgentInfo>>;
39+
3540
/// Provides a list of providers available in the current environment
3641
async fn get_providers(&self) -> Result<Vec<AnyProvider>>;
3742

crates/forge_api/src/forge_api.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ impl ForgeAPI<ForgeServices<ForgeRepo<ForgeInfra>>, ForgeRepo<ForgeInfra>> {
4848
/// * `cwd` - The working directory path for environment and file resolution
4949
/// * `config` - Pre-read application configuration (from startup)
5050
/// * `services_url` - Pre-validated URL for the gRPC workspace server
51-
pub fn init(cwd: PathBuf, config: ForgeConfig, services_url: Url) -> Self {
52-
let infra = Arc::new(ForgeInfra::new(cwd, config, services_url));
51+
pub fn init(cwd: PathBuf, config: ForgeConfig) -> Self {
52+
let infra = Arc::new(ForgeInfra::new(cwd, config));
5353
let repo = Arc::new(ForgeRepo::new(infra.clone()));
5454
let app = Arc::new(ForgeServices::new(repo.clone()));
5555
ForgeAPI::new(app, repo)
@@ -92,6 +92,10 @@ impl<
9292
self.services.get_agents().await
9393
}
9494

95+
async fn get_agent_infos(&self) -> Result<Vec<AgentInfo>> {
96+
self.services.get_agent_infos().await
97+
}
98+
9599
async fn get_providers(&self) -> Result<Vec<AnyProvider>> {
96100
Ok(self.services.get_all_providers().await?)
97101
}

crates/forge_app/src/infra.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,11 @@ pub trait AgentRepository: Send + Sync {
397397
/// * `provider_id` - Default provider applied to agents that do not specify
398398
/// one
399399
/// * `model_id` - Default model applied to agents that do not specify one
400-
async fn get_agents(
401-
&self,
402-
provider_id: forge_domain::ProviderId,
403-
model_id: forge_domain::ModelId,
404-
) -> anyhow::Result<Vec<forge_domain::Agent>>;
400+
async fn get_agents(&self) -> anyhow::Result<Vec<forge_domain::Agent>>;
401+
402+
/// Load lightweight metadata for all agents without requiring a configured
403+
/// provider or model.
404+
async fn get_agent_infos(&self) -> anyhow::Result<Vec<forge_domain::AgentInfo>>;
405405
}
406406

407407
/// Infrastructure trait for providing shared gRPC channel
@@ -411,7 +411,7 @@ pub trait AgentRepository: Send + Sync {
411411
/// cheaply across multiple clients.
412412
pub trait GrpcInfra: Send + Sync {
413413
/// Returns a cloned gRPC channel for the workspace server
414-
fn channel(&self) -> tonic::transport::Channel;
414+
fn channel(&self) -> anyhow::Result<tonic::transport::Channel>;
415415

416416
/// Hydrates the gRPC channel by establishing and then dropping the
417417
/// connection

crates/forge_app/src/services.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ pub trait AgentRegistry: Send + Sync {
469469
/// Get all agents from the registry store
470470
async fn get_agents(&self) -> anyhow::Result<Vec<forge_domain::Agent>>;
471471

472+
/// Get lightweight metadata for all agents without requiring a configured
473+
/// provider or model
474+
async fn get_agent_infos(&self) -> anyhow::Result<Vec<forge_domain::AgentInfo>>;
475+
472476
/// Get agent by ID (from registry store)
473477
async fn get_agent(&self, agent_id: &AgentId) -> anyhow::Result<Option<forge_domain::Agent>>;
474478

@@ -918,6 +922,10 @@ impl<I: Services> AgentRegistry for I {
918922
self.agent_registry().get_agents().await
919923
}
920924

925+
async fn get_agent_infos(&self) -> anyhow::Result<Vec<forge_domain::AgentInfo>> {
926+
self.agent_registry().get_agent_infos().await
927+
}
928+
921929
async fn get_agent(&self, agent_id: &AgentId) -> anyhow::Result<Option<forge_domain::Agent>> {
922930
self.agent_registry().get_agent(agent_id).await
923931
}

crates/forge_domain/src/agent.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,31 +106,31 @@ pub fn estimate_token_count(count: usize) -> usize {
106106
#[derive(Debug, Clone, PartialEq, Setters, Serialize, Deserialize, JsonSchema)]
107107
#[setters(strip_option, into)]
108108
pub struct Agent {
109+
/// Unique identifier for the agent
110+
pub id: AgentId,
111+
112+
/// Human-readable title for the agent
113+
pub title: Option<String>,
114+
115+
/// Human-readable description of the agent's purpose
116+
pub description: Option<String>,
117+
109118
/// Flag to enable/disable tool support for this agent.
110119
pub tool_supported: Option<bool>,
111120

112-
// Unique identifier for the agent
113-
pub id: AgentId,
114-
115121
/// Path to the agent definition file, if loaded from a file
116122
pub path: Option<String>,
117123

118-
/// Human-readable title for the agent
119-
pub title: Option<String>,
120-
121-
// Required provider for the agent
124+
/// Required provider for the agent
122125
pub provider: ProviderId,
123126

124-
// Required language model ID to be used by this agent
127+
/// Required language model ID to be used by this agent
125128
pub model: ModelId,
126129

127-
// Human-readable description of the agent's purpose
128-
pub description: Option<String>,
129-
130-
// Template for the system prompt provided to the agent
130+
/// Template for the system prompt provided to the agent
131131
pub system_prompt: Option<Template<SystemContext>>,
132132

133-
// Template for the user prompt provided to the agent
133+
/// Template for the user prompt provided to the agent
134134
pub user_prompt: Option<Template<EventContext>>,
135135

136136
/// Tools that the agent can use
@@ -168,16 +168,31 @@ pub struct Agent {
168168
pub max_requests_per_turn: Option<usize>,
169169
}
170170

171+
/// Lightweight metadata about an agent, used for listing without requiring a
172+
/// configured provider or model.
173+
#[derive(Debug, Default, Clone, PartialEq, Setters, Serialize, Deserialize, JsonSchema)]
174+
#[setters(strip_option, into)]
175+
pub struct AgentInfo {
176+
/// Unique identifier for the agent
177+
pub id: AgentId,
178+
179+
/// Human-readable title for the agent
180+
pub title: Option<String>,
181+
182+
/// Human-readable description of the agent's purpose
183+
pub description: Option<String>,
184+
}
185+
171186
impl Agent {
172187
/// Create a new Agent with required provider and model
173188
pub fn new(id: impl Into<AgentId>, provider: ProviderId, model: ModelId) -> Self {
174189
Self {
175190
id: id.into(),
191+
title: Default::default(),
192+
description: Default::default(),
176193
provider,
177194
model,
178-
title: Default::default(),
179195
tool_supported: Default::default(),
180-
description: Default::default(),
181196
system_prompt: Default::default(),
182197
user_prompt: Default::default(),
183198
tools: Default::default(),

crates/forge_infra/src/forge_infra.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ impl ForgeInfra {
6464
/// * `config` - Pre-read application configuration; used only at
6565
/// construction time to initialize infrastructure services
6666
/// * `services_url` - Pre-validated URL for the gRPC workspace server
67-
pub fn new(cwd: PathBuf, config: forge_config::ForgeConfig, services_url: Url) -> Self {
67+
pub fn new(cwd: PathBuf, config: forge_config::ForgeConfig) -> Self {
6868
let env = to_environment(cwd.clone());
6969
let config_infra = Arc::new(ForgeEnvironmentInfra::new(cwd, config.clone()));
70-
7170
let file_write_service = Arc::new(ForgeFileWriteService::new());
71+
let config = config_infra.cached_config().unwrap_or(config);
72+
7273
let http_service = Arc::new(ForgeHttpInfra::new(
73-
config_infra.cached_config().unwrap_or(config),
74+
config.clone(),
7475
file_write_service.clone(),
7576
));
7677
let file_read_service = Arc::new(ForgeFileReadService::new());
@@ -81,7 +82,7 @@ impl ForgeInfra {
8182
.map(|c| c.max_parallel_file_reads)
8283
.unwrap_or(4),
8384
));
84-
let grpc_client = Arc::new(ForgeGrpcClient::new(services_url));
85+
let grpc_client = Arc::new(ForgeGrpcClient::new(config.services_url.clone()));
8586
let output_printer = Arc::new(StdConsoleWriter::default());
8687

8788
Self {
@@ -359,7 +360,7 @@ impl StrategyFactory for ForgeInfra {
359360
}
360361

361362
impl GrpcInfra for ForgeInfra {
362-
fn channel(&self) -> tonic::transport::Channel {
363+
fn channel(&self) -> anyhow::Result<tonic::transport::Channel> {
363364
self.grpc_client.channel()
364365
}
365366

crates/forge_infra/src/grpc.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use url::Url;
1010
/// on first access.
1111
#[derive(Clone)]
1212
pub struct ForgeGrpcClient {
13-
server_url: Arc<Url>,
13+
server_url: String,
1414
channel: Arc<Mutex<Option<Channel>>>,
1515
}
1616

@@ -19,30 +19,27 @@ impl ForgeGrpcClient {
1919
///
2020
/// # Arguments
2121
/// * `server_url` - The URL of the gRPC server
22-
pub fn new(server_url: Url) -> Self {
23-
Self {
24-
server_url: Arc::new(server_url),
25-
channel: Arc::new(Mutex::new(None)),
26-
}
22+
pub fn new(server_url: String) -> Self {
23+
Self { server_url, channel: Arc::new(Mutex::new(None)) }
2724
}
2825

2926
/// Returns a clone of the underlying gRPC channel
3027
///
3128
/// Channels are cheap to clone and can be shared across multiple clients.
3229
/// The channel is created on first call and cached for subsequent calls.
33-
pub fn channel(&self) -> Channel {
30+
pub fn channel(&self) -> anyhow::Result<Channel> {
3431
let mut guard = self.channel.lock().unwrap();
3532

3633
if let Some(channel) = guard.as_ref() {
37-
return channel.clone();
34+
return Ok(channel.clone());
3835
}
3936

4037
let mut channel = Channel::from_shared(self.server_url.to_string())
4138
.expect("Invalid server URL")
4239
.concurrency_limit(256);
4340

4441
// Enable TLS for https URLs (webpki-roots is faster than native-roots)
45-
if self.server_url.scheme().contains("https") {
42+
if Url::parse(&self.server_url)?.scheme().contains("https") {
4643
let tls_config = tonic::transport::ClientTlsConfig::new().with_webpki_roots();
4744
channel = channel
4845
.tls_config(tls_config)
@@ -51,7 +48,7 @@ impl ForgeGrpcClient {
5148

5249
let new_channel = channel.connect_lazy();
5350
*guard = Some(new_channel.clone());
54-
new_channel
51+
Ok(new_channel)
5552
}
5653

5754
/// Hydrates the gRPC channel by forcing its initialization

crates/forge_main/src/main.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use forge_api::ForgeAPI;
88
use forge_config::ForgeConfig;
99
use forge_domain::TitleFormat;
1010
use forge_main::{Cli, Sandbox, TitleDisplayExt, UI, tracker};
11-
use url::Url;
1211

1312
/// Enables ENABLE_VIRTUAL_TERMINAL_PROCESSING on the stdout console handle.
1413
///
@@ -106,13 +105,6 @@ async fn run() -> Result<()> {
106105
let config =
107106
ForgeConfig::read().context("Failed to read Forge configuration from .forge.toml")?;
108107

109-
// Pre-validate services_url so a malformed URL produces a clear error
110-
// message at startup instead of panicking inside the constructor.
111-
let services_url: Url = config
112-
.services_url
113-
.parse()
114-
.context("services_url in configuration must be a valid URL")?;
115-
116108
// Handle worktree creation if specified
117109
let cwd: PathBuf = match (&cli.sandbox, &cli.directory) {
118110
(Some(sandbox), Some(cli)) => {
@@ -129,7 +121,7 @@ async fn run() -> Result<()> {
129121
};
130122

131123
let mut ui = UI::init(cli, config, move |config| {
132-
ForgeAPI::init(cwd.clone(), config, services_url.clone())
124+
ForgeAPI::init(cwd.clone(), config)
133125
})?;
134126
ui.run().await;
135127

crates/forge_main/src/model.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::sync::{Arc, Mutex};
22

3-
use forge_api::{Agent, Model, Template};
3+
use forge_api::{AgentInfo, Model, Template};
44
use forge_domain::UserCommand;
55
use strum::{EnumProperty, IntoEnumIterator};
66
use strum_macros::{EnumIter, EnumProperty};
@@ -143,7 +143,10 @@ impl ForgeCommandManager {
143143

144144
/// Registers agent commands to the manager.
145145
/// Returns information about the registration process.
146-
pub fn register_agent_commands(&self, agents: Vec<Agent>) -> AgentCommandRegistrationResult {
146+
pub fn register_agent_commands(
147+
&self,
148+
agents: Vec<AgentInfo>,
149+
) -> AgentCommandRegistrationResult {
147150
let mut guard = self.commands.lock().unwrap();
148151
let mut result =
149152
AgentCommandRegistrationResult { registered_count: 0, skipped_conflicts: Vec::new() };
@@ -840,24 +843,15 @@ mod tests {
840843

841844
#[test]
842845
fn test_register_agent_commands() {
843-
use forge_api::Agent;
844-
use forge_domain::{ModelId, ProviderId};
845-
846846
// Setup
847847
let fixture = ForgeCommandManager::default();
848848
let agents = vec![
849-
Agent::new(
850-
"test-agent",
851-
ProviderId::ANTHROPIC,
852-
ModelId::new("claude-3-5-sonnet-20241022"),
853-
)
854-
.title("Test Agent".to_string()),
855-
Agent::new(
856-
"another",
857-
ProviderId::ANTHROPIC,
858-
ModelId::new("claude-3-5-sonnet-20241022"),
859-
)
860-
.title("Another Agent".to_string()),
849+
forge_domain::AgentInfo::default()
850+
.id("test-agent")
851+
.title("Test Agent".to_string()),
852+
forge_domain::AgentInfo::default()
853+
.id("another")
854+
.title("Another Agent".to_string()),
861855
];
862856

863857
// Execute
@@ -885,18 +879,12 @@ mod tests {
885879

886880
#[test]
887881
fn test_parse_agent_switch_command() {
888-
use forge_api::Agent;
889-
use forge_domain::{ModelId, ProviderId};
890-
891882
// Setup
892883
let fixture = ForgeCommandManager::default();
893884
let agents = vec![
894-
Agent::new(
895-
"test-agent",
896-
ProviderId::ANTHROPIC,
897-
ModelId::new("claude-3-5-sonnet-20241022"),
898-
)
899-
.title("Test Agent".to_string()),
885+
forge_domain::AgentInfo::default()
886+
.id("test-agent")
887+
.title("Test Agent".to_string()),
900888
];
901889
let _result = fixture.register_agent_commands(agents);
902890

0 commit comments

Comments
 (0)