Compare commits

..

4 Commits

Author SHA1 Message Date
Antonio Scandurra
de8f1c3c84 WIP 2025-06-30 21:01:37 +02:00
Antonio Scandurra
9351e959a5 WIP 2025-06-30 19:32:42 +02:00
Antonio Scandurra
dac0838a80 WIP 2025-06-30 14:24:24 +02:00
Antonio Scandurra
38d5f36d38 WIP 2025-06-30 12:15:19 +02:00
46 changed files with 4634 additions and 8407 deletions

1
Cargo.lock generated
View File

@@ -78,6 +78,7 @@ dependencies = [
"language",
"language_model",
"log",
"markdown",
"parking_lot",
"paths",
"postage",

View File

@@ -42,6 +42,7 @@ itertools.workspace = true
language.workspace = true
language_model.workspace = true
log.workspace = true
markdown.workspace = true
paths.workspace = true
postage.workspace = true
project.workspace = true
@@ -68,7 +69,6 @@ zstd.workspace = true
[dev-dependencies]
assistant_tools.workspace = true
assistant_tool = { workspace = true, "features" = ["test-support"] }
gpui = { workspace = true, "features" = ["test-support"] }
indoc.workspace = true
language = { workspace = true, "features" = ["test-support"] }

View File

@@ -1,3 +1,4 @@
mod agent2;
pub mod agent_profile;
pub mod context;
pub mod context_server_tool;
@@ -5,14 +6,17 @@ pub mod context_store;
pub mod history_store;
pub mod thread;
pub mod thread_store;
mod zed_agent;
pub use agent2::*;
pub use context::{AgentContext, ContextId, ContextLoadResult};
pub use context_store::ContextStore;
pub use thread::{
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, ThreadError,
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio, ZedAgentThread,
LastRestoreCheckpoint, Message, MessageCrease, MessageSegment, Thread, ThreadError,
ThreadEvent, ThreadFeedback, ThreadTitle, TokenUsageRatio,
};
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
pub use zed_agent::*;
pub fn init(cx: &mut gpui::App) {
thread_store::init(cx);

117
crates/agent/src/agent2.rs Normal file
View File

@@ -0,0 +1,117 @@
use anyhow::Result;
use assistant_tool::{Tool, ToolResultOutput};
use futures::{channel::oneshot, future::BoxFuture, stream::BoxStream};
use gpui::SharedString;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display},
sync::Arc,
};
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
pub struct ThreadId(SharedString);
impl ThreadId {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_string(&self) -> String {
self.0.to_string()
}
}
impl From<&str> for ThreadId {
fn from(value: &str) -> Self {
ThreadId(SharedString::from(value.to_string()))
}
}
impl Display for ThreadId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct MessageId(pub usize);
#[derive(Debug, Clone)]
pub struct AgentThreadToolCallId(SharedString);
pub enum AgentThreadResponseEvent {
Text(String),
Thinking(String),
ToolCallChunk {
id: AgentThreadToolCallId,
tool: Arc<dyn Tool>,
input: serde_json::Value,
},
ToolCall {
id: AgentThreadToolCallId,
tool: Arc<dyn Tool>,
input: serde_json::Value,
response_tx: oneshot::Sender<Result<ToolResultOutput>>,
},
}
pub enum AgentThreadMessage {
User {
id: MessageId,
chunks: Vec<AgentThreadUserMessageChunk>,
},
Assistant {
id: MessageId,
chunks: Vec<AgentThreadAssistantMessageChunk>,
},
}
pub enum AgentThreadUserMessageChunk {
Text(String),
// here's where we would put mentions, etc.
}
pub enum AgentThreadAssistantMessageChunk {
Text(String),
Thinking(String),
ToolResult {
id: AgentThreadToolCallId,
tool: Arc<dyn Tool>,
input: serde_json::Value,
output: Result<ToolResultOutput>,
},
}
pub struct AgentThreadResponse {
pub user_message_id: MessageId,
pub assistant_message_id: MessageId,
pub events: BoxStream<'static, Result<AgentThreadResponseEvent>>,
}
pub trait Agent {
fn create_thread() -> BoxFuture<'static, Result<Arc<dyn AgentThread>>>;
fn list_threads();
fn load_thread(&self, thread_id: ThreadId) -> BoxFuture<'static, Result<Arc<dyn AgentThread>>>;
}
pub trait AgentThread {
fn id(&self) -> ThreadId;
fn title(&self) -> BoxFuture<'static, Result<String>>;
fn summary(&self) -> BoxFuture<'static, Result<String>>;
fn messages(&self) -> BoxFuture<'static, Result<Vec<AgentThreadMessage>>>;
fn truncate(&self, message_id: MessageId) -> BoxFuture<'static, Result<()>>;
fn edit(
&self,
message_id: MessageId,
content: Vec<AgentThreadUserMessageChunk>,
max_iterations: usize,
) -> BoxFuture<'static, Result<AgentThreadResponse>>;
fn send(
&self,
content: Vec<AgentThreadUserMessageChunk>,
max_iterations: usize,
) -> BoxFuture<'static, Result<AgentThreadResponse>>;
}

View File

