agent: Support initial prompt via zed://agent URL schema (#47959)

Adds `zed://agent?prompt=<url_encoded_text>` URL support to open the
Agent Panel with a pre-filled prompt.

Release Notes:

- Added support for opening the Agent Panel with an initial prompt via
`zed://agent?prompt=<url_encoded_text>` URL.
This commit is contained in:
Smit Barmase authored and GitHub committed 2026-01-30 00:52:47 +05:30
1 parent cde7c1e0da
commit 5b66eb93be
5 files changed
+69 -19

No files matched your search

+18 -7
View File
@@ -78,10 +78,10 @@ use crate::user_slash_command::{
};
use crate::{
AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, AuthorizeToolCall, ClearMessageQueue,
CycleFavoriteModels, CycleModeSelector, EditFirstQueuedMessage, ExpandMessageEditor, Follow,
KeepAll, NewThread, OpenAddContextMenu, OpenAgentDiff, OpenHistory, RejectAll, RejectOnce,
RemoveFirstQueuedMessage, SelectPermissionGranularity, SendImmediately, SendNextQueuedMessage,
ToggleProfileSelector, ToggleThinkingMode,
CycleFavoriteModels, CycleModeSelector, EditFirstQueuedMessage, ExpandMessageEditor,
ExternalAgentInitialContent, Follow, KeepAll, NewThread, OpenAddContextMenu, OpenAgentDiff,
OpenHistory, RejectAll, RejectOnce, RemoveFirstQueuedMessage, SelectPermissionGranularity,
SendImmediately, SendNextQueuedMessage, ToggleProfileSelector, ToggleThinkingMode,
};
const STOPWATCH_THRESHOLD: Duration = Duration::from_secs(30);
@@ -388,7 +388,7 @@ impl AcpThreadView {
pub fn new(
agent: Rc<dyn AgentServer>,
resume_thread: Option<AgentSessionInfo>,
summarize_thread: Option<AgentSessionInfo>,
initial_content: Option<ExternalAgentInitialContent>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
thread_store: Option<Entity<ThreadStore>>,
@@ -430,8 +430,19 @@ impl AcpThreadView {
window,
cx,
);
if let Some(entry) = summarize_thread {
editor.insert_thread_summary(entry, window, cx);
if let Some(content) = initial_content {
match content {
ExternalAgentInitialContent::ThreadSummary(entry) => {
editor.insert_thread_summary(entry, window, cx);
}
ExternalAgentInitialContent::Text(prompt) => {
editor.set_message(
vec![acp::ContentBlock::Text(acp::TextContent::new(prompt))],
window,
cx,
);
}
}
}
editor
});
+24 -6
View File
@@ -30,7 +30,10 @@ use crate::{
acp::{AcpThreadHistory, ThreadHistoryEvent},
text_thread_history::{TextThreadHistory, TextThreadHistoryEvent},
};
use crate::{ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary};
use crate::{
ExternalAgent, ExternalAgentInitialContent, NewExternalAgentThread,
NewNativeAgentThreadFromSummary,
};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
@@ -742,7 +745,7 @@ impl AgentPanel {
self.external_thread(
Some(ExternalAgent::NativeAgent),
None,
Some(thread),
Some(ExternalAgentInitialContent::ThreadSummary(thread)),
window,
cx,
);
@@ -795,7 +798,7 @@ impl AgentPanel {
&mut self,
agent_choice: Option<crate::ExternalAgent>,
resume_thread: Option<AgentSessionInfo>,
summarize_thread: Option<AgentSessionInfo>,
initial_content: Option<ExternalAgentInitialContent>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -857,7 +860,7 @@ impl AgentPanel {
agent_panel._external_thread(
server,
resume_thread,
summarize_thread,
initial_content,
workspace,
project,
ext_agent,
@@ -1414,6 +1417,21 @@ impl AgentPanel {
}
}
pub fn new_external_thread_with_text(
&mut self,
initial_text: Option<String>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.external_thread(
None,
None,
initial_text.map(ExternalAgentInitialContent::Text),
window,
cx,
);
}
pub fn new_agent_thread(
&mut self,
agent: AgentType,
@@ -1476,7 +1494,7 @@ impl AgentPanel {
&mut self,
server: Rc<dyn AgentServer>,
resume_thread: Option<AgentSessionInfo>,
summarize_thread: Option<AgentSessionInfo>,
initial_content: Option<ExternalAgentInitialContent>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
ext_agent: ExternalAgent,
@@ -1498,7 +1516,7 @@ impl AgentPanel {
crate::acp::AcpThreadView::new(
server,
resume_thread,
summarize_thread,
initial_content,
workspace.clone(),
project,
thread_store,
+6
View File
@@ -217,6 +217,12 @@ impl ExternalAgent {
}
}
/// Content to initialize new external agent with.
pub enum ExternalAgentInitialContent {
ThreadSummary(acp_thread::AgentSessionInfo),
Text(String),
}
/// Opens the profile management interface for configuring agent tools and settings.
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
+5 -3
View File
@@ -846,13 +846,15 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
})
.detach_and_log_err(cx);
}
OpenRequestKind::AgentPanel => {
OpenRequestKind::AgentPanel { initial_prompt } => {
cx.spawn(async move |cx| {
let workspace =
workspace::get_any_active_workspace(app_state, cx.clone()).await?;
workspace.update(cx, |workspace, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
panel.focus_handle(cx).focus(window, cx);
if let Some(panel) = workspace.focus_panel::<AgentPanel>(window, cx) {
panel.update(cx, |panel, cx| {
panel.new_external_thread_with_text(initial_prompt, window, cx);
});
}
})
})
+16 -3
View File
@@ -48,7 +48,9 @@ pub enum OpenRequestKind {
Extension {
extension_id: String,
},
AgentPanel,
AgentPanel {
initial_prompt: Option<String>,
},
SharedAgentThread {
session_id: String,
},
@@ -108,8 +110,8 @@ impl OpenRequest {
this.kind = Some(OpenRequestKind::Extension {
extension_id: extension_id.to_string(),
});
} else if url == "zed://agent" {
this.kind = Some(OpenRequestKind::AgentPanel);
} else if let Some(agent_path) = url.strip_prefix("zed://agent") {
this.parse_agent_url(agent_path)
} else if let Some(session_id_str) = url.strip_prefix("zed://agent/shared/") {
if uuid::Uuid::parse_str(session_id_str).is_ok() {
this.kind = Some(OpenRequestKind::SharedAgentThread {
@@ -160,6 +162,17 @@ impl OpenRequest {
}
}
fn parse_agent_url(&mut self, agent_path: &str) {
// Format: "" or "?prompt=<text>"
let initial_prompt = agent_path.strip_prefix('?').and_then(|query| {
url::form_urlencoded::parse(query.as_bytes())
.find_map(|(key, value)| (key == "prompt").then_some(value))
.filter(|s| !s.is_empty())
.map(|s| s.into_owned())
});
self.kind = Some(OpenRequestKind::AgentPanel { initial_prompt });
}
fn parse_git_clone_url(&mut self, clone_path: &str) -> Result<()> {
// Format: /?repo=<url> or ?repo=<url>
let clone_path = clone_path.strip_prefix('/').unwrap_or(clone_path);