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:
1 parent
cde7c1e0da
commit
5b66eb93be
5 files changed
+69
-19
No files matched your search
@@ -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
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user