@@ -1,4 +1,4 @@
use crate::thread::ZedAgentThread;
use crate::thread::Thread;
use assistant_context::AssistantContext;
use assistant_tool::outline;
use collections::HashSet;
@@ -560,7 +560,7 @@ impl Display for FetchedUrlContext {
#[derive(Debug, Clone)]
pub struct ThreadContextHandle {
pub agent: Entity<ZedAgentThread>,
pub thread: Entity<Thread>,
pub context_id: ContextId,
}
@@ -573,23 +573,23 @@ pub struct ThreadContext {
impl ThreadContextHandle {
pub fn eq_for_key(&self, other: &Self) -> bool {
self.agent == other.agent
self.thread == other.thread
}
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
self.agent.hash(state)
self.thread.hash(state)
}
pub fn title(&self, cx: &App) -> SharedString {
self.agent.read(cx).summary().or_default()
self.thread.read(cx).title().or_default()
}
fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
cx.spawn(async move |cx| {
let text = ZedAgentThread::wait_for_detailed_summary_or_text(&self.agent, cx).await?;
let text = Thread::wait_for_detailed_summary_or_text(&self.thread, cx).await?;
let title = self
.agent
.read_with(cx, |thread, _| thread.summary().or_default())
.thread
.read_with(cx, |thread, _cx| thread.title().or_default())
.ok()?;
let context = AgentContext::Thread(ThreadContext {
title,

View File

@@ -1,10 +1,11 @@
use crate::{
MessageId, ThreadId,
context::{
AgentContextHandle, AgentContextKey, ContextId, ContextKind, DirectoryContextHandle,
FetchedUrlContext, FileContextHandle, ImageContext, RulesContextHandle,
SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
},
thread::{MessageId, ThreadId, ZedAgentThread},
thread::Thread,
thread_store::ThreadStore,
};
use anyhow::{Context as _, Result, anyhow};
@@ -66,13 +67,13 @@ impl ContextStore {
pub fn new_context_for_thread(
&self,
thread: &ZedAgentThread,
thread: &Thread,
exclude_messages_from_id: Option<MessageId>,
_cx: &App,
) -> Vec<AgentContextHandle> {
let existing_context = thread
.messages()
.take_while(|message| exclude_messages_from_id.is_none_or(|id| message.id != id))
.iter()
.take_while(|message| exclude_messages_from_id.is_none_or(|id| message.id() != id))
.flat_map(|message| {
message
.loaded_context
@@ -207,15 +208,12 @@ impl ContextStore {
pub fn add_thread(
&mut self,
thread: Entity<ZedAgentThread>,
thread: Entity<Thread>,
remove_if_exists: bool,
cx: &mut Context<Self>,
) -> Option<AgentContextHandle> {
let context_id = self.next_context_id.post_inc();
let context = AgentContextHandle::Thread(ThreadContextHandle {
agent: thread,
context_id,
});
let context = AgentContextHandle::Thread(ThreadContextHandle { thread, context_id });
if let Some(existing) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
if remove_if_exists {
@@ -391,10 +389,7 @@ impl ContextStore {
if let Some(thread) = thread.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
AgentContextHandle::Thread(ThreadContextHandle {
agent: thread,
context_id,
}),
AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
cx,
);
}
@@ -418,11 +413,11 @@ impl ContextStore {
match &context {
AgentContextHandle::Thread(thread_context) => {
if let Some(thread_store) = self.thread_store.clone() {
thread_context.agent.update(cx, |thread, cx| {
thread_context.thread.update(cx, |thread, cx| {
thread.start_generating_detailed_summary_if_needed(thread_store, cx);
});
self.context_thread_ids
.insert(thread_context.agent.read(cx).id().clone());
.insert(thread_context.thread.read(cx).id().clone());
} else {
return false;
}
@@ -448,7 +443,7 @@ impl ContextStore {
match context {
AgentContextHandle::Thread(thread_context) => {
self.context_thread_ids
.remove(thread_context.agent.read(cx).id());
.remove(&thread_context.thread.read(cx).id());
}
AgentContextHandle::TextThread(text_thread_context) => {
if let Some(path) = text_thread_context.context.read(cx).path() {
@@ -577,7 +572,7 @@ pub enum SuggestedContext {
},
Thread {
name: SharedString,
thread: WeakEntity<ZedAgentThread>,
thread: WeakEntity<Thread>,
},
TextThread {
name: SharedString,

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
use crate::{
MessageId, ThreadId,
context_server_tool::ContextServerTool,
thread::{
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, ThreadId, ZedAgentThread,
},
thread::{DetailedSummaryState, ExceededWindowError, ProjectSnapshot, Thread},
};
use agent_settings::{AgentProfileId, CompletionMode};
use anyhow::{Context as _, Result, anyhow};
@@ -400,35 +399,17 @@ impl ThreadStore {
self.threads.iter()
}
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<ZedAgentThread> {
cx.new(|cx| {
ZedAgentThread::new(
self.project.clone(),
self.tools.clone(),
self.prompt_builder.clone(),
self.project_context.clone(),
cx,
)
})
}
pub fn create_thread_from_serialized(
&mut self,
serialized: SerializedThread,
cx: &mut Context<Self>,
) -> Entity<ZedAgentThread> {
cx.new(|cx| {
ZedAgentThread::deserialize(
ThreadId::new(),
serialized,
self.project.clone(),
self.tools.clone(),
self.prompt_builder.clone(),
self.project_context.clone(),
None,
cx,
)
})
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Thread>>> {
todo!()
// cx.new(|cx| {
// Thread::new(
// self.project.clone(),
// self.tools.clone(),
// self.prompt_builder.clone(),
// self.project_context.clone(),
// cx,
// )
// })
}
pub fn open_thread(
@@ -436,7 +417,7 @@ impl ThreadStore {
id: &ThreadId,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<Entity<ZedAgentThread>>> {
) -> Task<Result<Entity<Thread>>> {
let id = id.clone();
let database_future = ThreadsDatabase::global_future(cx);
let this = cx.weak_entity();
@@ -447,56 +428,53 @@ impl ThreadStore {
.await?
.with_context(|| format!("no thread found with ID: {id:?}"))?;
let thread = this.update_in(cx, |this, window, cx| {
cx.new(|cx| {
ZedAgentThread::deserialize(
id.clone(),
thread,
this.project.clone(),
this.tools.clone(),
this.prompt_builder.clone(),
this.project_context.clone(),
Some(window),
cx,
)
})
})?;
Ok(thread)
todo!();
// let thread = this.update_in(cx, |this, window, cx| {
// cx.new(|cx| {
// Thread::deserialize(
// id.clone(),
// thread,
// this.project.clone(),
// this.tools.clone(),
// this.prompt_builder.clone(),
// this.project_context.clone(),
// Some(window),
// cx,
// )
// })
// })?;
// Ok(thread)
})
}
pub fn save_thread(
&self,
thread: &Entity<ZedAgentThread>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let (metadata, serialized_thread) = thread.update(cx, |thread, cx| {
(thread.id().clone(), thread.serialize(cx))
});
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
todo!()
// let (metadata, serialized_thread) =
// thread.update(cx, |thread, cx| (thread.id().clone(), thread.serialize(cx)));
let database_future = ThreadsDatabase::global_future(cx);
cx.spawn(async move |this, cx| {
let serialized_thread = serialized_thread.await?;
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.save_thread(metadata, serialized_thread).await?;
// let database_future = ThreadsDatabase::global_future(cx);
// cx.spawn(async move |this, cx| {
// let serialized_thread = serialized_thread.await?;
// let database = database_future.await.map_err(|err| anyhow!(err))?;
// database.save_thread(metadata, serialized_thread).await?;
this.update(cx, |this, cx| this.reload(cx))?.await
})
// this.update(cx, |this, cx| this.reload(cx))?.await
// })
}
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
let id = id.clone();
let database_future = ThreadsDatabase::global_future(cx);
cx.spawn(async move |this, cx| {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.delete_thread(id.clone()).await?;
todo!()
// let id = id.clone();
// let database_future = ThreadsDatabase::global_future(cx);
// cx.spawn(async move |this, cx| {
// let database = database_future.await.map_err(|err| anyhow!(err))?;
// database.delete_thread(id.clone()).await?;
this.update(cx, |this, cx| {
this.threads.retain(|thread| thread.id != id);
cx.notify();
})
})
// this.update(cx, |this, cx| {
// this.threads.retain(|thread| thread.id != id);
// cx.notify();
// })
// })
}
pub fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
@@ -705,7 +683,7 @@ impl SerializedThreadV0_1_0 {
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedMessage {
pub id: MessageId,
pub role: Role,
@@ -719,9 +697,11 @@ pub struct SerializedMessage {
pub context: String,
#[serde(default)]
pub creases: Vec<SerializedCrease>,
#[serde(default)]
pub is_hidden: bool,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum SerializedMessageSegment {
#[serde(rename = "text")]
@@ -739,14 +719,14 @@ pub enum SerializedMessageSegment {
},
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedToolUse {
pub id: LanguageModelToolUseId,
pub name: SharedString,
pub input: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool,
@@ -804,11 +784,12 @@ impl LegacySerializedMessage {
tool_results: self.tool_results,
context: String::new(),
creases: Vec::new(),
is_hidden: false,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct SerializedCrease {
pub start: usize,
pub end: usize,
@@ -1069,7 +1050,7 @@ impl ThreadsDatabase {
#[cfg(test)]
mod tests {
use super::*;
use crate::thread::{DetailedSummaryState, MessageId};
use crate::{MessageId, thread::DetailedSummaryState};
use chrono::Utc;
use language_model::{Role, TokenUsage};
use pretty_assertions::assert_eq;
@@ -1107,6 +1088,7 @@ mod tests {
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false
}],
version: SerializedThread::VERSION.to_string(),
initial_project_snapshot: None,
@@ -1139,6 +1121,7 @@ mod tests {
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
SerializedMessage {
id: MessageId(2),
@@ -1154,6 +1137,7 @@ mod tests {
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
SerializedMessage {
id: MessageId(1),
@@ -1170,6 +1154,7 @@ mod tests {
}],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
],
version: SerializedThreadV0_1_0::VERSION.to_string(),
@@ -1201,6 +1186,7 @@ mod tests {
tool_results: vec![],
context: "".to_string(),
creases: vec![],
is_hidden: false
},
SerializedMessage {
id: MessageId(2),
@@ -1221,6 +1207,7 @@ mod tests {
}],
context: "".to_string(),
creases: vec![],
is_hidden: false,
},
],
version: SerializedThread::VERSION.to_string(),

View File

@@ -0,0 +1 @@
pub struct ZedAgentThread {}

View File

@@ -96,7 +96,6 @@ zed_llm_client.workspace = true
[dev-dependencies]
assistant_tools.workspace = true
assistant_tool = { workspace = true, "features" = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, "features" = ["test-support"] }

File diff suppressed because it is too large Load Diff

View File

@@ -180,7 +180,7 @@ impl ConfigurationSource {
}
fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String {
let (name, command, args, env) = match existing {
let (name, path, args, env) = match existing {
Some((id, cmd)) => {
let args = serde_json::to_string(&cmd.args).unwrap();
let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap();
@@ -198,12 +198,14 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
r#"{{
/// The name of your MCP server
"{name}": {{
/// The command which runs the MCP server
"command": "{command}",
/// The arguments to pass to the MCP server
"args": {args},
/// The environment variables to set
"env": {env}
"command": {{
/// The path to the executable
"path": "{path}",
/// The arguments to pass to the executable
"args": {args},
/// The environment variables to set for the executable
"env": {env}
}}
}}
}}"#
)
@@ -437,7 +439,8 @@ fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> {
let object = value.as_object().context("Expected object")?;
anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair");
let (context_server_name, value) = object.into_iter().next().unwrap();
let command: ContextServerCommand = serde_json::from_value(value.clone())?;
let command = value.get("command").context("Expected command")?;
let command: ContextServerCommand = serde_json::from_value(command.clone())?;
Ok((ContextServerId(context_server_name.clone().into()), command))
}

View File

@@ -1,8 +1,7 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
use agent::{ThreadEvent, ZedAgentThread};
use agent::{Thread, ThreadEvent};
use agent_settings::AgentSettings;
use anyhow::Result;
use assistant_tool::ActionLog;
use buffer_diff::DiffHunkStatus;
use collections::{HashMap, HashSet};
use editor::{
@@ -42,8 +41,7 @@ use zed_actions::assistant::ToggleFocus;
pub struct AgentDiffPane {
multibuffer: Entity<MultiBuffer>,
editor: Entity<Editor>,
agent: Entity<ZedAgentThread>,
action_log: Entity<ActionLog>,
thread: Entity<Thread>,
focus_handle: FocusHandle,
workspace: WeakEntity<Workspace>,
title: SharedString,
@@ -52,71 +50,70 @@ pub struct AgentDiffPane {
impl AgentDiffPane {
pub fn deploy(
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App,
) -> Result<Entity<Self>> {
workspace.update(cx, |workspace, cx| {
Self::deploy_in_workspace(agent, workspace, window, cx)
Self::deploy_in_workspace(thread, workspace, window, cx)
})
}
pub fn deploy_in_workspace(
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let existing_diff = workspace
.items_of_type::<AgentDiffPane>(cx)
.find(|diff| diff.read(cx).agent == agent);
.find(|diff| diff.read(cx).thread == thread);
if let Some(existing_diff) = existing_diff {
workspace.activate_item(&existing_diff, true, true, window, cx);
existing_diff
} else {
let agent_diff =
cx.new(|cx| AgentDiffPane::new(agent.clone(), workspace.weak_handle(), window, cx));
let agent_diff = cx
.new(|cx| AgentDiffPane::new(thread.clone(), workspace.weak_handle(), window, cx));
workspace.add_item_to_center(Box::new(agent_diff.clone()), window, cx);
agent_diff
}
}
pub fn new(
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let focus_handle = cx.focus_handle();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
let action_log = agent.read(cx).action_log();
let project = agent.read(cx).project().clone();
let project = thread.read(cx).project().clone();
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
editor.disable_inline_diagnostics();
editor.set_expand_all_diff_hunks(cx);
editor.set_render_diff_hunk_controls(diff_hunk_controls(&action_log), cx);
editor.set_render_diff_hunk_controls(diff_hunk_controls(&thread), cx);
editor.register_addon(AgentDiffAddon);
editor
});
let action_log = thread.read(cx).action_log().clone();
let mut this = Self {
_subscriptions: vec![
cx.observe_in(&action_log, window, |this, _action_log, window, cx| {
this.update_excerpts(window, cx)
}),
cx.subscribe(&agent, |this, _thread, event, cx| {
cx.subscribe(&thread, |this, _thread, event, cx| {
this.handle_thread_event(event, cx)
}),
],
title: SharedString::default(),
action_log,
multibuffer,
editor,
agent,
thread,
focus_handle,
workspace,
};
@@ -126,8 +123,8 @@ impl AgentDiffPane {
}
fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let agent = self.agent.read(cx);
let changed_buffers = agent.action_log().read(cx).changed_buffers(cx);
let thread = self.thread.read(cx);
let changed_buffers = thread.action_log().read(cx).changed_buffers(cx);
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
for (buffer, diff_handle) in changed_buffers {
@@ -214,7 +211,7 @@ impl AgentDiffPane {
}
fn update_title(&mut self, cx: &mut Context<Self>) {
let new_title = self.agent.read(cx).summary().unwrap_or("Agent Changes");
let new_title = self.thread.read(cx).title().unwrap_or("Agent Changes");
if new_title != self.title {
self.title = new_title;
cx.emit(EditorEvent::TitleChanged);
@@ -251,14 +248,14 @@ impl AgentDiffPane {
fn keep(&mut self, _: &Keep, window: &mut Window, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
keep_edits_in_selection(editor, &snapshot, &self.action_log, window, cx);
keep_edits_in_selection(editor, &snapshot, &self.thread, window, cx);
});
}
fn reject(&mut self, _: &Reject, window: &mut Window, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
reject_edits_in_selection(editor, &snapshot, &self.action_log, window, cx);
reject_edits_in_selection(editor, &snapshot, &self.thread, window, cx);
});
}
@@ -268,7 +265,7 @@ impl AgentDiffPane {
reject_edits_in_ranges(
editor,
&snapshot,
&self.action_log,
&self.thread,
vec![editor::Anchor::min()..editor::Anchor::max()],
window,
cx,
@@ -277,15 +274,15 @@ impl AgentDiffPane {
}
fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
self.action_log
.update(cx, |action_log, cx| action_log.keep_all_edits(cx));
self.thread
.update(cx, |thread, cx| thread.keep_all_edits(cx));
}
}
fn keep_edits_in_selection(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
action_log: &Entity<ActionLog>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -294,13 +291,13 @@ fn keep_edits_in_selection(
.disjoint_anchor_ranges()
.collect::<Vec<_>>();
keep_edits_in_ranges(editor, buffer_snapshot, &action_log, ranges, window, cx)
keep_edits_in_ranges(editor, buffer_snapshot, &thread, ranges, window, cx)
}
fn reject_edits_in_selection(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
action_log: &Entity<ActionLog>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -308,13 +305,13 @@ fn reject_edits_in_selection(
.selections
.disjoint_anchor_ranges()
.collect::<Vec<_>>();
reject_edits_in_ranges(editor, buffer_snapshot, &action_log, ranges, window, cx)
reject_edits_in_ranges(editor, buffer_snapshot, &thread, ranges, window, cx)
}
fn keep_edits_in_ranges(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
action_log: &Entity<ActionLog>,
thread: &Entity<Thread>,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -329,8 +326,8 @@ fn keep_edits_in_ranges(
for hunk in &diff_hunks_in_ranges {
let buffer = multibuffer.read(cx).buffer(hunk.buffer_id);
if let Some(buffer) = buffer {
action_log.update(cx, |action_log, cx| {
action_log.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx)
thread.update(cx, |thread, cx| {
thread.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx)
});
}
}
@@ -339,7 +336,7 @@ fn keep_edits_in_ranges(
fn reject_edits_in_ranges(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
action_log: &Entity<ActionLog>,
thread: &Entity<Thread>,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -364,9 +361,9 @@ fn reject_edits_in_ranges(
}
for (buffer, ranges) in ranges_by_buffer {
action_log
.update(cx, |action_log, cx| {
action_log.reject_edits_in_ranges(buffer, ranges, cx)
thread
.update(cx, |thread, cx| {
thread.reject_edits_in_ranges(buffer, ranges, cx)
})
.detach_and_log_err(cx);
}
@@ -464,7 +461,7 @@ impl Item for AgentDiffPane {
}
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
let summary = self.agent.read(cx).summary().or_default();
let summary = self.thread.read(cx).title().unwrap_or("Agent Changes");
Label::new(format!("Review: {}", summary))
.color(if params.selected {
Color::Default
@@ -514,7 +511,7 @@ impl Item for AgentDiffPane {
where
Self: Sized,
{
Some(cx.new(|cx| Self::new(self.agent.clone(), self.workspace.clone(), window, cx)))
Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx)))
}
fn is_dirty(&self, cx: &App) -> bool {
@@ -644,8 +641,8 @@ impl Render for AgentDiffPane {
}
}
fn diff_hunk_controls(action_log: &Entity<ActionLog>) -> editor::RenderDiffHunkControlsFn {
let action_log = action_log.clone();
fn diff_hunk_controls(thread: &Entity<Thread>) -> editor::RenderDiffHunkControlsFn {
let thread = thread.clone();
Arc::new(
move |row,
@@ -663,7 +660,7 @@ fn diff_hunk_controls(action_log: &Entity<ActionLog>) -> editor::RenderDiffHunkC
hunk_range,
is_created_file,
line_height,
&action_log,
&thread,
editor,
window,
cx,
@@ -679,7 +676,7 @@ fn render_diff_hunk_controls(
hunk_range: Range<editor::Anchor>,
is_created_file: bool,
line_height: Pixels,
action_log: &Entity<ActionLog>,
thread: &Entity<Thread>,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
@@ -714,14 +711,14 @@ fn render_diff_hunk_controls(
)
.on_click({
let editor = editor.clone();
let action_log = action_log.clone();
let thread = thread.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
reject_edits_in_ranges(
editor,
&snapshot,
&action_log,
&thread,
vec![hunk_range.start..hunk_range.start],
window,
cx,
@@ -736,14 +733,14 @@ fn render_diff_hunk_controls(
)
.on_click({
let editor = editor.clone();
let action_log = action_log.clone();
let thread = thread.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
keep_edits_in_ranges(
editor,
&snapshot,
&action_log,
&thread,
vec![hunk_range.start..hunk_range.start],
window,
cx,
@@ -1117,7 +1114,7 @@ impl Render for AgentDiffToolbar {
let has_pending_edit_tool_use = agent_diff
.read(cx)
.agent
.thread
.read(cx)
.has_pending_edit_tool_uses();
@@ -1190,7 +1187,7 @@ pub enum EditorState {
}
struct WorkspaceThread {
agent: WeakEntity<ZedAgentThread>,
thread: WeakEntity<Thread>,
_thread_subscriptions: [Subscription; 2],
singleton_editors: HashMap<WeakEntity<Buffer>, HashMap<WeakEntity<Editor>, Subscription>>,
_settings_subscription: Subscription,
@@ -1215,7 +1212,7 @@ impl AgentDiff {
pub fn set_active_thread(
workspace: &WeakEntity<Workspace>,
thread: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut App,
) {
@@ -1227,11 +1224,11 @@ impl AgentDiff {
fn register_active_thread_impl(
&mut self,
workspace: &WeakEntity<Workspace>,
agent: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let action_log = agent.read(cx).action_log().clone();
let action_log = thread.read(cx).action_log().clone();
let action_log_subscription = cx.observe_in(&action_log, window, {
let workspace = workspace.clone();
@@ -1240,7 +1237,7 @@ impl AgentDiff {
}
});
let thread_subscription = cx.subscribe_in(&agent, window, {
let thread_subscription = cx.subscribe_in(&thread, window, {
let workspace = workspace.clone();
move |this, _thread, event, window, cx| {
this.handle_thread_event(&workspace, event, window, cx)
@@ -1249,7 +1246,7 @@ impl AgentDiff {
if let Some(workspace_thread) = self.workspace_threads.get_mut(&workspace) {
// replace thread and action log subscription, but keep editors
workspace_thread.agent = agent.downgrade();
workspace_thread.thread = thread.downgrade();
workspace_thread._thread_subscriptions = [action_log_subscription, thread_subscription];
self.update_reviewing_editors(&workspace, window, cx);
return;
@@ -1274,7 +1271,7 @@ impl AgentDiff {
self.workspace_threads.insert(
workspace.clone(),
WorkspaceThread {
agent: agent.downgrade(),
thread: thread.downgrade(),
_thread_subscriptions: [action_log_subscription, thread_subscription],
singleton_editors: HashMap::default(),
_settings_subscription: settings_subscription,
@@ -1322,7 +1319,7 @@ impl AgentDiff {
fn register_review_action<T: Action>(
workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &Entity<ZedAgentThread>, &mut Window, &mut App) -> PostReviewState
review: impl Fn(&Entity<Editor>, &Entity<Thread>, &mut Window, &mut App) -> PostReviewState
+ 'static,
this: &Entity<AgentDiff>,
) {
@@ -1365,7 +1362,6 @@ impl AgentDiff {
| ThreadEvent::StreamedAssistantText(_, _)
| ThreadEvent::StreamedAssistantThinking(_, _)
| ThreadEvent::StreamedToolUse { .. }
| ThreadEvent::StreamedToolUse2 { .. }
| ThreadEvent::InvalidToolInput { .. }
| ThreadEvent::MissingToolUse { .. }
| ThreadEvent::MessageAdded(_)
@@ -1373,8 +1369,6 @@ impl AgentDiff {
| ThreadEvent::MessageDeleted(_)
| ThreadEvent::SummaryGenerated
| ThreadEvent::SummaryChanged
| ThreadEvent::UsePendingTools { .. }
| ThreadEvent::ToolFinished { .. }
| ThreadEvent::CheckpointChanged
| ThreadEvent::ToolConfirmationNeeded
| ThreadEvent::ToolUseLimitReached
@@ -1485,11 +1479,11 @@ impl AgentDiff {
return;
};
let Some(agent) = workspace_thread.agent.upgrade() else {
let Some(thread) = workspace_thread.thread.upgrade() else {
return;
};
let action_log = agent.read(cx).action_log();
let action_log = thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
let mut unaffected = self.reviewing_editors.clone();
@@ -1514,7 +1508,7 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx);
});
let new_state = if agent.read(cx).is_generating() {
let new_state = if thread.read(cx).is_generating() {
EditorState::Generating
} else {
EditorState::Reviewing
@@ -1527,7 +1521,7 @@ impl AgentDiff {
if previous_state.is_none() {
editor.update(cx, |editor, cx| {
editor.start_temporary_diff_override();
editor.set_render_diff_hunk_controls(diff_hunk_controls(&action_log), cx);
editor.set_render_diff_hunk_controls(diff_hunk_controls(&thread), cx);
editor.set_expand_all_diff_hunks(cx);
editor.register_addon(EditorAgentDiffAddon);
});
@@ -1595,22 +1589,22 @@ impl AgentDiff {
return;
};
let Some(WorkspaceThread { agent, .. }) =
let Some(WorkspaceThread { thread, .. }) =
self.workspace_threads.get(&workspace.downgrade())
else {
return;
};
let Some(agent) = agent.upgrade() else {
let Some(thread) = thread.upgrade() else {
return;
};
AgentDiffPane::deploy(agent, workspace.downgrade(), window, cx).log_err();
AgentDiffPane::deploy(thread, workspace.downgrade(), window, cx).log_err();
}
fn keep_all(
editor: &Entity<Editor>,
agent: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1619,7 +1613,7 @@ impl AgentDiff {
keep_edits_in_ranges(
editor,
&snapshot,
&agent.read(cx).action_log(),
thread,
vec![editor::Anchor::min()..editor::Anchor::max()],
window,
cx,
@@ -1630,7 +1624,7 @@ impl AgentDiff {
fn reject_all(
editor: &Entity<Editor>,
thread: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1639,7 +1633,7 @@ impl AgentDiff {
reject_edits_in_ranges(
editor,
&snapshot,
&thread.read(cx).action_log(),
thread,
vec![editor::Anchor::min()..editor::Anchor::max()],
window,
cx,
@@ -1650,26 +1644,26 @@ impl AgentDiff {
fn keep(
editor: &Entity<Editor>,
agent: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
keep_edits_in_selection(editor, &snapshot, &agent.read(cx).action_log(), window, cx);
keep_edits_in_selection(editor, &snapshot, thread, window, cx);
Self::post_review_state(&snapshot)
})
}
fn reject(
editor: &Entity<Editor>,
agent: &Entity<ZedAgentThread>,
thread: &Entity<Thread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
reject_edits_in_selection(editor, &snapshot, &agent.read(cx).action_log(), window, cx);
reject_edits_in_selection(editor, &snapshot, thread, window, cx);
Self::post_review_state(&snapshot)
})
}
@@ -1686,7 +1680,7 @@ impl AgentDiff {
fn review_in_active_editor(
&mut self,
workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &Entity<ZedAgentThread>, &mut Window, &mut App) -> PostReviewState,
review: impl Fn(&Entity<Editor>, &Entity<Thread>, &mut Window, &mut App) -> PostReviewState,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
@@ -1700,13 +1694,14 @@ impl AgentDiff {
return None;
}
let WorkspaceThread { agent, .. } = self.workspace_threads.get(&workspace.weak_handle())?;
let WorkspaceThread { thread, .. } =
self.workspace_threads.get(&workspace.weak_handle())?;
let agent = agent.upgrade()?;
let thread = thread.upgrade()?;
if let PostReviewState::AllReviewed = review(&editor, &agent, window, cx) {
if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) {
if let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
let changed_buffers = agent.read(cx).action_log().read(cx).changed_buffers(cx);
let changed_buffers = thread.read(cx).action_log().read(cx).changed_buffers(cx);
let mut keys = changed_buffers.keys().cycle();
keys.find(|k| *k == &curr_buffer);
@@ -1804,13 +1799,16 @@ mod tests {
})
.await
.unwrap();
let agent = thread_store.update(cx, |store, cx| store.create_thread(cx));
let action_log = agent.read_with(cx, |agent, _| agent.action_log().clone());
let thread = thread_store
.update(cx, |store, cx| store.create_thread(cx))
.await
.unwrap();
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let agent_diff = cx.new_window_entity(|window, cx| {
AgentDiffPane::new(agent.clone(), workspace.downgrade(), window, cx)
AgentDiffPane::new(thread.clone(), workspace.downgrade(), window, cx)
});
let editor = agent_diff.read_with(cx, |diff, _cx| diff.editor.clone());
@@ -1898,7 +1896,7 @@ mod tests {
keep_edits_in_ranges(
editor,
&snapshot,
&agent.read(cx).action_log(),
&thread,
vec![position..position],
window,
cx,
@@ -1969,8 +1967,11 @@ mod tests {
})
.await
.unwrap();
let agent = thread_store.update(cx, |store, cx| store.create_thread(cx));
let action_log = agent.read_with(cx, |agent, _| agent.action_log().clone());
let thread = thread_store
.update(cx, |store, cx| store.create_thread(cx))
.await
.unwrap();
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@@ -1992,7 +1993,7 @@ mod tests {
// Set the active thread
cx.update(|window, cx| {
AgentDiff::set_active_thread(&workspace.downgrade(), &agent, window, cx)
AgentDiff::set_active_thread(&workspace.downgrade(), &thread, window, cx)
});
let buffer1 = project
@@ -2149,7 +2150,7 @@ mod tests {
keep_edits_in_ranges(
editor,
&snapshot,
&agent.read(cx).action_log(),
&thread,
vec![position..position],
window,
cx,

View File

@@ -45,7 +45,7 @@ impl AgentModelSelector {
let registry = LanguageModelRegistry::read_global(cx);
if let Some(provider) = registry.provider(&model.provider_id())
{
thread.set_configured_model(
thread.set_model(
Some(ConfiguredModel {
provider,
model: model.clone(),

View File

@@ -26,7 +26,7 @@ use crate::{
ui::AgentOnboardingModal,
};
use agent::{
ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio, ZedAgentThread,
Thread, ThreadError, ThreadEvent, ThreadId, ThreadTitle, TokenUsageRatio,
context_store::ContextStore,
history_store::{HistoryEntryId, HistoryStore},
thread_store::{TextThreadStore, ThreadStore},
@@ -122,8 +122,8 @@ pub fn init(cx: &mut App) {
workspace.focus_panel::<AgentPanel>(window, cx);
match &panel.read(cx).active_view {
ActiveView::Thread { thread, .. } => {
let agent = thread.read(cx).agent().clone();
AgentDiffPane::deploy_in_workspace(agent, workspace, window, cx);
let thread = thread.read(cx).thread().clone();
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
}
ActiveView::TextThread { .. }
| ActiveView::History
@@ -251,9 +251,9 @@ impl ActiveView {
let new_summary = editor.read(cx).text(cx);
thread.update(cx, |thread, cx| {
thread.agent().update(cx, |agent, cx| {
agent.set_summary(new_summary, cx);
})
thread.thread().update(cx, |thread, cx| {
thread.set_title(new_summary, cx);
});
})
}
EditorEvent::Blurred => {
@@ -274,11 +274,11 @@ impl ActiveView {
cx.notify();
}
}),
cx.subscribe_in(&active_thread.read(cx).agent().clone(), window, {
cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, {
let editor = editor.clone();
move |_, agent, event, window, cx| match event {
move |_, thread, event, window, cx| match event {
ThreadEvent::SummaryGenerated => {
let summary = agent.read(cx).summary().or_default();
let summary = thread.read(cx).title().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
@@ -492,10 +492,15 @@ impl AgentPanel {
None
};
let thread = thread_store
.update(cx, |this, cx| this.create_thread(cx))?
.await?;
let panel = workspace.update_in(cx, |workspace, window, cx| {
let panel = cx.new(|cx| {
Self::new(
workspace,
thread,
thread_store,
context_store,
prompt_store,
@@ -518,13 +523,13 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
thread: Entity<Thread>,
thread_store: Entity<ThreadStore>,
context_store: Entity<TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let agent = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone();
let user_store = workspace.app_state().user_store.clone();
let project = workspace.project();
@@ -546,13 +551,13 @@ impl AgentPanel {
prompt_store.clone(),
thread_store.downgrade(),
context_store.downgrade(),
agent.clone(),
thread.clone(),
window,
cx,
)
});
let thread_id = agent.read(cx).id().clone();
let thread_id = thread.read(cx).id().clone();
let history_store = cx.new(|cx| {
HistoryStore::new(
thread_store.clone(),
@@ -566,7 +571,7 @@ impl AgentPanel {
let active_thread = cx.new(|cx| {
ActiveThread::new(
agent.clone(),
thread.clone(),
thread_store.clone(),
context_store.clone(),
message_editor_context_store.clone(),
@@ -607,7 +612,7 @@ impl AgentPanel {
}
};
AgentDiff::set_active_thread(&workspace, &agent, window, cx);
AgentDiff::set_active_thread(&workspace, &thread, window, cx);
let weak_panel = weak_self.clone();
@@ -647,11 +652,12 @@ impl AgentPanel {
|this, _, event: &language_model::Event, cx| match event {
language_model::Event::DefaultModelChanged => match &this.active_view {
ActiveView::Thread { thread, .. } => {
thread
.read(cx)
.agent()
.clone()
.update(cx, |agent, cx| agent.get_or_init_configured_model(cx));
// todo!(do we need this?);
// thread
// .read(cx)
// .thread()
// .clone()
// .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
}
ActiveView::TextThread { .. }
| ActiveView::History
@@ -753,7 +759,7 @@ impl AgentPanel {
None
};
let agent = self
let thread = self
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
@@ -784,46 +790,61 @@ impl AgentPanel {
.detach_and_log_err(cx);
}
let active_thread = cx.new(|cx| {
ActiveThread::new(
agent.clone(),
self.thread_store.clone(),
self.context_store.clone(),
context_store.clone(),
self.language_registry.clone(),
self.workspace.clone(),
window,
cx,
)
});
let fs = self.fs.clone();
let user_store = self.user_store.clone();
let thread_store = self.thread_store.clone();
let text_thread_store = self.context_store.clone();
let prompt_store = self.prompt_store.clone();
let language_registry = self.language_registry.clone();
let workspace = self.workspace.clone();
let message_editor = cx.new(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.user_store.clone(),
context_store.clone(),
self.prompt_store.clone(),
self.thread_store.downgrade(),
self.context_store.downgrade(),
agent.clone(),
window,
cx,
)
});
cx.spawn_in(window, async move |this, cx| {
let thread = thread.await?;
let active_thread = cx.new_window_entity(|window, cx| {
ActiveThread::new(
thread.clone(),
thread_store.clone(),
text_thread_store.clone(),
context_store.clone(),
language_registry.clone(),
workspace.clone(),
window,
cx,
)
})?;
if let Some(text) = preserved_text {
message_editor.update(cx, |editor, cx| {
editor.set_text(text, window, cx);
});
}
let message_editor = cx.new_window_entity(|window, cx| {
MessageEditor::new(
fs.clone(),
workspace.clone(),
user_store.clone(),
context_store.clone(),
prompt_store.clone(),
thread_store.downgrade(),
text_thread_store.downgrade(),
thread.clone(),
window,
cx,
)
})?;
message_editor.focus_handle(cx).focus(window);
if let Some(text) = preserved_text {
message_editor.update_in(cx, |editor, window, cx| {
editor.set_text(text, window, cx);
});
}
let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
self.set_active_view(thread_view, window, cx);
this.update_in(cx, |this, window, cx| {
message_editor.focus_handle(cx).focus(window);
AgentDiff::set_active_thread(&self.workspace, &agent, window, cx);
let thread_view =
ActiveView::thread(active_thread.clone(), message_editor, window, cx);
this.set_active_view(thread_view, window, cx);
AgentDiff::set_active_thread(&this.workspace, &thread, window, cx);
})
})
.detach_and_log_err(cx);
}
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -971,7 +992,7 @@ impl AgentPanel {
pub(crate) fn open_thread(
&mut self,
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -984,7 +1005,7 @@ impl AgentPanel {
let active_thread = cx.new(|cx| {
ActiveThread::new(
agent.clone(),
thread.clone(),
self.thread_store.clone(),
self.context_store.clone(),
context_store.clone(),
@@ -1003,7 +1024,7 @@ impl AgentPanel {
self.prompt_store.clone(),
self.thread_store.downgrade(),
self.context_store.downgrade(),
agent.clone(),
thread.clone(),
window,
cx,
)
@@ -1012,7 +1033,7 @@ impl AgentPanel {
let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
self.set_active_view(thread_view, window, cx);
AgentDiff::set_active_thread(&self.workspace, &agent, window, cx);
AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
}
pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
@@ -1137,10 +1158,10 @@ impl AgentPanel {
) {
match &self.active_view {
ActiveView::Thread { thread, .. } => {
let agent = thread.read(cx).agent().clone();
let thread = thread.read(cx).thread().clone();
self.workspace
.update(cx, |workspace, cx| {
AgentDiffPane::deploy_in_workspace(agent, workspace, window, cx)
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx)
})
.log_err();
}
@@ -1190,7 +1211,7 @@ impl AgentPanel {
match &self.active_view {
ActiveView::Thread { thread, .. } => {
active_thread::open_active_thread_as_markdown(
thread.read(cx).agent().clone(),
thread.read(cx).thread().clone(),
workspace,
window,
cx,
@@ -1228,9 +1249,9 @@ impl AgentPanel {
}
}
pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<ZedAgentThread>> {
pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
match &self.active_view {
ActiveView::Thread { thread, .. } => Some(thread.read(cx).agent().clone()),
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
_ => None,
}
}
@@ -1249,21 +1270,16 @@ impl AgentPanel {
return;
};
let agent_state = thread.read(cx).agent().read(cx);
if !agent_state.tool_use_limit_reached() {
let thread_state = thread.read(cx).thread().read(cx);
if !thread_state.tool_use_limit_reached() {
return;
}
let model = agent_state.configured_model().map(|cm| cm.model.clone());
if let Some(model) = model {
thread.update(cx, |active_thread, cx| {
active_thread.agent().update(cx, |agent, cx| {
agent.send_continue_message(model, Some(window.window_handle()), cx);
});
});
} else {
log::warn!("No configured model available for continuation");
}
thread.update(cx, |active_thread, cx| {
active_thread
.thread()
.update(cx, |thread, cx| thread.resume(window, cx))
});
}
fn toggle_burn_mode(
@@ -1277,10 +1293,10 @@ impl AgentPanel {
};
thread.update(cx, |active_thread, cx| {
active_thread.agent().update(cx, |agent, _cx| {
let current_mode = agent.completion_mode();
active_thread.thread().update(cx, |thread, _cx| {
let current_mode = thread.completion_mode();
agent.set_completion_mode(match current_mode {
thread.set_completion_mode(match current_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
@@ -1323,7 +1339,7 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => {
let thread = thread.read(cx);
if thread.is_empty() {
let id = thread.agent().read(cx).id().clone();
let id = thread.thread().read(cx).id().clone();
self.history_store.update(cx, |store, cx| {
store.remove_recently_opened_thread(id, cx);
});
@@ -1334,7 +1350,7 @@ impl AgentPanel {
match &new_view {
ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
let id = thread.read(cx).agent().read(cx).id().clone();
let id = thread.read(cx).thread().read(cx).id().clone();
store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
}),
ActiveView::TextThread { context_editor, .. } => {
@@ -1545,24 +1561,24 @@ impl AgentPanel {
let state = {
let active_thread = active_thread.read(cx);
if active_thread.is_empty() {
&ThreadSummary::Pending
&ThreadTitle::Pending
} else {
active_thread.summary(cx)
}
};
match state {
ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT.clone())
ThreadTitle::Pending => Label::new(ThreadTitle::DEFAULT.clone())
.truncate()
.into_any_element(),
ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
ThreadTitle::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
.truncate()
.into_any_element(),
ThreadSummary::Ready(_) => div()
ThreadTitle::Ready(_) => div()
.w_full()
.child(change_title_editor.clone())
.into_any_element(),
ThreadSummary::Error => h_flex()
ThreadTitle::Error => h_flex()
.w_full()
.child(change_title_editor.clone())
.child(
@@ -1719,7 +1735,7 @@ impl AgentPanel {
};
let active_thread = match &self.active_view {
ActiveView::Thread { thread, .. } => Some(thread.clone()),
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
};
@@ -1754,7 +1770,7 @@ impl AgentPanel {
this.action(
"New From Summary",
Box::new(NewThread {
from_thread_id: Some(thread.agent().read(cx).id().clone()),
from_thread_id: Some(thread.id().clone()),
}),
)
} else {
@@ -1897,14 +1913,14 @@ impl AgentPanel {
return None;
}
let agent = active_thread.agent().read(cx);
let is_generating = agent.is_generating();
let conversation_token_usage = agent.total_token_usage(cx)?;
let thread = active_thread.thread().read(cx);
let is_generating = thread.is_generating();
let conversation_token_usage = thread.total_token_usage()?;
let (total_token_usage, is_estimating) =
if let Some((editing_message_id, unsent_tokens)) = active_thread.editing_message_id() {
let combined = agent
.token_usage_up_to_message(editing_message_id, cx)
let combined = thread
.token_usage_up_to_message(editing_message_id)
.add(unsent_tokens);
(combined, unsent_tokens > 0)
@@ -2015,9 +2031,9 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => {
let is_using_zed_provider = thread
.read(cx)
.agent()
.thread()
.read(cx)
.configured_model()
.model()
.map_or(false, |model| {
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
});
@@ -2615,14 +2631,14 @@ impl AgentPanel {
}
};
let agent = active_thread.read(cx).agent().read(cx);
let thread = active_thread.read(cx).thread().read(cx);
let tool_use_limit_reached = agent.tool_use_limit_reached();
let tool_use_limit_reached = thread.tool_use_limit_reached();
if !tool_use_limit_reached {
return None;
}
let model = agent.configured_model()?.model;
let model = thread.model()?.model;
let focus_handle = self.focus_handle(cx);
@@ -2670,8 +2686,8 @@ impl AgentPanel {
let active_thread = active_thread.clone();
cx.listener(move |this, _, window, cx| {
active_thread.update(cx, |active_thread, cx| {
active_thread.agent().update(cx, |agent, _cx| {
agent.set_completion_mode(CompletionMode::Burn);
active_thread.thread().update(cx, |thread, _cx| {
thread.set_completion_mode(CompletionMode::Burn);
});
});
this.continue_conversation(window, cx);
@@ -3055,8 +3071,8 @@ impl Render for AgentPanel {
match &this.active_view {
ActiveView::Thread { thread, .. } => {
thread.update(cx, |active_thread, cx| {
active_thread.agent().update(cx, |agent, _cx| {
agent.set_completion_mode(CompletionMode::Burn);
active_thread.thread().update(cx, |thread, _cx| {
thread.set_completion_mode(CompletionMode::Burn);
});
});
this.continue_conversation(window, cx);

View File

@@ -26,7 +26,7 @@ mod ui;
use std::sync::Arc;
use agent::{ThreadId, ZedAgentThread};
use agent::{Thread, ThreadId};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
use assistant_slash_command::SlashCommandRegistry;
use client::Client;
@@ -114,14 +114,14 @@ impl ManageProfiles {
#[derive(Clone)]
pub(crate) enum ModelUsageContext {
Thread(Entity<ZedAgentThread>),
Thread(Entity<Thread>),
InlineAssistant,
}
impl ModelUsageContext {
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
match self {
Self::Thread(thread) => thread.read(cx).configured_model(),
Self::Thread(thread) => thread.read(cx).model(),
Self::InlineAssistant => {
LanguageModelRegistry::read_global(cx).inline_assistant_model()
}

View File

@@ -670,7 +670,7 @@ fn recent_context_picker_entries(
let mut threads = unordered_thread_entries(thread_store, text_thread_store, cx)
.filter(|(_, thread)| match thread {
ThreadContextEntry::Thread { id, .. } => {
Some(id) != active_thread_id && !current_threads.contains(id)
Some(id) != active_thread_id.as_ref() && !current_threads.contains(id)
}
ThreadContextEntry::Context { .. } => true,
})

View File

@@ -22,7 +22,7 @@ use util::ResultExt as _;
use workspace::Workspace;
use agent::{
ZedAgentThread,
Thread,
context::{AgentContextHandle, AgentContextKey, RULES_ICON},
thread_store::{TextThreadStore, ThreadStore},
};
@@ -449,7 +449,7 @@ impl ContextPickerCompletionProvider {
let context_store = context_store.clone();
let thread_store = thread_store.clone();
window.spawn::<_, Option<_>>(cx, async move |cx| {
let thread: Entity<ZedAgentThread> = thread_store
let thread: Entity<Thread> = thread_store
.update_in(cx, |thread_store, window, cx| {
thread_store.open_thread(&thread_id, window, cx)
})

View File

@@ -169,13 +169,13 @@ impl ContextStrip {
if self
.context_store
.read(cx)
.includes_thread(active_thread.id())
.includes_thread(&active_thread.id())
{
return None;
}
Some(SuggestedContext::Thread {
name: active_thread.summary().or_default(),
name: active_thread.title().or_default(),
thread: weak_active_thread,
})
} else if let Some(active_context_editor) = panel.active_context_editor() {

View File

@@ -9,7 +9,6 @@ use crate::ui::{
MaxModeTooltip,
preview::{AgentPreview, UsageCallout},
};
use agent::thread::UserMessageParams;
use agent::{
context::{AgentContextKey, ContextLoadResult, load_context},
context_store::ContextStoreEvent,
@@ -32,7 +31,7 @@ use gpui::{
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, Subscription, Task, TextStyle,
WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
};
use language::{Buffer, Language};
use language::{Buffer, Language, Point};
use language_model::{
ConfiguredModel, LanguageModelRequestMessage, MessageContent, ZED_CLOUD_PROVIDER_ID,
};
@@ -48,6 +47,7 @@ use ui::{
};
use util::ResultExt as _;
use workspace::{CollaboratorId, Workspace};
use zed_llm_client::CompletionIntent;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
@@ -58,14 +58,14 @@ use crate::{
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
};
use agent::{
MessageCrease, TokenUsageRatio, ZedAgentThread,
MessageCrease, Thread, TokenUsageRatio,
context_store::ContextStore,
thread_store::{TextThreadStore, ThreadStore},
};
#[derive(RegisterComponent)]
pub struct MessageEditor {
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
incompatible_tools_state: Entity<IncompatibleToolsState>,
editor: Entity<Editor>,
workspace: WeakEntity<Workspace>,
@@ -156,7 +156,7 @@ impl MessageEditor {
prompt_store: Option<Entity<PromptStore>>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
agent: Entity<ZedAgentThread>,
thread: Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -182,13 +182,13 @@ impl MessageEditor {
Some(text_thread_store.clone()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
ModelUsageContext::Thread(agent.clone()),
ModelUsageContext::Thread(thread.clone()),
window,
cx,
)
});
let incompatible_tools = cx.new(|cx| IncompatibleToolsState::new(agent.clone(), cx));
let incompatible_tools = cx.new(|cx| IncompatibleToolsState::new(thread.clone(), cx));
let subscriptions = vec![
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
@@ -200,7 +200,9 @@ impl MessageEditor {
// When context changes, reload it for token counting.
let _ = this.reload_context(cx);
}),
cx.observe(&agent.read(cx).action_log().clone(), |_, _, cx| cx.notify()),
cx.observe(&thread.read(cx).action_log().clone(), |_, _, cx| {
cx.notify()
}),
];
let model_selector = cx.new(|cx| {
@@ -208,20 +210,20 @@ impl MessageEditor {
fs.clone(),
model_selector_menu_handle,
editor.focus_handle(cx),
ModelUsageContext::Thread(agent.clone()),
ModelUsageContext::Thread(thread.clone()),
window,
cx,
)
});
let profile_selector =
cx.new(|cx| ProfileSelector::new(fs, agent.clone(), editor.focus_handle(cx), cx));
cx.new(|cx| ProfileSelector::new(fs, thread.clone(), editor.focus_handle(cx), cx));
Self {
editor: editor.clone(),
project: agent.read(cx).project().clone(),
project: thread.read(cx).project().clone(),
user_store,
agent,
thread,
incompatible_tools_state: incompatible_tools.clone(),
workspace,
context_store,
@@ -311,11 +313,11 @@ impl MessageEditor {
return;
}
self.agent.update(cx, |thread, cx| {
self.thread.update(cx, |thread, cx| {
thread.cancel_editing(cx);
});
if self.agent.read(cx).is_generating() {
if self.thread.read(cx).is_generating() {
self.stop_current_and_send_new_message(window, cx);
return;
}
@@ -352,7 +354,7 @@ impl MessageEditor {
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(ConfiguredModel { model, provider }) = self
.agent
.thread
.update(cx, |thread, cx| thread.get_or_init_configured_model(cx))
else {
return;
@@ -373,7 +375,7 @@ impl MessageEditor {
self.last_estimated_token_count.take();
cx.emit(MessageEditorEvent::EstimatedTokenCount);
let agent = self.agent.clone();
let thread = self.thread.clone();
let git_store = self.project.read(cx).git_store().clone();
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let context_task = self.reload_context(cx);
@@ -383,19 +385,16 @@ impl MessageEditor {
let (checkpoint, loaded_context) = future::join(checkpoint, context_task).await;
let loaded_context = loaded_context.unwrap_or_default();
agent
thread
.update(cx, |thread, cx| {
thread.send_message(
UserMessageParams {
text: user_message,
creases: user_message_creases,
checkpoint: checkpoint.ok(),
context: loaded_context,
},
model,
Some(window_handle),
cx,
);
todo!();
// thread.send(
// user_message,
// loaded_context,
// checkpoint.ok(),
// user_message_creases,
// cx,
// );
})
.log_err();
})
@@ -403,11 +402,11 @@ impl MessageEditor {
}
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.agent.update(cx, |thread, cx| {
self.thread.update(cx, |thread, cx| {
thread.cancel_editing(cx);
});
let cancelled = self.agent.update(cx, |thread, cx| {
let cancelled = self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
});
@@ -449,7 +448,7 @@ impl MessageEditor {
fn handle_review_click(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.edits_expanded = true;
AgentDiffPane::deploy(self.agent.clone(), self.workspace.clone(), window, cx).log_err();
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
cx.notify();
}
@@ -465,7 +464,7 @@ impl MessageEditor {
cx: &mut Context<Self>,
) {
if let Ok(diff) =
AgentDiffPane::deploy(self.agent.clone(), self.workspace.clone(), window, cx)
AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx)
{
let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);
diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
@@ -478,7 +477,7 @@ impl MessageEditor {
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.agent.update(cx, |thread, _cx| {
self.thread.update(cx, |thread, _cx| {
let active_completion_mode = thread.completion_mode();
thread.set_completion_mode(match active_completion_mode {
@@ -489,22 +488,36 @@ impl MessageEditor {
}
fn handle_accept_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
if self.agent.read(cx).has_pending_edit_tool_uses() {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
let action_log = self.agent.read(cx).action_log();
action_log.update(cx, |action_log, cx| action_log.keep_all_edits(cx));
self.thread.update(cx, |thread, cx| {
thread.keep_all_edits(cx);
});
cx.notify();
}
fn handle_reject_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
if self.agent.read(cx).has_pending_edit_tool_uses() {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
let action_log = self.agent.read(cx).action_log();
action_log.update(cx, |action_log, cx| action_log.reject_all_edits(cx));
// Since there's no reject_all_edits method in the thread API,
// we need to iterate through all buffers and reject their edits
let action_log = self.thread.read(cx).action_log().clone();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
for (buffer, _) in changed_buffers {
self.thread.update(cx, |thread, cx| {
let buffer_snapshot = buffer.read(cx);
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
thread
.reject_edits_in_ranges(buffer, vec![start..end], cx)
.detach();
});
}
cx.notify();
}
@@ -514,13 +527,17 @@ impl MessageEditor {
_window: &mut Window,
cx: &mut Context<Self>,
) {
if self.agent.read(cx).has_pending_edit_tool_uses() {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
let action_log = self.agent.read(cx).action_log();
action_log.update(cx, |action_log, cx| {
action_log.reject_buffer_edits(buffer, cx)
self.thread.update(cx, |thread, cx| {
let buffer_snapshot = buffer.read(cx);
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
thread
.reject_edits_in_ranges(buffer, vec![start..end], cx)
.detach();
});
cx.notify();
}
@@ -531,19 +548,21 @@ impl MessageEditor {
_window: &mut Window,
cx: &mut Context<Self>,
) {
if self.agent.read(cx).has_pending_edit_tool_uses() {
if self.thread.read(cx).has_pending_edit_tool_uses() {
return;
}
let action_log = self.agent.read(cx).action_log();
action_log.update(cx, |action_log, cx| {
action_log.keep_buffer_edits(buffer, cx)
self.thread.update(cx, |thread, cx| {
let buffer_snapshot = buffer.read(cx);
let start = buffer_snapshot.anchor_before(Point::new(0, 0));
let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
thread.keep_edits_in_range(buffer, start..end, cx);
});
cx.notify();
}
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let thread = self.agent.read(cx);
let thread = self.thread.read(cx);
let model = thread.configured_model();
if !model?.model.supports_burn_mode() {
return None;
@@ -614,7 +633,7 @@ impl MessageEditor {
}
fn render_editor(&self, window: &mut Window, cx: &mut Context<Self>) -> Div {
let thread = self.agent.read(cx);
let thread = self.thread.read(cx);
let model = thread.configured_model();
let editor_bg_color = cx.theme().colors().editor_background;
@@ -915,7 +934,7 @@ impl MessageEditor {
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
let is_edit_changes_expanded = self.edits_expanded;
let thread = self.agent.read(cx);
let thread = self.thread.read(cx);
let pending_edits = thread.has_pending_edit_tool_uses();
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
@@ -1217,7 +1236,7 @@ impl MessageEditor {
}
fn is_using_zed_provider(&self, cx: &App) -> bool {
self.agent
self.thread
.read(cx)
.configured_model()
.map_or(false, |model| {
@@ -1295,7 +1314,7 @@ impl MessageEditor {
Button::new("start-new-thread", "Start New Thread")
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
let from_thread_id = Some(this.agent.read(cx).id().clone());
let from_thread_id = Some(this.thread.read(cx).id().clone());
window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
})),
);
@@ -1329,11 +1348,10 @@ impl MessageEditor {
fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
let load_task = cx.spawn(async move |this, cx| {
let Ok(load_task) = this.update(cx, |this, cx| {
let new_context = this.context_store.read(cx).new_context_for_thread(
this.agent.read(cx),
None,
cx,
);
let new_context = this
.context_store
.read(cx)
.new_context_for_thread(this.thread.read(cx), None);
load_context(new_context, &this.project, &this.prompt_store, cx)
}) else {
return;
@@ -1365,7 +1383,7 @@ impl MessageEditor {
cx.emit(MessageEditorEvent::Changed);
self.update_token_count_task.take();
let Some(model) = self.agent.read(cx).configured_model() else {
let Some(model) = self.thread.read(cx).configured_model() else {
self.last_estimated_token_count.take();
return;
};
@@ -1570,16 +1588,16 @@ impl Focusable for MessageEditor {
impl Render for MessageEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let agent = self.agent.read(cx);
let token_usage_ratio = agent
.total_token_usage(cx)
let thread = self.thread.read(cx);
let token_usage_ratio = thread
.total_token_usage()
.map_or(TokenUsageRatio::Normal, |total_token_usage| {
total_token_usage.ratio()
});
let burn_mode_enabled = agent.completion_mode() == CompletionMode::Burn;
let burn_mode_enabled = thread.completion_mode() == CompletionMode::Burn;
let action_log = agent.action_log();
let action_log = self.thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
@@ -1662,7 +1680,7 @@ impl AgentPreview for MessageEditor {
let weak_project = project.downgrade();
let context_store = cx.new(|_cx| ContextStore::new(weak_project, None));
let active_thread = active_thread.read(cx);
let agent = active_thread.agent().clone();
let thread = active_thread.thread().clone();
let thread_store = active_thread.thread_store().clone();
let text_thread_store = active_thread.text_thread_store().clone();
@@ -1675,7 +1693,7 @@ impl AgentPreview for MessageEditor {
None,
thread_store.downgrade(),
text_thread_store.downgrade(),
agent,
thread,
window,
cx,
)

View File

@@ -1,6 +1,6 @@
use crate::{ManageProfiles, ToggleProfileSelector};
use agent::{
ZedAgentThread,
Thread,
agent_profile::{AgentProfile, AvailableProfiles},
};
use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
@@ -17,7 +17,7 @@ use ui::{
pub struct ProfileSelector {
profiles: AvailableProfiles,
fs: Arc<dyn Fs>,
thread: Entity<ZedAgentThread>,
thread: Entity<Thread>,
menu_handle: PopoverMenuHandle<ContextMenu>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
@@ -26,7 +26,7 @@ pub struct ProfileSelector {
impl ProfileSelector {
pub fn new(
fs: Arc<dyn Fs>,
thread: Entity<ZedAgentThread>,
thread: Entity<Thread>,
focus_handle: FocusHandle,
cx: &mut Context<Self>,
) -> Self {
@@ -156,7 +156,7 @@ impl Render for ProfileSelector {
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
let configured_model = self.thread.read(cx).configured_model().or_else(|| {
let configured_model = self.thread.read(cx).model().or_else(|| {
let model_registry = LanguageModelRegistry::read_global(cx);
model_registry.default_model()
});

View File

@@ -1,4 +1,4 @@
use agent::{ThreadEvent, ZedAgentThread};
use agent::{Thread, ThreadEvent};
use assistant_tool::{Tool, ToolSource};
use collections::HashMap;
use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
@@ -8,12 +8,12 @@ use ui::prelude::*;
pub struct IncompatibleToolsState {
cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
thread: Entity<ZedAgentThread>,
thread: Entity<Thread>,
_thread_subscription: Subscription,
}
impl IncompatibleToolsState {
pub fn new(thread: Entity<ZedAgentThread>, cx: &mut Context<Self>) -> Self {
pub fn new(thread: Entity<Thread>, cx: &mut Context<Self>) -> Self {
let _tool_working_set_subscription =
cx.subscribe(&thread, |this, _, event, _| match event {
ThreadEvent::ProfileChanged => {

View File

@@ -488,7 +488,7 @@ impl AddedContext {
parent: None,
tooltip: None,
icon_path: None,
status: if handle.agent.read(cx).is_generating_detailed_summary() {
status: if handle.thread.read(cx).is_generating_detailed_summary() {
ContextStatus::Loading {
message: "Summarizing…".into(),
}
@@ -496,9 +496,9 @@ impl AddedContext {
ContextStatus::Ready
},
render_hover: {
let agent = handle.agent.clone();
let thread = handle.thread.clone();
Some(Rc::new(move |_, cx| {
let text = agent.read(cx).latest_detailed_summary_or_text(cx);
let text = thread.read(cx).latest_detailed_summary_or_text(cx);
ContextPillHover::new_text(text.clone(), cx).into()
}))
},

View File

@@ -5,9 +5,6 @@ edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[features]
test-support = []
[lints]
workspace = true

View File

@@ -495,10 +495,6 @@ impl ActionLog {
cx.notify();
}
pub fn keep_buffer_edits(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
self.keep_edits_in_range(buffer, Anchor::MIN..Anchor::MAX, cx);
}
pub fn keep_edits_in_range(
&mut self,
buffer: Entity<Buffer>,
@@ -559,19 +555,6 @@ impl ActionLog {
}
}
pub fn reject_all_edits(&mut self, cx: &mut Context<Self>) {
let changed_buffers = self.changed_buffers(cx);
for (buffer, _) in changed_buffers {
self.reject_edits_in_ranges(buffer, vec![Anchor::MIN..Anchor::MAX], cx)
.detach();
}
}
pub fn reject_buffer_edits(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
self.reject_edits_in_ranges(buffer, vec![Anchor::MIN..Anchor::MAX], cx)
.detach()
}
pub fn reject_edits_in_ranges(
&mut self,
buffer: Entity<Buffer>,

View File

@@ -70,7 +70,7 @@ pub struct ToolResultOutput {
pub output: Option<serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
pub enum ToolResultContent {
Text(String),
Image(LanguageModelImage),
@@ -135,8 +135,7 @@ pub trait ToolCard: 'static + Sized {
) -> impl IntoElement;
}
#[derive(Debug, Clone)]
#[cfg_attr(any(test, feature = "test-support"), derive(PartialEq, Eq))]
#[derive(Clone)]
pub struct AnyToolCard {
entity: gpui::AnyEntity,
render: fn(

View File

@@ -29,7 +29,6 @@ impl Display for ContextServerId {
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
pub struct ContextServerCommand {
#[serde(rename = "command")]
pub path: String,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,

View File

@@ -868,7 +868,7 @@ impl DebugPanel {
let threads =
running_state.update(cx, |running_state, cx| {
let session = running_state.session();
session.read(cx).is_started().then(|| {
session.read(cx).is_running().then(|| {
session.update(cx, |session, cx| {
session.threads(cx)
})
@@ -1468,94 +1468,6 @@ impl Render for DebugPanel {
if has_sessions {
this.children(self.active_session.clone())
} else {
let docked_to_bottom = self.position(window, cx) == DockPosition::Bottom;
let welcome_experience = v_flex()
.when_else(
docked_to_bottom,
|this| this.w_2_3().h_full().pr_8(),
|this| this.w_full().h_1_3(),
)
.items_center()
.justify_center()
.gap_2()
.child(
Button::new("spawn-new-session-empty-state", "New Session")
.icon(IconName::Plus)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(crate::Start.boxed_clone(), cx);
}),
)
.child(
Button::new("edit-debug-settings", "Edit debug.json")
.icon(IconName::Code)
.icon_size(IconSize::XSmall)
.color(Color::Muted)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(
zed_actions::OpenProjectDebugTasks.boxed_clone(),
cx,
);
}),
)
.child(
Button::new("open-debugger-docs", "Debugger Docs")
.icon(IconName::Book)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/debugger")),
)
.child(
Button::new(
"spawn-new-session-install-extensions",
"Debugger Extensions",
)
.icon(IconName::Blocks)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(
zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::DebugAdapters,
),
}
.boxed_clone(),
cx,
);
}),
);
let breakpoint_list =
v_flex()
.group("base-breakpoint-list")
.items_start()
.when_else(
docked_to_bottom,
|this| this.min_w_1_3().h_full(),
|this| this.w_full().h_2_3(),
)
.p_1()
.child(
h_flex()
.pl_1()
.w_full()
.justify_between()
.child(Label::new("Breakpoints").size(LabelSize::Small))
.child(h_flex().visible_on_hover("base-breakpoint-list").child(
self.breakpoint_list.read(cx).render_control_strip(),
))
.track_focus(&self.breakpoint_list.focus_handle(cx)),
)
.child(Divider::horizontal())
.child(self.breakpoint_list.clone());
this.child(
v_flex()
.h_full()
@@ -1563,23 +1475,65 @@ impl Render for DebugPanel {
.items_center()
.justify_center()
.child(
div()
.when_else(docked_to_bottom, Div::h_flex, Div::v_flex)
.size_full()
.map(|this| {
if docked_to_bottom {
this.items_start()
.child(breakpoint_list)
.child(Divider::vertical())
.child(welcome_experience)
} else {
this.items_end()
.child(welcome_experience)
.child(Divider::horizontal())
.child(breakpoint_list)
}
}),
),
h_flex().size_full()
.items_start()
.child(v_flex().group("base-breakpoint-list").items_start().min_w_1_3().h_full().p_1()
.child(h_flex().pl_1().w_full().justify_between()
.child(Label::new("Breakpoints").size(LabelSize::Small))
.child(h_flex().visible_on_hover("base-breakpoint-list").child(self.breakpoint_list.read(cx).render_control_strip())))
.child(Divider::horizontal())
.child(self.breakpoint_list.clone()))
.child(Divider::vertical())
.child(
v_flex().w_2_3().h_full().items_center().justify_center()
.gap_2()
.pr_8()
.child(
Button::new("spawn-new-session-empty-state", "New Session")
.icon(IconName::Plus)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(crate::Start.boxed_clone(), cx);
})
)
.child(
Button::new("edit-debug-settings", "Edit debug.json")
.icon(IconName::Code)
.icon_size(IconSize::XSmall)
.color(Color::Muted)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx);
})
)
.child(
Button::new("open-debugger-docs", "Debugger Docs")
.icon(IconName::Book)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, _, cx| {
cx.open_url("https://zed.dev/docs/debugger")
})
)
.child(
Button::new("spawn-new-session-install-extensions", "Debugger Extensions")
.icon(IconName::Blocks)
.color(Color::Muted)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(zed_actions::Extensions { category_filter: Some(zed_actions::ExtensionCategoryFilter::DebugAdapters)}.boxed_clone(), cx);
})
)
)
)
)
}
})

View File

@@ -877,13 +877,20 @@ impl LineBreakpoint {
})
.cursor_pointer()
.child(
Label::new(format!("{}:{}", self.name, self.line))
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel),
h_flex()
.gap_1()
.child(
Label::new(format!("{}:{}", self.name, self.line))
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel),
)
.children(self.dir.clone().map(|dir| {
Label::new(dir)
.color(Color::Muted)
.size(LabelSize::Small)
.line_height_style(ui::LineHeightStyle::UiLabel)
})),
)
.when_some(self.dir.as_ref(), |this, parent_dir| {
this.tooltip(Tooltip::text(format!("Worktree parent path: {parent_dir}")))
})
.child(BreakpointOptionsStrip {
props,
breakpoint: BreakpointEntry {

View File

@@ -114,7 +114,7 @@ impl Console {
}
fn is_running(&self, cx: &Context<Self>) -> bool {
self.session.read(cx).is_started()
self.session.read(cx).is_running()
}
fn handle_stack_frame_list_events(

View File

@@ -1,5 +1,3 @@
use crate::display_map::inlay_map::InlayChunk;
use super::{
Highlights,
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
@@ -1062,7 +1060,7 @@ impl sum_tree::Summary for TransformSummary {
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)]
pub struct FoldId(pub(super) usize);
pub struct FoldId(usize);
impl From<FoldId> for ElementId {
fn from(val: FoldId) -> Self {
@@ -1313,7 +1311,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> {
pub struct FoldChunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
inlay_chunks: InlayChunks<'a>,
inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
inlay_chunk: Option<(InlayOffset, language::Chunk<'a>)>,
inlay_offset: InlayOffset,
output_offset: FoldOffset,
max_output_offset: FoldOffset,
@@ -1405,8 +1403,7 @@ impl<'a> Iterator for FoldChunks<'a> {
}
// Otherwise, take a chunk from the buffer's text.
if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
let chunk = &mut inlay_chunk.chunk;
if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
let transform_end = self.transform_cursor.end(&()).1;
let chunk_end = buffer_chunk_end.min(transform_end);
@@ -1431,7 +1428,7 @@ impl<'a> Iterator for FoldChunks<'a> {
is_tab: chunk.is_tab,
is_inlay: chunk.is_inlay,
underline: chunk.underline,
renderer: inlay_chunk.renderer,
renderer: None,
});
}

View File

@@ -1,4 +1,4 @@
use crate::{ChunkRenderer, HighlightStyles, InlayId, display_map::FoldId};
use crate::{HighlightStyles, InlayId};
use collections::BTreeSet;
use gpui::{Hsla, Rgba};
use language::{Chunk, Edit, Point, TextSummary};
@@ -8,11 +8,9 @@ use multi_buffer::{
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
sync::Arc,
};
use sum_tree::{Bias, Cursor, SumTree};
use text::{Patch, Rope};
use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
use super::{Highlights, custom_highlights::CustomHighlightsChunks};
@@ -254,13 +252,6 @@ pub struct InlayChunks<'a> {
snapshot: &'a InlaySnapshot,
}
#[derive(Clone)]
pub struct InlayChunk<'a> {
pub chunk: Chunk<'a>,
/// Whether the inlay should be customly rendered.
pub renderer: Option<ChunkRenderer>,
}
impl InlayChunks<'_> {
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
self.transforms.seek(&new_range.start, Bias::Right, &());
@@ -280,7 +271,7 @@ impl InlayChunks<'_> {
}
impl<'a> Iterator for InlayChunks<'a> {
type Item = InlayChunk<'a>;
type Item = Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.output_offset == self.max_output_offset {
@@ -305,12 +296,9 @@ impl<'a> Iterator for InlayChunks<'a> {
chunk.text = suffix;
self.output_offset.0 += prefix.len();
InlayChunk {
chunk: Chunk {
text: prefix,
..chunk.clone()
},
renderer: None,
Chunk {
text: prefix,
..chunk.clone()
}
}
Transform::Inlay(inlay) => {
@@ -325,7 +313,6 @@ impl<'a> Iterator for InlayChunks<'a> {
}
}
let mut renderer = None;
let mut highlight_style = match inlay.id {
InlayId::InlineCompletion(_) => {
self.highlight_styles.inline_completion.map(|s| {
@@ -338,33 +325,14 @@ impl<'a> Iterator for InlayChunks<'a> {
}
InlayId::Hint(_) => self.highlight_styles.inlay_hint,
InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
InlayId::Color(id) => {
if let Some(color) = inlay.color {
renderer = Some(ChunkRenderer {
id: FoldId(id),
render: Arc::new(move |cx| {
div()
.w_4()
.h_4()
.relative()
.child(
div()
.absolute()
.right_1()
.w_3p5()
.h_3p5()
.border_2()
.border_color(cx.theme().colors().border)
.bg(color),
)
.into_any_element()
}),
constrain_width: false,
measured_width: None,
});
InlayId::Color(_) => match inlay.color {
Some(color) => {
let mut style = self.highlight_styles.inlay_hint.unwrap_or_default();
style.color = Some(color);
Some(style)
}
self.highlight_styles.inlay_hint
}
None => self.highlight_styles.inlay_hint,
},
};
let next_inlay_highlight_endpoint;
let offset_in_inlay = self.output_offset - self.transforms.start().0;
@@ -402,14 +370,11 @@ impl<'a> Iterator for InlayChunks<'a> {
self.output_offset.0 += chunk.len();
InlayChunk {
chunk: Chunk {
text: chunk,
highlight_style,
is_inlay: true,
..Chunk::default()
},
renderer,
Chunk {
text: chunk,
highlight_style,
is_inlay: true,
..Default::default()
}
}
};
@@ -1101,7 +1066,7 @@ impl InlaySnapshot {
#[cfg(test)]
pub fn text(&self) -> String {
self.chunks(Default::default()..self.len(), false, Highlights::default())
.map(|chunk| chunk.chunk.text)
.map(|chunk| chunk.text)
.collect()
}
@@ -1739,7 +1704,7 @@ mod tests {
..Highlights::default()
},
)
.map(|chunk| chunk.chunk.text)
.map(|chunk| chunk.text)
.collect::<String>();
assert_eq!(
actual_text,

View File

@@ -547,7 +547,6 @@ pub enum SoftWrap {
#[derive(Clone)]
pub struct EditorStyle {
pub background: Hsla,
pub border: Hsla,
pub local_player: PlayerColor,
pub text: TextStyle,
pub scrollbar_width: Pixels,
@@ -563,7 +562,6 @@ impl Default for EditorStyle {
fn default() -> Self {
Self {
background: Hsla::default(),
border: Hsla::default(),
local_player: PlayerColor::default(),
text: TextStyle::default(),
scrollbar_width: Pixels::default(),
@@ -22407,7 +22405,6 @@ impl Render for Editor {
&cx.entity(),
EditorStyle {
background,
border: cx.theme().colors().border,
local_player: cx.theme().players().local(),
text: text_style,
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,

View File

@@ -10,7 +10,7 @@ use crate::{
ToolMetrics,
assertions::{AssertionsReport, RanAssertion, RanAssertionResult},
};
use agent::{ThreadEvent, ZedAgentThread};
use agent::{ContextLoadResult, Thread, ThreadEvent};
use agent_settings::AgentProfileId;
use anyhow::{Result, anyhow};
use async_trait::async_trait;
@@ -89,7 +89,7 @@ impl Error for FailedAssertion {}
pub struct ExampleContext {
meta: ExampleMetadata,
log_prefix: String,
agent_thread: Entity<agent::ZedAgentThread>,
agent_thread: Entity<agent::Thread>,
app: AsyncApp,
model: Arc<dyn LanguageModel>,
pub assertions: AssertionsReport,
@@ -100,7 +100,7 @@ impl ExampleContext {
pub fn new(
meta: ExampleMetadata,
log_prefix: String,
agent_thread: Entity<ZedAgentThread>,
agent_thread: Entity<Thread>,
model: Arc<dyn LanguageModel>,
app: AsyncApp,
) -> Self {
@@ -120,7 +120,13 @@ impl ExampleContext {
pub fn push_user_message(&mut self, text: impl ToString) {
self.app
.update_entity(&self.agent_thread, |thread, cx| {
thread.insert_user_message(text.to_string(), cx);
thread.insert_user_message(
text.to_string(),
ContextLoadResult::default(),
None,
Vec::new(),
cx,
);
})
.unwrap();
}
@@ -244,7 +250,6 @@ impl ExampleContext {
| ThreadEvent::UsePendingTools { .. }
| ThreadEvent::CompletionCanceled => {}
ThreadEvent::ToolUseLimitReached => {}
ThreadEvent::StreamedToolUse2 { .. } => {}
ThreadEvent::ToolFinished {
tool_use_id,
pending_tool_use,
@@ -307,10 +312,10 @@ impl ExampleContext {
let model = self.model.clone();
let message_count_before = self.app.update_entity(&self.agent_thread, |agent, cx| {
agent.set_remaining_turns(iterations);
agent.send_to_model(model, CompletionIntent::UserPrompt, None, cx);
agent.messages().len()
let message_count_before = self.app.update_entity(&self.agent_thread, |thread, cx| {
thread.set_remaining_turns(iterations);
thread.send_to_model(model, CompletionIntent::UserPrompt, None, cx);
thread.messages().len()
})?;
loop {
@@ -328,13 +333,13 @@ impl ExampleContext {
}
}
let messages = self.app.read_entity(&self.agent_thread, |agent, cx| {
let messages = self.app.read_entity(&self.agent_thread, |thread, cx| {
let mut messages = Vec::new();
for message in agent.messages().skip(message_count_before) {
for message in thread.messages().skip(message_count_before) {
messages.push(Message {
_role: message.role,
text: message.to_string(),
tool_use: agent
tool_use: thread
.tool_uses_for_message(message.id, cx)
.into_iter()
.map(|tool_use| ToolUse {
@@ -382,7 +387,7 @@ impl ExampleContext {
.unwrap()
}
pub fn agent_thread(&self) -> Entity<ZedAgentThread> {
pub fn agent_thread(&self) -> Entity<Thread> {
self.agent_thread.clone()
}
}

View File

@@ -32,9 +32,9 @@ impl Example for CommentTranslation {
cx.run_to_end().await?;
let mut create_or_overwrite_count = 0;
cx.agent_thread().read_with(cx, |agent, cx| {
for message in agent.messages() {
for tool_use in agent.tool_uses_for_message(message.id, cx) {
cx.agent_thread().read_with(cx, |thread, cx| {
for message in thread.messages() {
for tool_use in thread.tool_uses_for_message(message.id, cx) {
if tool_use.name == "edit_file" {
let input: EditFileToolInput = serde_json::from_value(tool_use.input)?;
if !matches!(input.mode, EditFileMode::Edit) {

View File

@@ -1,4 +1,3 @@
use agent::thread::ToolUseSegment;
use agent::{Message, MessageSegment, SerializedThread, ThreadStore};
use anyhow::{Context as _, Result, anyhow, bail};
use assistant_tool::ToolWorkingSet;
@@ -308,7 +307,7 @@ impl ExampleInstance {
let thread_store = thread_store.await?;
let agent =
let thread =
thread_store.update(cx, |thread_store, cx| {
let thread = if let Some(json) = &meta.existing_thread_json {
let serialized = SerializedThread::from_json(json.as_bytes()).expect("Can't read serialized thread");
@@ -323,7 +322,7 @@ impl ExampleInstance {
})?;
agent.update(cx, |thread, _cx| {
thread.update(cx, |thread, _cx| {
let mut request_count = 0;
let previous_diff = Rc::new(RefCell::new("".to_string()));
let example_output_dir = this.run_directory.clone();
@@ -371,7 +370,7 @@ impl ExampleInstance {
let mut example_cx = ExampleContext::new(
meta.clone(),
this.log_prefix.clone(),
agent.clone(),
thread.clone(),
model.clone(),
cx.clone(),
);
@@ -420,12 +419,11 @@ impl ExampleInstance {
fs::write(this.run_directory.join("diagnostics_after.txt"), diagnostics_after)?;
}
agent.update(cx, |agent, _cx| {
let response_count = agent
thread.update(cx, |thread, _cx| {
let response_count = thread
.messages()
.filter(|message| message.role == language_model::Role::Assistant)
.count();
let all_messages = messages_to_markdown(agent.messages());
RunOutput {
repository_diff,
diagnostic_summary_before,
@@ -433,9 +431,9 @@ impl ExampleInstance {
diagnostics_before,
diagnostics_after,
response_count,
token_usage: agent.cumulative_token_usage(),
token_usage: thread.cumulative_token_usage(),
tool_metrics: example_cx.tool_metrics.lock().unwrap().clone(),
all_messages,
all_messages: messages_to_markdown(thread.messages()),
programmatic_assertions: example_cx.assertions,
}
})
@@ -850,9 +848,11 @@ fn messages_to_markdown<'a>(message_iter: impl IntoIterator<Item = &'a Message>)
messages.push_str(&text);
messages.push_str("\n");
}
MessageSegment::ToolUse(ToolUseSegment { name, input, .. }) => {
messages.push_str(&format!("**Tool Use**: {}\n\n", name));
messages.push_str(&format!("Input: {:?}\n\n", input));
MessageSegment::RedactedThinking(items) => {
messages.push_str(&format!(
"**Redacted Thinking**: {} item(s)\n\n",
items.len()
));
}
}
}

View File

@@ -2,7 +2,7 @@ use crate::{
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
LanguageModelToolChoice, LanguageModelToolUse, StopReason,
LanguageModelToolChoice,
};
use futures::{FutureExt, StreamExt, channel::mpsc, future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, App, AsyncApp, Entity, Task, Window};
@@ -91,12 +91,7 @@ pub struct ToolUseRequest {
#[derive(Default)]
pub struct FakeLanguageModel {
current_completion_txs: Mutex<
Vec<(
LanguageModelRequest,
mpsc::UnboundedSender<LanguageModelCompletionEvent>,
)>,
>,
current_completion_txs: Mutex<Vec<(LanguageModelRequest, mpsc::UnboundedSender<String>)>>,
}
impl FakeLanguageModel {
@@ -115,7 +110,7 @@ impl FakeLanguageModel {
pub fn stream_completion_response(
&self,
request: &LanguageModelRequest,
stream: impl Into<FakeLanguageModelStream>,
chunk: impl Into<String>,
) {
let current_completion_txs = self.current_completion_txs.lock();
let tx = current_completion_txs
@@ -123,9 +118,7 @@ impl FakeLanguageModel {
.find(|(req, _)| req == request)
.map(|(_, tx)| tx)
.unwrap();
for event in stream.into().events {
tx.unbounded_send(event).unwrap();
}
tx.unbounded_send(chunk.into()).unwrap();
}
pub fn end_completion_stream(&self, request: &LanguageModelRequest) {
@@ -134,7 +127,7 @@ impl FakeLanguageModel {
.retain(|(req, _)| req != request);
}
pub fn stream_last_completion_response(&self, chunk: impl Into<FakeLanguageModelStream>) {
pub fn stream_last_completion_response(&self, chunk: impl Into<String>) {
self.stream_completion_response(self.pending_completions().last().unwrap(), chunk);
}
@@ -143,29 +136,6 @@ impl FakeLanguageModel {
}
}
pub struct FakeLanguageModelStream {
events: Vec<LanguageModelCompletionEvent>,
}
impl<T: Into<String>> From<T> for FakeLanguageModelStream {
fn from(chunk: T) -> Self {
Self {
events: vec![LanguageModelCompletionEvent::Text(chunk.into())],
}
}
}
impl From<LanguageModelToolUse> for FakeLanguageModelStream {
fn from(tool_use: LanguageModelToolUse) -> Self {
Self {
events: vec![
LanguageModelCompletionEvent::ToolUse(tool_use),
LanguageModelCompletionEvent::Stop(StopReason::ToolUse),
],
}
}
}
impl LanguageModel for FakeLanguageModel {
fn id(&self) -> LanguageModelId {
language_model_id()
@@ -220,7 +190,12 @@ impl LanguageModel for FakeLanguageModel {
> {
let (tx, rx) = mpsc::unbounded();
self.current_completion_txs.lock().push((request, tx));
async move { Ok(rx.map(Ok).boxed()) }.boxed()
async move {
Ok(rx
.map(|text| Ok(LanguageModelCompletionEvent::Text(text)))
.boxed())
}
.boxed()
}
fn as_fake(&self) -> &Self {

View File

@@ -330,14 +330,6 @@ impl MessageContent {
| MessageContent::Image(_) => false,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn as_tool_result(&self) -> Option<&LanguageModelToolResult> {
match self {
MessageContent::ToolResult(tool_result) => Some(tool_result),
_ => None,
}
}
}
impl From<String> for MessageContent {
@@ -372,40 +364,6 @@ impl LanguageModelRequestMessage {
pub fn contents_empty(&self) -> bool {
self.content.iter().all(|content| content.is_empty())
}
pub fn push(&mut self, content: MessageContent) {
if let Some(last_content) = self.content.last_mut() {
match (last_content, content) {
(MessageContent::Text(last_text), MessageContent::Text(new_text)) => {
last_text.push_str(&new_text);
}
(
MessageContent::Thinking {
text: last_text,
signature,
},
MessageContent::Thinking {
text: new_text,
signature: new_signature,
},
) => {
last_text.push_str(&new_text);
if signature.is_none() {
*signature = new_signature;
}
}
(
MessageContent::RedactedThinking(last_text),
MessageContent::RedactedThinking(new_text),
) => {
last_text.push_str(&new_text);
}
(_, content) => self.content.push(content),
}
} else {
self.content.push(content);
}
}
}
#[derive(Debug, PartialEq, Hash, Clone, Serialize, Deserialize)]

View File

@@ -87,9 +87,3 @@ pub(crate) mod m_2025_06_25 {
pub(crate) use settings::SETTINGS_PATTERNS;
}
pub(crate) mod m_2025_06_27 {
mod settings;
pub(crate) use settings::SETTINGS_PATTERNS;
}

View File

@@ -1,133 +0,0 @@
use std::ops::Range;
use tree_sitter::{Query, QueryMatch};
use crate::MigrationPatterns;
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
SETTINGS_CONTEXT_SERVER_PATTERN,
flatten_context_server_command,
)];
const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document
(object
(pair
key: (string (string_content) @context-servers)
value: (object
(pair
key: (string (string_content) @server-name)
value: (object
(pair
key: (string (string_content) @source-key)
value: (string (string_content) @source-value)
)
(pair
key: (string (string_content) @command-key)
value: (object) @command-object
) @command-pair
) @server-settings
)
)
)
)
(#eq? @context-servers "context_servers")
(#eq? @source-key "source")
(#eq? @source-value "custom")
(#eq? @command-key "command")
)"#;
fn flatten_context_server_command(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let command_pair_index = query.capture_index_for_name("command-pair")?;
let command_pair = mat.nodes_for_capture_index(command_pair_index).next()?;
let command_object_index = query.capture_index_for_name("command-object")?;
let command_object = mat.nodes_for_capture_index(command_object_index).next()?;
let server_settings_index = query.capture_index_for_name("server-settings")?;
let _server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
// Parse the command object to extract path, args, and env
let mut path_value = None;
let mut args_value = None;
let mut env_value = None;
let mut cursor = command_object.walk();
for child in command_object.children(&mut cursor) {
if child.kind() == "pair" {
if let Some(key_node) = child.child_by_field_name("key") {
if let Some(string_content) = key_node.child(1) {
let key = &contents[string_content.byte_range()];
if let Some(value_node) = child.child_by_field_name("value") {
let value_range = value_node.byte_range();
match key {
"path" => path_value = Some(&contents[value_range]),
"args" => args_value = Some(&contents[value_range]),
"env" => env_value = Some(&contents[value_range]),
_ => {}
}
}
}
}
}
}
let path = path_value?;
// Get the proper indentation from the command pair
let command_pair_start = command_pair.start_byte();
let line_start = contents[..command_pair_start]
.rfind('\n')
.map(|pos| pos + 1)
.unwrap_or(0);
let indent = &contents[line_start..command_pair_start];
// Build the replacement string
let mut replacement = format!("\"command\": {}", path);
// Add args if present - need to reduce indentation
if let Some(args) = args_value {
replacement.push_str(",\n");
replacement.push_str(indent);
replacement.push_str("\"args\": ");
let reduced_args = reduce_indentation(args, 4);
replacement.push_str(&reduced_args);
}
// Add env if present - need to reduce indentation
if let Some(env) = env_value {
replacement.push_str(",\n");
replacement.push_str(indent);
replacement.push_str("\"env\": ");
replacement.push_str(&reduce_indentation(env, 4));
}
let range_to_replace = command_pair.byte_range();
Some((range_to_replace, replacement))
}
fn reduce_indentation(text: &str, spaces: usize) -> String {
let lines: Vec<&str> = text.lines().collect();
let mut result = String::new();
for (i, line) in lines.iter().enumerate() {
if i > 0 {
result.push('\n');
}
// Count leading spaces
let leading_spaces = line.chars().take_while(|&c| c == ' ').count();
if leading_spaces >= spaces {
// Reduce indentation
result.push_str(&line[spaces..]);
} else {
// Keep line as is if it doesn't have enough indentation
result.push_str(line);
}
}
result
}

View File

@@ -156,10 +156,6 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
migrations::m_2025_06_25::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_06_25,
),
(
migrations::m_2025_06_27::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_06_27,
),
];
run_migrations(text, migrations)
}
@@ -266,10 +262,6 @@ define_query!(
SETTINGS_QUERY_2025_06_25,
migrations::m_2025_06_25::SETTINGS_PATTERNS
);
define_query!(
SETTINGS_QUERY_2025_06_27,
migrations::m_2025_06_27::SETTINGS_PATTERNS
);
// custom query
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
@@ -294,15 +286,6 @@ mod tests {
pretty_assertions::assert_eq!(migrated.as_deref(), output);
}
fn assert_migrate_settings_with_migrations(
migrations: &[(MigrationPatterns, &Query)],
input: &str,
output: Option<&str>,
) {
let migrated = run_migrations(input, migrations).unwrap();
pretty_assertions::assert_eq!(migrated.as_deref(), output);
}
#[test]
fn test_replace_array_with_single_string() {
assert_migrate_keymap(
@@ -890,11 +873,7 @@ mod tests {
#[test]
fn test_mcp_settings_migration() {
assert_migrate_settings_with_migrations(
&[(
migrations::m_2025_06_16::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_06_16,
)],
assert_migrate_settings(
r#"{
"context_servers": {
"empty_server": {},
@@ -1079,14 +1058,7 @@ mod tests {
}
}
}"#;
assert_migrate_settings_with_migrations(
&[(
migrations::m_2025_06_16::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_06_16,
)],
settings,
None,
);
assert_migrate_settings(settings, None);
}
#[test]
@@ -1159,100 +1131,4 @@ mod tests {
None,
);
}
#[test]
fn test_flatten_context_server_command() {
assert_migrate_settings(
r#"{
"context_servers": {
"some-mcp-server": {
"source": "custom",
"command": {
"path": "npx",
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
}"#,
Some(
r#"{
"context_servers": {
"some-mcp-server": {
"source": "custom",
"command": "npx",
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}"#,
),
);
// Test with additional keys in server object
assert_migrate_settings(
r#"{
"context_servers": {
"server-with-extras": {
"source": "custom",
"command": {
"path": "/usr/bin/node",
"args": ["server.js"]
},
"settings": {}
}
}
}"#,
Some(
r#"{
"context_servers": {
"server-with-extras": {
"source": "custom",
"command": "/usr/bin/node",
"args": ["server.js"],
"settings": {}
}
}
}"#,
),
);
// Test command without args or env
assert_migrate_settings(
r#"{
"context_servers": {
"simple-server": {
"source": "custom",
"command": {
"path": "simple-mcp-server"
}
}
}
}"#,
Some(
r#"{
"context_servers": {
"simple-server": {
"source": "custom",
"command": "simple-mcp-server"
}
}
}"#,
),
);
}
}

View File

@@ -1037,6 +1037,10 @@ impl Session {
matches!(self.mode, Mode::Building)
}
pub fn is_running(&self) -> bool {
matches!(self.mode, Mode::Running(_))
}
pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
match &mut self.mode {
Mode::Running(local_mode) => Some(local_mode),

View File

@@ -97,8 +97,9 @@ pub enum ContextServerSettings {
/// Whether the context server is enabled.
#[serde(default = "default_true")]
enabled: bool,
#[serde(flatten)]
/// The command to run this context server.
///
/// This will override the command set by an extension.
command: ContextServerCommand,
},
Extension {

View File

@@ -8,7 +8,7 @@ use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
use serde_json::Value;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId, type_name},
@@ -967,38 +967,16 @@ impl SettingsStore {
}
}
const ZED_SETTINGS: &str = "ZedSettings";
let RootSchema {
meta_schema,
schema: zed_settings_schema,
mut definitions,
} = combined_schema;
definitions.insert(ZED_SETTINGS.to_string(), zed_settings_schema.into());
let zed_settings_ref = Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}"));
// settings file contents matches ZedSettings + overrides for each release stage
let mut root_schema = json!({
"allOf": [
zed_settings_ref,
{
"properties": {
"dev": zed_settings_ref,
"nightly": zed_settings_ref,
"stable": zed_settings_ref,
"preview": zed_settings_ref,
}
}
],
"definitions": definitions,
});
if let Some(meta_schema) = meta_schema {
if let Some(root_schema_object) = root_schema.as_object_mut() {
root_schema_object.insert("$schema".to_string(), meta_schema.into());
}
for release_stage in ["dev", "nightly", "stable", "preview"] {
let schema = combined_schema.schema.clone();
combined_schema
.schema
.object()
.properties
.insert(release_stage.to_string(), schema.into());
}
root_schema
serde_json::to_value(&combined_schema).unwrap()
}
fn recompute_values(

View File

@@ -1,14 +1,9 @@
# R
R support is available via multiple R Zed extensions:
R support is available through the [R extension](https://github.com/ocsmit/zed-r).
- [ocsmit/zed-r](https://github.com/ocsmit/zed-r)
- Tree-sitter: [r-lib/tree-sitter-r](https://github.com/r-lib/tree-sitter-r)
- Language-Server: [REditorSupport/languageserver](https://github.com/REditorSupport/languageserver)
- [posit-dev/air](https://github.com/posit-dev/air/tree/main/editors/zed)
- Language-Server: [posit-dev/air](https://github.com/posit-dev/air)
- Tree-sitter: [r-lib/tree-sitter-r](https://github.com/r-lib/tree-sitter-r)
- Language-Server: [REditorSupport/languageserver](https://github.com/REditorSupport/languageserver)
## Installation
@@ -20,7 +15,7 @@ install.packages("languageserver")
install.packages("lintr")
```
3. Install the [ocsmit/zed-r](https://github.com/ocsmit/zed-r) through Zed's extensions manager.
3. Install the [R Zed extension](https://github.com/ocsmit/zed-r) through Zed's extensions manager.
For example on macOS:
@@ -33,65 +28,7 @@ Rscript -e 'packageVersion("languageserver")'
Rscript -e 'packageVersion("lintr")'
```
## Configuration
### Linting
`REditorSupport/languageserver` bundles support for [r-lib/lintr](https://github.com/r-lib/lintr) as a linter. This can be configured via the use of a `.lintr` inside your project (or in your home directory for global defaults).
```r
linters: linters_with_defaults(
line_length_linter(120),
commented_code_linter = NULL
)
exclusions: list(
"inst/doc/creating_linters.R" = 1,
"inst/example/bad.R",
"tests/testthat/exclusions-test"
)
```
Or exclude it from linting anything,
```r
exclusions: list(".")
```
See [Using lintr](https://lintr.r-lib.org/articles/lintr.html) for a complete list of options,
### Formatting
`REditorSupport/languageserver` bundles support for [r-lib/styler](https://github.com/r-lib/styler) as a formatter. See [Customizing Styler](https://cran.r-project.org/web/packages/styler/vignettes/customizing_styler.html) for more information on how to customize its behavior.
### REditorSupport/languageserver Configuration
You can configure the [R languageserver settings](https://github.com/REditorSupport/languageserver#settings) via Zed Project Settings `.zed/settings.json` or Zed User Settings `~/.config/zed/settings.json`:
For example to disable Lintr linting and suppress code snippet suggestions (both enabled by default):
```json
{
"lsp": {
"r_language_server": {
"settings": {
"r": {
"lsp": {
"diagnostics": false,
"snippet_support": false
}
}
}
}
}
}
```
<!--
TBD: R REPL Docs
## REPL
### Ark Installation
## Ark Installation
To use the Zed REPL with R you need to install [Ark](https://github.com/posit-dev/ark), an R Kernel for Jupyter applications.
You can down the latest version from the [Ark GitHub Releases](https://github.com/posit-dev/ark/releases) and then extract the `ark` binary to a directory in your `PATH`.
@@ -119,4 +56,6 @@ unzip ark-latest-linux.zip ark
sudo mv /tmp/ark /usr/local/bin/
```
<!--
TBD: R REPL Docs
-->

View File

@@ -79,35 +79,21 @@ h6 code {
display: none !important;
}
h1 {
font-size: 3.4rem;
}
h2 {
padding-bottom: 1rem;
border-bottom: 1px solid;
border-color: var(--border-light);
}
h3 {
font-size: 2rem;
}
h4 {
font-size: 1.8rem;
}
h5 {
font-size: 1.6rem;
}
h2,
h3,
h4,
h5 {
h3 {
margin-block-start: 1.5em;
margin-block-end: 0;
}
h4,
h5 {
margin-block-start: 2em;
}
.header + .header h3,
.header + .header h4,