diff --git a/README.md b/README.md index 9f641fb384..8d7ccd1d09 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +> [!IMPORTANT] +> Remove this line to confirm you've reviewed this PR before submitting. # Zed [![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev) diff --git a/assets/settings/default.json b/assets/settings/default.json index 671653708d..0f9d996cbb 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -2851,6 +2851,14 @@ // Configures context servers for use by the agent. "context_servers": {}, // Configures agent servers available in the agent panel. + // Example for Oh My Pi (omp) harness: + // "agent_servers": { + // "omp": { + // "type": "custom", + // "command": "omp", + // "args": ["acp"] + // } + // }, "agent_servers": {}, "debugger": { "stepping_granularity": "line", diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index 64f90e5bda..c9cf6660cc 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -18,6 +18,9 @@ pub const GEMINI_ID: &str = "gemini"; pub const CLAUDE_AGENT_ID: &str = "claude-acp"; pub const CODEX_ID: &str = "codex-acp"; pub const CURSOR_ID: &str = "cursor"; +pub const OMP_ID: &str = "omp"; +pub const OMP_NAME: &str = "Oh My Pi"; + /// A generic agent server implementation for custom user-defined agents pub struct CustomAgentServer { @@ -37,7 +40,7 @@ impl AgentServer for CustomAgentServer { fn logo(&self) -> IconName { match self.agent_id.0.as_ref() { - "omp" => IconName::Omp, + OMP_ID => IconName::Omp, _ => IconName::Terminal, } } @@ -53,8 +56,14 @@ impl AgentServer for CustomAgentServer { settings .as_ref() .and_then(|s| s.default_mode().map(acp::SessionModeId::new)) + .or_else(|| { + if self.agent_id().0.as_ref() == OMP_ID { + Some(acp::SessionModeId::new("task")) + } else { + None + } + }) } - fn favorite_config_option_value_ids( &self, config_id: &acp::SessionConfigId, @@ -248,6 +257,26 @@ impl AgentServer for CustomAgentServer { _ => {} } } + if agent_id.as_ref() == OMP_ID { + extra_env.insert("ZED".to_owned(), "1".to_owned()); + extra_env.insert("SURFACE".to_owned(), "zed".to_owned()); + extra_env.insert( + "ZED_VERSION".to_owned(), + release_channel::AppVersion::global(cx).to_string(), + ); + let project_paths: Vec = project + .read(cx) + .worktrees(cx) + .filter_map(|w| { + w.read(cx) + .as_local() + .map(|l| l.abs_path().to_string_lossy().into_owned()) + }) + .collect(); + if !project_paths.is_empty() { + extra_env.insert("ZED_PROJECT_PATHS".to_owned(), project_paths.join(":")); + } + } let store = delegate.store.downgrade(); cx.spawn(async move |cx| { if is_registry_agent && agent_id.as_ref() == GEMINI_ID { @@ -431,4 +460,17 @@ mod tests { assert!(is_registry_agent("agent-from-settings", cx)); }); } + + #[gpui::test] + fn test_omp_agent_server_attributes(cx: &mut TestAppContext) { + init_test(cx); + let server = CustomAgentServer::new(AgentId::new(OMP_ID)); + assert_eq!(server.logo(), IconName::Omp); + cx.update(|cx| { + assert_eq!( + server.default_mode(cx), + Some(acp::SessionModeId::new("task")) + ); + }); + } } diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index 63806e9b7c..9b6ac8abf3 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -191,6 +191,9 @@ pub struct AgentServerStore { pub struct AgentServersUpdated; impl EventEmitter for AgentServerStore {} +pub const OMP_ID: &str = "omp"; +pub const OMP_NAME: &str = "Oh My Pi"; + static EXTENSION_TO_REGISTRY_IDS: LazyLock> = LazyLock::new(|| { @@ -256,6 +259,10 @@ impl AgentServerStore { self.external_agents .get(name) .and_then(|entry| entry.display_name.clone()) + .or_else(|| match name.0.as_ref() { + OMP_ID => Some(SharedString::from(OMP_NAME)), + _ => None, + }) } pub fn init_remote(session: &AnyProtoClient) { @@ -360,6 +367,10 @@ impl AgentServerStore { match settings { CustomAgentServerSettings::Custom { command, .. } => { let agent_name = AgentId(name.clone().into()); + let display_name = match name.as_str() { + OMP_ID => Some(SharedString::from(OMP_NAME)), + _ => None, + }; self.external_agents.insert( agent_name.clone(), ExternalAgentEntry::new( @@ -368,7 +379,7 @@ impl AgentServerStore { project_environment: project_environment.clone(), }) as Box, ExternalAgentSource::Custom, - None, + display_name, None, ), ); @@ -444,6 +455,31 @@ impl AgentServerStore { } } + // Auto-detect Oh My Pi (omp) harness if not explicitly configured in settings + let omp_agent_id = AgentId(OMP_ID.into()); + if !self.external_agents.contains_key(&omp_agent_id) + && !new_settings.contains_key(OMP_ID) + { + if let Ok(omp_path) = which::which(OMP_ID) { + self.external_agents.insert( + omp_agent_id, + ExternalAgentEntry::new( + Box::new(LocalCustomAgent { + command: AgentServerCommand { + path: omp_path, + args: vec!["acp".to_string()], + env: None, + }, + project_environment: project_environment.clone(), + }) as Box, + ExternalAgentSource::Custom, + Some(SharedString::from(OMP_NAME)), + None, + ), + ); + } + } + // For each rebuilt versioned agent, compare the version. If it // changed, notify the active connection to reconnect. Otherwise, // transfer the channel to the new entry so future updates can use it. @@ -2337,4 +2373,17 @@ mod tests { ); }); } + + #[gpui::test] + fn test_omp_agent_display_name(_cx: &mut TestAppContext) { + let store = AgentServerStore::collab(); + assert_eq!( + store.agent_display_name(&AgentId::new(OMP_ID)), + Some(SharedString::from(OMP_NAME)) + ); + assert_eq!( + store.agent_display_name(&AgentId::new("random_agent")), + None + ); + } }